[设计模式]七大原则

单一职责原则

  • 一个类只负责一项职责。换言之,一个类应该只有一个引起它变化的原因。

开闭原则

  • 软件实体类(类、模块、函数等)应该是可以扩展的,但是不可以修改。
  • 我们应该通过添加新的代码来扩展软件实体的行为,而不是修改原有的代码。

里氏替换原则

  • 子类应该可以完全替换父类,并且可以在不影响程序正确性的前提下增加额外的行为。
  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类可以增加自己特有的方法。
  • 子类的方法重载父类的方法时,方法的前置条件(方法的输入/入参)要比父类方法的输入参数更宽松。
  • 子类的方法实现父类的方法时(重写/重载货实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更加严格或者一致。

依赖倒置原则

  • 高层模块不应该依赖于底层模块,它们应该都依赖于抽象。
  • 抽象不应该依赖于细节,细节应该依赖于抽象。

接口隔离原则

  • 客户端不应该依赖它不需要的接口。
  • 一个类对另一个类的依赖应该建立在最小的接口上。- 我们在定义接口的时候,一定要注意控制接口的粒度,比如下面的例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    interface Device {
    String getCpu();
    String getType();
    String getMemory();
    }

    //电脑就是一种电子设备,那么我们就实现此接口
    class Computer implements Device {

    @Override
    public String getCpu() {
    return "i9-12900K";
    }

    @Override
    public String getType() {
    return "电脑";
    }

    @Override
    public String getMemory() {
    return "32G DDR5";
    }
    }

    //电风扇也算是一种电子设备
    class Fan implements Device {

    @Override
    public String getCpu() {
    return null; //就一个破风扇,还需要CPU?
    }

    @Override
    public String getType() {
    return "风扇";
    }

    @Override
    public String getMemory() {
    return null; //风扇也不需要内存吧
    }
    }
  • 虽然我们定义了一个Device接口,但是由于此接口的粒度不够细,虽然比较契合电脑这种设备,但是不适合风扇这种设备.
  • 因为风扇压根就不需要CPU和内存,所以风扇完全不需要这些方法。这时我们就必须要对其进行更细粒度的划分:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    interface SmartDevice {   //智能设备才有getCpu和getMemory
    String getCpu();
    String getType();
    String getMemory();
    }

    interface NormalDevice { //普通设备只有getType
    String getType();
    }

    //电脑就是一种电子设备,那么我们就继承此接口
    class Computer implements SmartDevice {

    @Override
    public String getCpu() {
    return "i9-12900K";
    }

    @Override
    public String getType() {
    return "电脑";
    }

    @Override
    public String getMemory() {
    return "32G DDR5";
    }
    }

    //电风扇也算是一种电子设备
    class Fan implements NormalDevice {
    @Override
    public String getType() {
    return "风扇";
    }
    }

合成复用原则

  • 核心就是委派。
  • 优先使用对象组合,而不是通过继承来达到复用的目的。
  • 在一个新对象里面使用一些已有的对象,使之成为新对象的一部分,新的对象通过向这些对象的委派达到复用已有功能的目的。
  • 比如A类总实现了连接数据库的功能,恰巧B类中也需要,因该是在用的时候给一个A,而不是通过继承。
  • 直接继承会造成耦合度太高:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class A {
    public void connectDatabase(){
    System.out.println("我是连接数据库操作!");
    }
    }

    class B extends A{ //直接通过继承的方式,得到A的数据库连接逻辑
    public void test(){
    System.out.println("我是B的方法,我也需要连接数据库!");
    connectDatabase(); //直接调用父类方法就行
    }
    }
  • 应该:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class A {
    public void connectDatabase(){
    System.out.println("我是连接数据库操作!");
    }
    }

    class B { //不进行继承,而是在用的时候给我一个A,当然也可以抽象成一个接口,更加灵活
    public void test(A a){
    System.out.println("我是B的方法,我也需要连接数据库!");
    a.connectDatabase(); //在通过传入的对象A去执行
    }
    }

迪米特法则

  • 又称最少知识原则,对程序内部数据交互的限制。
  • 一个对象应该对其他对象由最少的了解。
  • 例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Main {
    public static void main(String[] args) throws IOException {
    Socket socket = new Socket("localhost", 8080); //假设我们当前的程序需要进行网络通信
    Test test = new Test();
    test.test(socket); //现在需要执行test方法来做一些事情
    }

    static class Test {
    /**
    * 比如test方法需要得到我们当前Socket连接的本地地址
    */
    public void test(Socket socket){
    System.out.println("IP地址:"+socket.getLocalAddress());
    }
    }
    }
  • 直接提供一个Socket对象,然后再由test方法来取出IP地址。
  • 但是这样显然违背了迪米特法则,实际上这里的test方法只需要一个IP地址即可,我们完全可以直接传入一个字符串,而不是整个Socket对象,我们需要保证与其他类的交互尽可能的少。
  • 就像我们在餐厅吃完了饭,应该是我们自己扫码付款,而不是直接把手机交给老板来帮你操作付款。