【项目实战】23种设计模式入门介绍

一、优点

面向界面编程
降低耦合性
增加灵活性

二、23种设计模式是什么?

2.1 创建型模式

AbstractFactory ( 抽象工厂 )
FactoryMethod ( 工厂方法 )
Singleton ( 单态模式 )
Builder ( 建造者模式 )
Prototype ( 原型模式 )

2.2 结构型模式

适配器模式
桥接模式
组合模式
装饰模式
外观模式
享元模式
代理模式

2.3 行为型模式

责任链模式
命令模式
解释器模式
迭代器模式
中介者模式
备忘录模式
观察者模式
状态模式
策略模式
模板方法
访问者模式

三、设计模式之间的关系图解

在这里插入图片描述

在这里插入图片描述

四、设计模式六大原则

4.1 单一职责原则

4.1.1定义

  • 不要存在多于一个导致类变更的原因。
  • 一个类只负责一项职责。
  • There should never be more than one reason for a class to change.
  • 一个类,应当只有一个引起它变化的原因。
  • SRP — Single Responsibility Principle
  • 高内聚、低耦合

4.1.2 问题由来

类T负责两个不同的职责:职责P1,职责P2。
当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。

4.1.3 解决方案:

遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。
这样,当修改类T1时,不会使职责P2发生故障风险;
同理,当修改T2时,也不会使职责P1发生故障风险。

4.1.4 其他

说到单一职责原则,很多人都会不屑一顾。因为它太简单了。稍有经验的程序员即使从来没有读过设计模式、从来没有听说过单一职责原则,在设计软件时也会自觉的遵守这一重要原则,因为这是常识。在软件编程中,谁也不希望因为修改了一个功能导致其他的功能发生故障。而避免出现这一问题的方法,便是遵循单一职责原则。虽然单一职责原则如此简单,并且被认为是常识,但是即便是经验丰富的程序员写出的程序,也会有违背这一原则的代码存在。

4.1.5 为什么会出现这种现象呢?

因为有职责扩散。所谓职责扩散,就是因为某种原因,职责P被分化为粒度更细的职责P1和P2。
比如:类T只负责一个职责P,这样设计是符合单一职责原则的。后来由于某种原因,也许是需求变更了,也许是程序的设计者境界提高了,需要将职责P细分为粒度更细的职责P1,P2,这时如果要使程序遵循单一职责原则,需要将类T也分解为两个类T1和T2,分别负责P1、P2两个职责。但是在程序已经写好的情况下,这样做简直太费时间了。所以,简单的修改类T,用它来负责两个职责是一个比较不错的选择,虽然这样做有悖于单一职责原则。(这样做的风险在于职责扩散的不确定性,因为我们不会想到这个职责P,在未来可能会扩散为P1,P2,P3,P4……Pn。所以记住,在职责扩散到我们无法控制的程度之前,立刻对代码进行重构。)

只有逻辑足够简单,才可以在代码级别上违反单一职责原则;只有类中方法数量足够少,才可以在方法级别上违反单一职责原则;例如本文所举的这个例子,它太简单了,它只有一个方法,所以,无论是在代码级别上违反单一职责原则,还是在方法级别上违反,都不会造成太大的影响。实际应用中的类都要复杂的多,一旦发生职责扩散而需要修改类时,除非这个类本身非常简单,否则还是遵循单一职责原则的好。

4.1.6 遵循单一职责原的优点有:

  • 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
  • 提高类的可读性,提高系统的可维护性;
  • 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。

4.1.7 注意

单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。

4.1.8 应用场景

JavaEE中的分层框架模式实际上体现了单一职责原则
将整个系统按照职责的内聚性分为不同的层,层内的模块与类具有宏观的内聚性,所关注的的事情是一致的

  • 业务逻辑层主要关注的是系统的业务逻辑与业务流
  • 数据访问层只负责数据的持久化与访问

4.2 里氏替换原则

4.2.1 历史

原则最早是在1988年,由麻省理工学院的一位姓里的女士(Barbara Liskov)提出来的。

  • 里氏替代原则LSP
  • LSP–Liskov Substitution Principle
  • functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
  • 所有引用基类的地方必须能透明地使用其子类的对象。子类可以扩展父类的功能,但不能改变父类原有的功能
  • 只要父类能出现的地方子类就可以出现,而且调用子类还不产生任何的错误或异常,调用者可能根本就不需要知道是父类还是子类。但是反过来就不成了,有子类出现的地方,父类未必就能适应。

4.2.2 定义

  • 如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
  • 所有引用基类的地方,必须能透明地使用其子类的对象。

4.2.3 问题由来

有一功能P1,由类A完成
现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。
新功能P2由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。

4.2.4 解决方案

当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。

4.2.5 继承的一重含义:

父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。

4.2.6 继承带来的弊端:

  • 使用继承会给程序带来侵入性
  • 程序的可移植性降低
  • 增加了对象间的耦合性3
  • 如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能会产生故障。

4.2.7 四重含义

  1. 子类必须完全实现父类的方法

  2. 子类可以有自己的个性

  3. 覆盖和实现父类的方法时,输入参数可以被放大,但不能被缩小

  4. 覆盖和实现父类的方法时,输出结果可以被缩小,但不能被放大

    • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
    • 子类中可以增加自己特有的方法。
    • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
    • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

猜你喜欢

转载自blog.csdn.net/wstever/article/details/129979051
今日推荐