设计模式课程笔记-1

课程目标

理解松耦合设计思想
掌握面向对象设计原则
掌握重构技法改善设计
掌握GOF核心设计模式

具体内容

  1. 定义虚函数,有一个空的析构函数:子类以后通过多态释放的时候,子类的析构函数才能正确调用到。
  2. 所有的继承推荐使用public

面向对象设计原则

  1. 依赖倒置原则(DIP Dependence inversion principle):面向接口编程
    高层模块(稳定)不应该依赖于底层模块(变化),二者都应该依赖于抽象(稳定)
    抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)
    ——>稳定一定不能依赖变化
  2. 开放封闭原则(OCP Open closed principle)
    对扩展开放,对更改封闭
    类模块应该是可以扩展的,但是不可以修改
  3. 单一职责原则(SRP Single Responsibility Principle)
    定义:一个类只负责一项职责,尽量做到类只有一个行为原因引起变化
    一个类应该仅有一个引起它的变化的原因
    变化的方向隐含着类的责任
  4. 里氏替换原则(LSP Liskov subsitution principle )
    定义: 子类可以扩展父类的功能,但是不能改变原有父类的功能
    子类必须能够替换他们的基类(IS-A)
    继承表达类型抽象
  5. 接口隔离原则(ISP Interface segregation principle):建立单一接口
    不应该强迫客户程序依赖他们不用的方法
    接口应该小而完备
  6. 优先使用对象组合,而不是类继承
    类继承通常为“白箱复用”,对象组合通常为“黑箱复用”
    继承在某种程度上破坏了封装性,子类父类耦合度高
    而对象组合则只要求被组合对象具有良好定义的接口,耦合度低
  7. 封装变化点
    使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不化对另一侧产生不良影响,从而实现层次间的松耦合
  8. 针对接口编程,而不是针对实现编程
    不将变量类型声明为某个特定的具体类,而是声明为某个接口
    客户程序无需获知对象的具体类型,只需要知道对象所具有的接口
    减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案

GOF-23模式分类

  1. 从目的看
    创建型(Creational)模式
    结构型(Structural)模式
    行为型(Behavioral)模式
  2. 从范围看
    类模式处理类与子类的静态关系
  3. 从封装变化角度对模式分类
    组建协作:Template Method; Strategy; Observer/ Event
    单一职责:Decorator; Bridge
    对象创建:Factory Method; Abstract Factory; Prototype; Builder
    对象性能:Singleton; Flyweight
    接口隔离:Facade; Proxy; Mediator; Adapter
    状态变化:Memento; State
    数据结构:Composite; Iterator; Chain of Resposibility
    行为变化:Command; Visitor
    领域问题:Interpretor

重构获得模式(Refactoring to Patterns)

  1. 重构关键技法
    静态->动态
    早绑定->晚绑定
    继承->组合
    编译时依赖->运行时依赖
    紧耦合->松耦合
  2. 组件协作模式
    典型模式: Template Method; Strategy; Observer/Event

组件协作模式

Template Method(模板方法)

动机:如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求
定义:定义一个操作中的算法的骨架(稳定),而将一些步骤延迟(变化)到子类中(也就是定义向下调用的虚函数,让子类具体决定操作方式)。Template Method使得子类可以不改变(复用)一个算法的结构,即可重定义(override重写)该算法的某些特定步骤。
工程借鉴意义:对于每一个代码,都尽量去找一下稳定的部分和变化的部分,把握变化与稳定之间关系
着重注意:设计模式最大作用是在稳定和变化中寻找隔离点(记住必须有稳定和变化两部分,设计模式才有意义),分离它们,从而来管理变化。
结构
在这里插入图片描述

Strategy(策略模式)

