目的
提高软件系统的可维护性和可复用性,增加软件的可拓展性和灵活性,程序员遵循 6 条原则来开发程序,从而提高软件开发效率、节约软件开发成本和维护成本。
开闭原则(OCP)
核心思想
1. 对拓展开放
- 软件模块应该在不修改原有代码的情况下,通过扩展的方式增加新功能。
- 目标:提高系统的可拓展性,适应不断变化的需求。
2. 对修改关闭
- 在新增需求时,尽量避免修改现有代码。
- 目标:降低由于修改代码引发的潜在问题,提高系统的稳定性。
实现方式
1. 使用抽象
- 通过定义接口或抽象类,让具体实现依赖于抽象,而不是直接依赖具体类。
- 增加新功能时,实现新的子类,不需要修改原有类。
2. 多态机制
- 借助继承与多态,在运行时决定调用哪个具体类实现。
示例
AbstractSkin:
// 皮肤抽象类
public abstract class AbstractSkin {
public abstract void display();
}
DefaultSpecificSkin:
// 默认皮肤
public class DefaultSpecificSkin extends AbstractSkin{
@Override
public void display() {
System.out.println("默认皮肤.....");
}
}
SupermanSpecificSkin:
// 超级英雄皮肤
public class SupermanSpecificSkin extends AbstractSkin{
@Override
public void display() {
System.out.println("超人皮肤.....");
}
}
SougouInput:
// 搜狗输入法
public class SougouInput {
private AbstractSkin skin;
public void setSkin(AbstractSkin skin) {
this.skin = skin;
}
public void display() {
skin.display();
}
}
Client:
public class Client {
public static void main(String[] args) {
// 1.创建搜狗输入法对象
SougouInput input = new SougouInput();
// 2.创建皮肤对象
// AbstractSkin skin = new SupermanSpecificSkin();
AbstractSkin skin = new DefaultSpecificSkin();
// 3.设置皮肤
input.setSkin(skin);
// 4.显示皮肤
input.display();
}
}
里氏代换原则(LSP)
核心思想
子类对象应该能够替代父类对象出现在程序中的位置,并且程序的行为不会因此受到影响。(一个程序使用父类的对象,那么在不改变程序行为的前提下,子类对象可以替代父类对象)。
实现方式
1. 子类不应该改变父类方法的预期行为,尽量保留父类的方法逻辑。
2. 方法的输入输出应符合父类的契约,子类应遵循父类的行为约定。
3. 使用接口或抽象类设计,可以减少子类对父类的依赖。
4. 确保子类与父类的行为一致性,避免引入错误或不一致的行为。
示例
Quadrilateral
// 四边形接口
public interface Quadrilateral {
double getLength();
double getWidth();
}
Rectangle
// 矩形类
public class Rectangle implements Quadrilateral{
private double length;
private double width;
public void setLength(double length) {
this.length = length;
}
public void setWidth(double width) {
this.width = width;
}
@Override
public double getLength() {
return length;
}
@Override
public double getWidth() {
return width;
}
}
Square
public class Square implements Quadrilateral{
private double side;
public double getSide() {
return side;
}
public void setSide(double side) {
this.side = side;
}
@Override
public double getLength() {
return side;
}
@Override
public double getWidth() {
return side;
}
}
RectangleDemo
public class RectangleDemo {
public static void main(String[] args) {
// 1.创建长方形对象
Rectangle rectangle = new Rectangle();
// 2.设置长和宽
rectangle.setLength(20);
rectangle.setWidth(10);
// 3.调用扩宽方法
resize(rectangle);
// 4.打印长和宽
printLengthAndWidth(rectangle);
}
// 扩宽方法
public static void resize(Rectangle rectangle){
while (rectangle.getLength() >= rectangle.getWidth()){
rectangle.setWidth(rectangle.getWidth() + 1);
}
}
// 打印长和宽
public static void printLengthAndWidth(Quadrilateral quadrilateral){
System.out.println("长:" + quadrilateral.getLength() + " 宽:" + quadrilateral.getWidth());
}
}
依赖倒转原则(DIP)
核心思想:
- 高层模块(负责实现核心业务逻辑的模块)不应该依赖低层模块(负责实现具体的细节,比如数据存储、网络通信),二者应该依赖抽象。
- 抽象(接口或抽象类)不应该依赖细节(具体实现类),细节应该依赖抽象。
- 若没有遵循 DIP,高层模块直接依赖于低层模块,使得两者耦合度变高,且低层模块的变化可能会直接影响到高层模块,从而增加了系统的复杂性和维护难度。
- DIP 的关键在于,将高层模块与低层模块之间的依赖关系通过抽象接口或抽象类来分离,从而减少直接耦合,增加系统的灵活性。
示例
CPU
// 抽象CPU接口
public interface CPU {
// 运行cpu
void run();
}
HardDisk
// 硬盘接口
public interface HardDisk {
// 存储数据
void save(String data);
// 获取数据
String get();
}
Memory
// 内存条接口
public interface Memory {
void save();
}
IntelCpu
// 英特尔CPU
public class IntelCpu implements CPU{
@Override
public void run() {
System.out.println("英特尔CPU运行");
}
}
XiJieHardDisk
// 希捷硬盘
public class XiJieHardDisk implements HardDisk{
@Override
public void save(String data) {
System.out.println("希捷硬盘保存数据为:" + data);
}
@Override
public String get() {
System.out.println("希捷硬盘读取数据");
return "数据";
}
}
KingstonMemory
// 金士顿内存条
public class KingstonMemory implements Memory{
@Override
public void save() {
System.out.println("金士顿内存条");
}
}
Computer
public class Computer {
private HardDisk hardDisk;
private CPU cpu;
private Memory memory;
public HardDisk getHardDisk() {
return hardDisk;
}
public void setHardDisk(HardDisk hardDisk) {
this.hardDisk = hardDisk;
}
public CPU getCpu() {
return cpu;
}
public void setCpu(CPU cpu) {
this.cpu = cpu;
}
public Memory getMemory() {
return memory;
}
public void setMemory(Memory memory) {
this.memory = memory;
}
// 运行计算机
public void run() {
System.out.println("计算机开始运行...");
String data = hardDisk.get();
System.out.println("从硬盘中获取数据:" + data);
cpu.run();
memory.save();
}
}
ComputerDemo
public class ComputerDemo {
public static void main(String[] args) {
// 1.创建计算机对象
Computer computer = new Computer();
// 2.创建计算机组件对象
HardDisk hardDisk = new XiJieHardDisk();
CPU cpu = new IntelCpu();
Memory memory = new KingstonMemory();
// 3.设置计算机组件
computer.setHardDisk(hardDisk);
computer.setCpu(cpu);
computer.setMemory(memory);
// 4.运行计算机
computer.run();
}
}
接口隔离原则(ISP)
核心思想
- 不应该强迫一个类依赖于它不需要的接口。(客户端不应被迫实现它不使用的方法)
- 接口的设计应该小而精,每个接口只包含一组相关的功能,避免一个大型接口包含不相关的方法,从而导致实现类必须实现他们(即使不需要)
- 这个原则旨在通过拆分接口,使得类只依赖于它实际需要的接口,避免出现类与接口之间的强耦合。
示例
迪米特法则(LoD)
核心思想
- 一个对象应该对其他对象有最少的了解,即一个对象应该只和它直接相关的对象进行交互,而不应该依赖于它的“朋友”的“朋友”或其他间接对象。
- 又称最少知识原则,如果两个软件实体无需直接通信,那么就不应该发生直接的相互调用,可以通过第三方转发该调用,以此降低类之间的耦合度,提高模块的相对独立性。
- 直接的朋友对象:
-
- 当前对象本身
- 当前对象的成员对象
- 当前对象所创建的对象
- 当前对象的方法参数等
- 同当前对象存在关联、聚合和组合关系的对象。
示例
合成复用原则(CARP)
核心思想:
- 尽量使用对象的组合(聚合)来实现功能复用,而不是通过继承来扩展功能。
- 组合:一个对象作为另一个对象的成员,通过成员变量引用实现功能的复用。
- 聚合:一个对象与另一个对象之间通过外部关联实现松散耦合,体现的是“部分-整体”关系。
- 继承虽是一种复用代码的有效手段,但它是一种强耦合的复用方式,子类过多依赖父类实现,导致灵活性降低。
- 组合/聚合可以使模块间的耦合度降低,组件可以自由组合,从而提高系统的可扩展性和可维护性。