设计模式学习笔记

学习《大话设计模式》!

设计模式类型

序号 模式 & 描述 包括
1 创建型模式 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。 工厂模式(Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
2 结构型模式 这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。 适配器模式(Adapter Pattern)
桥接模式(Bridge Pattern)
过滤器模式(Filter、Criteria Pattern)
组合模式(Composite Pattern)
装饰器模式(Decorator Pattern)
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern)
3 行为型模式 这些设计模式特别关注对象之间的通信。 责任链模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
备忘录模式(Memento Pattern)
观察者模式(Observer Pattern)
状态模式(State Pattern)
空对象模式(Null Object Pattern)
策略模式(Strategy Pattern)模板模式(Template Pattern)访问者模式(Visitor Pattern)

设计模式

简单工厂模式

策略模式 Strategy

定义:它定义了算法家族, 分别封装起来, 让 它们之间可以互相替换, 此模式让算法的变化, 不会影响到使用算法的客户。

优点:

  • 策略模式是一种定义一系列算法的方法,从概念上来看,所有这些 算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类 与使用算法类之间的耦合
  • 策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取
    出这些算法中的公共功能
  • 策略模式的优点是简化了单元测试,因为每个算法都有自己的类,可以通过 自己的接口单独测试
  • 策略模式就是用来封装算法的, 但在实践中, 我们发现可以用它来封装几乎任何类型的规则, 只要在分析过程中听到需要在不同时间应用不同的业务规则, 就可以考虑使用策略模式处理 这种变化的可能性

装饰模式 Decorator

定义

动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。

image-20200715224658073

优点

把类中的装饰功能从类中搬移去除,可以简化原有的类。有效地把类的核心职责和装饰功能区分开额,而且可以去除相关类中重复的装饰逻辑。

代理模式 Proxy

定义

为其他对象提供一种代理以控制对这个对象的访问。

image-20200716113554927

应用场合

  1. 远程代理,也就是为一个对象在不同的地址空间提供局部代理。这样可以隐藏一个对象存在不同地址空间的事实。

  2. 虚拟代理,是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。

    例:浏览器中利用代理模式来优化下载。

  3. 安全代理,用来控制真实对象访问时的权限。

  4. 智能指引,是指当调用真实的对象时,代理处理另外一些事。

工厂方法模式 Factory Method

定义

定义一个用于创建对象的借口,让子类决定实例化哪一个类。工厂模式使一个类的实例化延迟到其子类。

简单工厂vs.工厂模式

  • 简单工厂模式的最大优点在于工厂类中包含了必要的判断逻辑,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。

    但是新增功能时,需要修改运算工厂中的分支。违背了开闭原则

  • 工厂模式克服了简单工厂违背开闭原则的缺点,又保持了封装对象创建过程的优点。

原型模式 Prototype

定义

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

image-20200716132938389

需要注意的点:浅复制 & 深复制

模板方法模式 Template Method

定义

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。image-20200716212157259

目的

当不可变的和可变的行为在方法的子类实现中混合在一起的时候,不变的行为就会在子类中重复出现。我们通过模板方法模式把这些行为搬移到单一的地方,这样就帮助子类摆脱重复的不变行为的纠缠。

外观模式 Facade

定义

为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

体现了依赖倒转原则和迪米特法则的思想。

image-20200716213717291

何时使用

这要分三个阶段来说

  1. 首先,在设计初期阶段,应该要有意识的将不同的两个层分离,比如经典的三层架构,就需要考虑在数据访问层和业务逻辑层、业务逻辑层和表示层的层与层之间建立外观Facade,这样可以为复杂的子系统提供一个简单的接口,使得耦合大大降低。
  2. 其次,在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,大多数的模式使用时也都会产生很多很小的类,这本是好事,但也给外部调用它们的用户程序带来了使用上的困难,增加外观Facade 可以提供一个简单的接口,减少它们之间的依赖
  3. 第三,在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,但因为它包含非常重要的功能,新的需求开发必须要依赖于它。此时用外观模式Facade也是非常合适的。你可以为新系统开发一个外观Facade类,来提供设计粗糙或高度复杂的遗留代码的比较清晰简单的接口,让新系统与Facade对象交互,Facade 与遗留代码交互所有复杂的工作。【R2P】”