编程思维:考虑问题时要有一个时间轴的概念,即以后是否会存在新的需求
动机:某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使得对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。–>如何在运行时根据需要透明的使用更改对象的算法,将算法与对象本身解耦合,从而避免上述问题?
c++程序的一个注意点:任何一个基类,一定要对它的虚函数添加一个析构函数 ,否则多态的delate会出问题
模式定义:定义一系列算法,把它们一个个封装起来,并且使他们可以互相替换(变化),该模式使得算法可以独立使用它的客户程序(稳定)而变化(扩展,子类化)
编程注意:要使用指针来实现多态变量,而不是用对象
面向对象的复用:这里的复用指的是二进制层面的,而不是使用源代码的片面,粘贴一段代码不叫复用
结构
在这里插入图片描述
要点总结

  1. Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便的根据需要在各个算法之间进行切换
  2. Stragedy模式提供了用条件判断语句之外的另一种选择,消除条件判断语句,就是在解耦,含有许多条件判断语句的代码通常都需要Stragedy模式(只有if else绝对不变时,采用if else,比如性别,但是业务场景,很少存在if else 不变的情况 )
  3. 如果Stragedy对象没有实例变量,那么各个上下文可以共享同一个Stragedy对象,从而节省对象开销

Observer(观察者模式)

动机:在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。–>使用面向对象的技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系,从而实现软件体系结构的松耦合。
模式定义:定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
结构
在这里插入图片描述
要点总结:(1)使用面向对象的抽象,Obeserver模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达至松耦合。
(2)目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
(3)观察者自己决定是否需要订阅通知,目标对象对此一无所知
(4)Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。

“单一职责”模式

在软件组件设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任。
典型模式:Decorator; Bridge

Decorator(装饰模式)

设计模式的真谛:编译时让它复用,它的需求的变化都丢到运行过程中,用多态的方式来支持变化
动机:(1)在某些情况下,我们可能会“过度的使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性,并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
(2)如何使“对象功能的扩展”能够根据需要来动态的实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响降为最低?
模式定义:动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码&减少子类个数)。
结构(Structure)
在这里插入图片描述
要点总结
(1)通过采用组合而非继承的手法,Decorator模式实现了在运行时动态扩展功能的能力,而且可以根据需要扩展多个功能,避免了使用继承带来的“灵活性差”和“多子类衍生问题”
(2)Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类(所以一般在看到一个函数既有继承,又有组合,可以考虑这是一个装饰模式)
(3)Decorator模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为“装饰”的含义。

桥模式

动机
(1)由于某些类型的固有实现逻辑,使得它们具有两个变化的维度,乃至多个维度的变化
(2)如何应对这种“多维度的变化”?如何利用面向对象技术来使得类型可以轻松的沿着两个乃至多个方向的变化,而不引入额外的复杂度?
模式定义:将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立地变化。
结构
在这里插入图片描述
要点总结
(1)Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化,所谓抽象和实现沿着各自维度的变化,即“子类化”它们。
(2)Bridge模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方法
(3)Bridge模式的应用一般在“两个非常强的变化维度”,有时一个类也有多于两个的变化维度,这时可以使用Bridge的扩展模式。

“对象创建”模式

  1. 通过“对象创建”模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类,造成一种细节依赖状况),从而支持对象创建的稳定,它是接口抽象之后的第一步工作
  2. 典型模式
    Factory Method
    Abstract Factory
    Prototype
    Builder

Factory Method(工厂方法)

面向接口编程:最基本表现是变量要声明成抽象基类。抽象基类或者接口是不能引用的。
动机
(1)在软件系统中,经常面临着创建对象的工作,由于需求的变化,需要创建的对象的具体类型经常变化
(2)如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?
处理事情的艺术:并不是消灭变化,而是将变化赶到某个局部的地方
模式定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟(目的:解耦,解new后面具体类的耦合;手段:虚函数)到子类
结构
在这里插入图片描述
要点总结
(1)Factory Method模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱
(2)Factory Method模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。
(3)Factory Method模式解决“单个对象”的需求变化,缺点在于要求创建方法/参数相同

Abstract Factory(抽象工厂)

动机
(1)在软件系统中,经常面临着==“一系列相互依赖的对象”==的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。
(2)如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合?
模式定义:提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。
结构(红色表示稳定的,蓝色表示第一种变化,绿色表示第二种变化)
在这里插入图片描述
要点总结
(1)如果没有应对“多系列构建”的需求变化,则没有必要使用Abstract Factory模式,这时候使用简单的工厂完全可以
(2)“系列对象”指的是在某一特定系列下的对象之间有相互依赖、或作用关系。不同系列的对象之间不能相互依赖。
(3)Abstract Factory模式主要在于应对“新系列”的需求变动,其缺点在于难以应付“新对象”的需求变动。

