设计模式之七大原则

445 阅读6分钟

设计模式之七大原则

我们为什么要学习设计模式?

1、个人观点

在最近的编码过程中,尤其是在实习的几个月里,我深刻地意识到了设计模式的重要性。写代码,即使是CURD的代码,也需要考虑到整个系统的维护性[可读性、规范性]、扩展性、重用性问题。

当然,一段质量高的代码,不仅仅需要设计模式,也需要 clean code, effective code 等书籍的指导。

2、客观观点

  • 面试必要
  • 能显著提升代码能力
  • 在框架和模块设计中呗大量使用

七大原则

  1. 单一职责
  2. 接口隔离
  3. 依赖倒置
  4. 里式替换
  5. 开闭原则
  6. 迪米特原则
  7. 合成复用
设计原则一句话归纳目的
开闭原则 OCP对扩展开放,对修改关闭降低维护带来的新风险
依赖倒置原则 DIP高层不应该依赖低层,要面向接口编程更利于代码结构的升级扩展
单一职责原则 SRP一个类只干一件事,实现类要单一便于理解,提高代码的可读性
接口隔离原则 ISP一个接口只干一件事,接口要精简单一功能解耦,高聚合、低耦合
迪米特法则 LoD不该知道的不要知道,一个类应该保持对其它对象最少的了解,降低耦合度只和朋友交流,不和陌生人说话,减少代码臃肿
里氏替换原则 LSP不要破坏继承体系,子类重写方法功能发生改变,不应该影响父类方法的含义防止继承泛滥
合成复用原则 CRP尽量使用组合或者聚合关系实现代码复用,少使用继承降低代码耦合

1、单一职责原则

对于类的维度来说,一个类应该只负责一项职责。如果某个类负责了两个不同的职责,则它应该被拆分为两个类。

举例

设计一个学生工作管理程序,学生工作分为生活辅导和学业辅导。所以,应该让辅导员类负责生活辅导,学生导师负责学业辅导。不应该将两份工作交给一个老师来负责。

注意点

  • 只有当类中的方法数量足够少,可以在方法级别保持单一原则

2、接口隔离原则

客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。

举例:

该例子中,A类依赖了它不需要的接口,造成了不必要的耦合

image-20220101225633859

解决方法

应该将 Interface1 拆分成几个独立的接口,类A 和 类C 分别与他们需要的接口建立依赖关系

image-20220101225858013

3、依赖倒置原则

1、高层模块不应该依赖低层模块,二者都应该依赖其抽象

2、抽象不应该依赖细节,细节应该依赖抽象

3、依赖倒置的中心思想是 面向接口编程,而不是面向具体细节编程

因为,相对于细节的多边形,抽象的东西要稳定的多

举例

就比如一个支付系统,今天客户要求要能支持微信支付,明天可能就要新增支付宝支付,支付操作的具体细节是多变的。我们通过抽象,把支付操作抽象为一个接口或者抽象方法来保持其稳定,客户端就不需要进行修改,只需要传递想要进行支付的方式的实现类即可。以抽象为基础搭建的架构比细节为基础的框架要稳定的多。

Java 中的抽象与细节:

  • 抽象,使用接口和抽象类
  • 细节,即实现类

依赖关系的三种传递方式

1、接口传递

将接口作为参数,进行传递

interface IOpenAndClose {
    public void open(ITV tv); //抽象方法,接收接口
}

interface ITV { //ITV 接口
    public void play();
}

2、构造方法传递

interface IOpenAndClose {
    public void open( ); //抽象方法,接收接口
}

interface ITV { //ITV 接口
    public void play();
}


class OpenAndClose implements IOpenAndClose {
    public ITV tv; //成员

    public OpenAndClose(ITV tv) { //构造器
        this.tv = tv;
    }

    public void open() {
        this.tv.play();
    }
}

3、setter 方法传递

interface IOpenAndClose {
    public void open( ); //抽象方法,接收接口

    public void setTv(ITV tv);
}

class OpenAndClose implements IOpenAndClose {
    private ITV tv;

    public void setTv(ITV tv) {
        this.tv = tv;
    }

    public void open() {
        this.tv.play();
    }
}

注意点

  1. 变量的声明尽可能是抽象类或接口,这样有益于程序扩展和优化
  2. 低层模块尽量要有抽象类或接口,这样程序稳定性更好

4、里式替换原则

继承必须确保超类所拥有的性质在子类中仍然成立。

首先,继承包含了一层意思,即凡是父类中已经实现好的方法,虽然父类并不要求所有的子类都必须遵循,但是如果子类对这些已经实现好的方法任意修改,则会对整个继承体系造成破坏。

继承虽然带来了便利,亦带来了侵入性,增加了程序对象之间的耦合性,降低了可移植性。

所以根据里式替换原则:

  • 在使用继承时,子类中尽量不要重写父类方法
  • 继承实际上使得两个类耦合性增强了,所以尽量使用 聚合、组合、依赖 来代替继承,解决问题

5、开闭原则

一个软件实体,如类、模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方);

当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有代码来实现

举例

image-20220102213600483

该类图展示了一个图形设计,根据type类型来画出不同的图形。但是这个设计违反了开闭原则,如果要新增加一个三角形,不仅要在GrapicEditor类中添加 drawTriangle() 还要 增加 if else 的判断。

解决方法

把创建 Shape 类做成抽象类,并提供一个 抽象的 draw 方法,让子类去实现即可,这样我们有新的图形种类时,只需要让新的图形类继承 Shape,并实现 draw 方法即可使用方的代码就不需要修 -> 满足了开闭原则

6、迪米特法则

迪米特法则(Demeter Principle)又叫 最少知道原则,即一个类 对自己依赖的类知道的越少越好。

简单来说,对于被依赖的类,不管这个类多么复杂,都尽量地将逻辑封装在自己内部。对外只提供 public 方法,不对外泄露任何信息

直接朋友概念

每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。

耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现 成员变量、方法参数、方法返回值 中的类为直接的朋友,而出现在**局部变量中的类不是直接的朋友。**也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。

7、合成复用原则

简单来说,能用合成/聚合,就不要用继承

通过构造方法、成员变量直接实例化、set方法赋值等方式来解耦,代替继承

image-20220102214527148