image-20200716214144198

建造者模式 Builder

定义

建造者模式(Builder),又叫生成器模式,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

image-20200716215547317

详解

  • Builder: 是为创建一个Product对象的各个部件指定的抽象接口
  • ConcreteBuilder: 是具体的建造者,实现Builder接口,构造和装配各个部件
  • Product:指那些具体的产品角色
  • Director:指挥者,是构建一个使用Builder接口的对象

何时使用

主要用于创建一些复杂的对象,这些对象内部构建间的创造顺序通常是稳定的,但是对象内部的构造通常面临着复杂的变化

好处

使得建造代码与表示代码分离,由于建造者隐藏了该产品是如何组装的,所以若需要改变一个产品的内部便是,只需要再定义一个具体的建造者就可以了。

观察者模式 Observer

定义

观察者模式,又叫发布-订阅模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

image-20200716220917736

动机

将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合, 这样会给维护、扩展和重用都带来不便【DP】。而观察者模式的关键对象是主题Subject和观察者Observer,一个Subject可以有任意数目的依赖它的Observer,一旦 Subject的状态发生了改变,所有的Observer都可以得到通知。Subject 发出通知时并不需要知道谁是它的观察者,也就是说,具体观察者是谁,它根本不需要知道。而任何一个具体观察者不知道也不需要知道其他观察者的存在。

什么时候使用

  • 当一个对象的改变需要同时改变其他对象的时候,而且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。
  • 当一个抽象模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。

总的来讲,观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。”

抽象工厂模式 Abstract Factory

定义

提供一个创建一系列相关或相互依赖对象的接口,而无需制定它们具体的类。

image-20200716225551885

好处

  1. 最大的好处便是易于交换产品系列,由于具体工厂类,例如 IFactory factory= new AccessFactoryO,
    在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只
    需要改变具体工厂即可使用不同的产品配置。我们的设计不能去防止需求的更改,那么我们的理想便是
    让改动变得最小,现在如果你要更改数据库访问,我们只需要更改具体工厂就可以做到。

  2. 第二大好处是,它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。事实上,你刚才写的例子,客户端所认识的只有 IUser
    和 DEpartment,至于它是用 SQL Server来实现还是 Access来实现就不知道了。”

状态模式 State

定义

当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。

目的:主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一些列类当中,可以把复杂的判断逻辑简化。

image-20200716231428596

好处

将特定状态相关的行为局部化,并且将不同状态的行为分割开来。

何时使用

当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式

适配器模式 Adapter

定义

将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

系统的数据和行为都正确,但接口不符时,我们应该考虑使用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况。

image-20200717180522590

备忘录模式 Memento

定义

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样之后就可将该对象恢复到原先保存的状态。

image-20200717181113948

何时使用

  • Memento模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,Originator 可以根据保存的Memento信息还原到前一状态。”

  • 如果在某个系统中使用命令模式时,需要实现命令的撤销功能,那么命令模式可以使用备忘录模式来存储可撤销操作的状态【DP】。有时一些对象 的内部信息必须保存在对象以外的地方,但是必须要由对象自己读取,这时,使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来【DP】,从而可以恰当地保持封装的边界。

  • 最大的作用还是在当角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原【DP】

缺点

角色状态需要完整存储到备忘录对象中,如果状态数据很大很多,那么在资源消耗上,备忘录对象会非常耗内存。

组合模式 Composite

定义

将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

image-20200717181910229

何时使用

当发现需求中是体现部分与整体层次的结构时,以及希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑组合模式。

好处

  • 组合模式这样就定义了包含人力资源部和财务部这些基本对象和分公司、办事处等组合对家的类层次结构。基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去,客户代码中,任何用到基本对象的地方都可以使用组合对象了。
  • 用户是不用关心到底是处理一个叶节点还是处理一个组合组件,也就用不着为定义组合而写一些选择判断语句了。简单点说,就是组合模式让客户可以一致地使用组合结构和单个对象。

迭代器模式 Iterator

定义