Prototype(原型模式)–用的较少

动机
(1)在软件系统中,经常面临着==“某些结构复杂的对象”==的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有较稳定一致的接口
(2)如何应对这种变化?如何向“客户程序(使用这些对象的程序”隔离出“这些易变的对象”,从而使得“依赖这些易变对象的客户程序”不随着需求改变而改变。
模式定义:使用原型实例指定创建对象的种类,然后通过拷贝(深度克隆)这些原型来创建新的对象
结构
在这里插入图片描述
要点总结
(1)Prototype模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,他同样要求这些“易变类”拥有“稳定的接口”
(2)Prototype模式对于“如何创建易变类的实体对象”采用“原型克隆”的方法来做,它使得我们可以非常灵活的动态创建“拥有某些稳定接口”的新对象——所需工作仅仅是注册一个新类的对象(即原型),然后在任何需要的地方Clone(使用c++的拷贝构造函数就可以)
(3)Prototype模式中的Clone方法可以利用某些框架中的序列化来实现深拷贝

Builder(构建器)

动机
(1)软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常有各个部分的子对象用一定的算法构成,由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定
(2)如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求的改变而改变?
c++ Tips:c++构造函数中调用虚函数是静态绑定,它是无法调用子类虚函数的构造类型的
模式定义:将一个复杂对象的构建与其表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)
结构
在这里插入图片描述
要点总结
(1)Builder模式主要用于“分步骤构建一个复杂的对象”。在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化
(2)变化点在哪里,封装哪里——Builder模式主要在于应对“复杂对象的各个部分”的频繁需求变动。其缺点在于难以应对“分步构建算法”的需求变动
(3)在Builder模式中,要注意不同语言中构造器内调用虚函数的差别(c++ vs. c#)

“对象性能”模式

  1. 面向对象很好地解决了“抽象”的问题,但是必不可免地要付出一定的代价。对于通常情况来讲,面向对象的成本大都可以忽略不计。但是某些情况,面向对象所带来的成本必须谨慎处理
  2. 典型模式
    Singleton
    Flyweight

Singleton(单件模式)

动机
(1)软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率
(2)如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?
(3)这应该是设计者的责任,而不是使用者的责任
模式定义:保证一个类仅有一个实例,并提供一个该实例的全局访问点
结构
在这里插入图片描述
要点总结
(1)Singleton模式中的实例构造器可以设置为protected以允许子类派生
(2)Singleton模式一般不要支持拷贝构造函数和Clone接口,因为这有可能导致多个对象实例,与Singleton模式的初衷违背
(3)如何实现多线程环境下安全的Singleton?注意对双检查锁的正确实现

Flyweight(享元模式)——解决性能问题

动机
(1)在软件系统采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价——主要指内存需求方面的代价
(2)如何在避免大量细粒度对象问题的同时,让外部客户程序仍然能够透明地使用面对对象的方式来进行操作
模式定义:运用共享技术有效地支持大量细粒度的对象
操作细节:在一个对象池中查找,找到了返回;否则,若没有找到,就创建新的
要点总结
(1)面向对象很好地解决了抽象性的问题,但是作为一个运行在机器中的程序实体,我们需要考虑对象的代价问题。Flyweight主要解决面向对象的代价问题,一般不触及面向对象的抽象性问题
(2)Flyweight采用对象共享的做法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力。再具体实现方面,要注意对象状态(一般是只读方式)的处理
(3)对象的数量太大从而导致对象的内存开销加大——什么样的数量才能算大?这需要我们仔细根据具体应用情况进行评估,而不能凭空臆断。

“接口隔离”模式

  1. 在组建构建过程中,某些接口之间直接的依赖常常会带来很多问题、甚至根本无法实现。采用添加一层间接(稳定)接口,来隔离本来互相紧密关联的接口是一种常见的解决方案
  2. 典型模式
    Facade
    Proxy
    Adapter
    Mediator

Facade(门面模式)

系统耦合的复杂度
在这里插入图片描述
动机
(1)上述A方案的问题在于组件的客户和组件中各种复杂的子系统有了过多的耦合,随着外部客户程序和各子系统的演化,这种过多的耦合面临很多变化的挑战
(2)如何简化外部客户程序和系统间的交互接口?如何将外部客户程序的演化和内部子系统的变化之间的依赖相互解耦?
模式定义:为子系统中的一组接口提供一个一致(稳定)的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用(复用)。
结构
在这里插入图片描述
要点总结
(1)从客户程序的角度看,Facade模式简化了整个组件系统的接口,对于组件内部与外部客户程序来说,达到了一种 “解耦”的效果——内部子系统的任何变化不会影响到Facade接口的变化
(2)Facade设计模式更注重从架构层次去看整个系统,而不是单个类的层次。Facade很多时候更是一种架构设计模式
(3)Facade设计模式并非一个集装箱,可以任意的放进多个对象。Facade模式中组价的内部应该是“相互耦合关系比较大的一系列组件”(松耦合,高内聚),而不是一个简单的功能集合

Proxy(代理模式)

动机
(1)在面向对象的系统中,有些对象由于某种原因(比如对象创建的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者、或者系统结构带来很多麻烦
(2)如何在不失去透明操作对象的同时来管理/控制这些对象特有的复杂性?增加一层间接层是软件开发中常见的解决方式
模式定义:为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问
结构
在这里插入图片描述
要点总结
(1)“增加一层间接层”是软件系统中对许多复杂问题的一种常见解决方法。在面向对象的系统中,直接使用某些对象会带来很多问题,作为间接层的proxy对象便是解决这一问题的常用手段
(2)具体proxy设计模式的实现方法、实现粒度都相差很大,有些可能对单个对象做细粒度的控制,如copy-on-write技术,有些可能对组件模块提供抽象代理层,在构架层次对对象做proxy
(3)Proxy并不一定要求保持接口完整的一致性,只要能够实现间接控制,有时候损及一些透明性是可以接受的

Adapter(适配器)

动机
(1)在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新的环境要求的接口是这些现存对象所不能满足的
(2)如何应对这种“迁移的变化”?如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?
模式定义:将一个类的接口转换成用户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
结构
在这里插入图片描述
要点总结
(1)Adapter模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用
(2)GoF 23定义了两种Adapter模式的实现结构:对象适配器和类适配器。但类适配器采用“多继承”的实现方式,一般不推荐使用;对象适配器采用“对象组合”的方式,更符合松耦合精神
(3)Adapter模式可以实现的非常灵活,不必拘泥于GoF 23中定义的两种结构。例如,完全可以将Adapter模式中的“现存对象”作为新的接口方法参数,来达到是配的目的。

Mediator(中介模式)

与Facade区别:Facade用于处理系统外和系统内之间的依赖;Mediator用来处理系统内各个模块之间的依赖
动机
(1)在软件构建过程中,经常会出现多个对象互相关联交互的情况,对象之间常常会维持一种复杂的引用关系,如果遇到一些需求的更改,这种直接的引用关系将面临不断的变化
(2)在这种情况下,我们可以使用一个“中介对象”来管理对象间的关联关系,避免相互交互的对象之间的紧耦合引用关系,从而更好地抵御变化
模式定义:用一个中介对象来封装(封装变化)一系列的对象交互。中介者使各对象不需要显式的相互引用(编译时依赖->运行时依赖),从而使其耦合松散(管理变化),而且可以独立的改变他们之间的交互。
结构
在这里插入图片描述
要点总结
(1)将多个对象间复杂的关联关系解耦,Mediator模式将多个对象间的控制逻辑进行集中管理,变“多个对象互相关联”为“多个对象和一个中介者关联”,简化了系统的维护,抵御了可能的变化
(2)随着控制逻辑的复杂变化,Mediator具体对象的实现可能相当复杂。这时候可以对Mediator对象进行分解处理
(3)Facade模式是解耦系统间(单向)的对象关联关系;Mediator模式是解耦系统内各个对象之间(双向)的关联关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值