提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

image-20200717182838517

用途

迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可以让外部代码透明地访问集合内部的数据。

单例模式 Singleton

定义

保证一个类只有一个实例,并提供一个访问它的全局访问点。

通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。

image-20200717201916746

好处

  1. 可以保证唯一的实例
  2. 单例模式因为封装它的唯一实例,这样它可以严格地控制客户怎样访问它以及何时访问它。简单地说就是对唯一实例的受控访问。

多线程问题

  • 懒汉式单例:在第一次被引用时,才会将自己实例化

    会面临多线程访问的安全性问题,需要做双重锁定来保证安全

  • 俄汉式单例:在自己被加载时就将自己实例化

    类一加载就实例化的对象,所以要提前占用系统资源

桥接模式 Bridge

定义

将抽象部分与它的实现部分分离,使它们都可以独立地变化

抽象与它的实现分离,这并不是说,让抽象类与其派生类分离,因为这没有任何意义。实现指的是抽象类和它的派生类用来实现自己的对象。

实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。

image-20200717203831627

命令模式 Command

定义

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录晶球日志,以及支持可撤销的操作

image-20200717204619276

好处

  1. 能较容易地设计一个命令队列
  2. 在需要的情况下,可以较容易地将命令记入日志
  3. 允许接收的一方决定是否要否决请求
  4. 可以容易地实现对请求的撤销和崇左
  5. 由于加进新的具体命令类不影响其他的类,因此增加新的具体命令了很容易
  • 关键点:命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开

职责链模式 Chain of Responsibility

定义

使对个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

image-20200718171454851

特点

  • 接受者和发送者哦读没有对方的明确信息,且链中的对象自己也并不知道链的结构。结果是职责链可以简化对象的相互连接,它们仅需保持一个指向其后继者的引用,而不需保持它所有的候选接受者的引用,大大降低了耦合度
  • 可以随时增加或修改处理一个请求的结构。增强了给对象指派职责的灵活性。
  • ⚠️:一个请求极有可能到了链的末端都得不到处理,或者因为没有正确配置而得不到处理,这就很糟糕

中介者模式

享元模式

解释器模式

访问者模式


设计原则

单一职责原则 SPR

定义

就一个类而言,应该仅有一个引起它变化的原因。

不分开会出现的问题

如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。

编程时我们需要在类的职责分离上多思考,做到单一职责, 这样你的代码才是真正的易维护、易扩展、易复用、灵活多样。

开放-封闭原则

定义

软件实体(类、模块、函数等等)应该是可以扩展,但是不可以修改。

两个特征

  1. 对于扩展是开放的 Open for extention
  2. 对于更改是封闭的 Closed for mordification

核心

开闭原则是OOP设计的核心所在。遵循这个原则可以带来OOP技术所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。开发人员应该仅对程序中呈现出频繁变化的那些部分做出抽象,然而,对于应用程序中的每个部分都刻意地进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要。

依赖倒转原则

定义

  1. 高层模块不应该依赖低层模块。两个都应该依赖抽象
  2. 抽象不应该依赖细节,细节应该依赖抽象

说白了,就是要针对接口编程,不要对实现编程

依赖倒转其实可以说是OOP设计的标志,用哪种语言来编写程序不重要,如果编写时考虑的都是如何针对抽象编程,而不是针对细节编程,即程序中所有的依赖关系都是终止于抽象类或者接口,那就是面向对象的设计,反之那就是过程化的设计。

里氏代换原则 LSP

定义

子类型必须能够替换掉他们的父类型。

只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。

迪米特法则 LoD

定义

迪米特法则(LoD),又叫最少知识原则, 如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。【J&DP】

  • 前提:在类的结构设计上,每一个类应当尽量降低成员的访问权限。
  • 根本思想:强调类之间的松耦合。类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及。

合成/聚合复用原则 CARP

定义

尽量使用合成/聚合,尽量不要使用类继承

  • 聚合表示一种弱的‘拥有’关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分
  • 合成表示一种强的‘拥有’关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样
image-20200717203318216

好处

优先使用对象的合成/聚合将有助于你保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控的庞然大物。

0%