《设计模式:可复用面向对象软件的基础》读书笔记

设计模式

一、前言、引言

  本书主要描述在面向对象软件设计过程中针对特定问题简洁而优雅的解决方案。根据模式的性质进行分类:创建型、结构型和行为型。

  粒度:指的是系统内存扩展增量的最小值,指数据仓库中数据单位保存数据的细化程度。对于程序设计来说,粒度就是程序中的一个最小单元划分,比如面向过程语言中的单元是过程和函数,是程序中的最小组件,而这些组件就可以实现最基本的代码重用。但当程序变大变复杂就会显得粒度太低,过多的函数非常难以管理。

  粗/细粒度:指相对规模的划分,可以理解为抽象程度。

  很难一次就得到兼具复用性和灵活性的设计,重复设计是不可避免的,但有经验的面向对象设计者能做出良好的设计。

  其中内行的一点是,不是解决任何问题都要从头做起,复用以前用过的解决方案,待有好的解决方案时,再一次次的使用新的方案。

  本书的目的就是将面向对象软件的设计经验作为设计模式记录下来

  本书的设计模式是对被用来在特定场景下解决一般设计问题的类和相互通信的对象的描述。


二、模式的四要素

  模式由四个基本要素组成:

  1. 名称:方便记忆和使用,加强思考。
  2. 问题:描述模式的使用场景,也可以包括使用模式必须包括的一系列先决条件。
  3. 解决方案:模式设计的组成成分,之间的相互关系及各自的职责和协作方式。
  4. 效果:模式应用的效果以及使用模式应权衡的问题。

三、MVC的设计模式

  MVC包括三类对象,模型Model是应用对象,视图View是在屏幕上的表示,控制器Controller定义用户界面对用户输入的响应方式。

  MVC分离了用户界面设计的这些对象,来提高灵活性和复用性。

  MVC通过创建一个”订购/通知”的协议来分离V和M。视图需要保证其显示正确的反映了模型的状态。一旦模型数据发生变化,需要通知相关视图,视图则进行刷新,这样一个模型可以提供不同的多个视图表现,也可以只更新视图而无需重写模型。

  MVC的这种分离设计也用于进行对象分离,进行解耦,即观察者模式(Observer)。

  MVC另一个特征是视图可以进行嵌套,也就是将对象划分为一组,并将这组对象看作一个对象。即组合模式(Composite)。此模式通过创建一个类的层次结构,用一些子类来定义原子对象,其他类来定义组合对象,组合对象即原子对象组合而成的对象。

  MVC也可以在不改变视图的情况下改变对用户输入的响应方式。这些都是解耦之后带来的灵活性提升。

  View-Controller关系是策略模式(Strategy)的一个例子。

  MVC还涉及到的设计模式:用来指定视图缺省控制器的工厂方法模式(Factory method),用来增加视图滚动的装饰器模式(Decorator),不过这些都是次要的应用了。


四、介绍设计模式的统一结构

  本书通过以下抽象结构来统一介绍设计模式。

  1. 模式名和分类
  2. 意图:回答这些问题(此模式是做什么的?其基本原理和意图是什么?它解决的是什么样的特定设计问题?)
  3. 别名
  4. 动机:用来说明一个设计问题以及如何使用模式中的类和对象来解决此问题的特定情境。
  5. 适用性:什么情况下适用?此模式又改进了哪些不良设计?我们又该如何识别这些情况?
  6. 结构:通过基于对象建模技术(OMT)的表示法对模式中的类进行图形描述。
  7. 参与者:模式中的类和对象以及它们的职责。
  8. 协作:参与者如何协作实现职责。
  9. 效果:模式怎样支持其目标?使用此模式的效果和所需做的权衡取舍?系统结构的哪些方面可以独立改变?
  10. 实现:实现此模式时需要知道的提示、技术要点以及应该避免的缺陷,还有是否存在某些特定于实现语言的问题。
  11. 代码示例
  12. 已知应用:实际系统中模式案例。
  13. 相关模式:和此模式紧密相关的模式有哪些?不同之处是什么?此模式应该和哪些模式一起使用?

五、所有设计模式

  本书包含的模式:

参与 创建型 结构型 行为型
Factory Method
工厂方法
Adapter(类)
适配器-类
Interpreter
解释器
Template Method
模板方法
对象 Abstract Factory
抽象工厂
Builder
生成器
Prototype
原型
Singleton
单例
Adapter(对象)
适配器-对象
Bridge
桥接
Composite
组合
Decorator
装饰
Facade
外观
Flyweight
享元
Proxy
代理
Chain of Responsibility
职责链
Command
命令
Iterator
迭代器
Mediator
中介者
Memento
备忘录
Observer
观察者
State
状态
Strategy
策略
Visitor
访问者

  根据目的准则划分:创建型、结构型和行为型。其中创建型模式与对象的创建有关;结构型模式处理类和对象的组合;行为型模式对类或对象怎样交互和怎样分配进行描述。

  根据范围准则划分:类和对象。类模式处理类和子类的关系,关系通过继承建立,是静态的,编译时便确定。对象模式则处理对象间的关系,这些关系在运行时刻可以变换。

  1. 创建型类模式,将对象的部分创建工作延迟到子类。
  2. 创建型对象模式,将对象的部分创建工作延迟到另一个对象。
  3. 结构型类模式,使用继承机制来组合类。
  4. 结构型对象模式,描述了对象的组装方式。
  5. 行为型类模式,使用继承描述算法和控制流。
  6. 行为型对象模式,描述一组对象怎样协作完成单个对象无法完成的任务。

  组合模式(Composite)常和迭代器模式(Iterator)或访问者模式(Visitor)一起使用。原型模式(Prototype)常用量替代抽象工厂模式(Abstract Factory)。组合模式(Composite)和装饰模式(Decorator)结构类似。

设计模式之间的关系


六、设计模式怎样解决设计问题

1 寻找合适的对象

  客户请求是使对象执行操作的唯一方法,操作又是对象改变内部数据的唯一方法。因为这些限制,对象的内部状态是被封装的。面向对象设计最困难的部分是将系统分解为对象集合。

  面向对象设计支持众多的设计方法。可以写出一个问题描述,挑出名词和动词,进而创建相应的类和操作;也可以关注于系统的协作和职责关系;也可以对现实世界建模,再将分析时发现的对象转化至设计中。

  设计中往往需要抽象出现实世界并不存在的对象的抽象方法,这对设计的灵活性是很重要的。

2 决定对象的粒度

  对象可以表示下自硬件上到整个应用的任何事物。

  外观模式(Facade)描述了怎样用对象表示完整的子系统;享元模式(Flyweight)描述了如何支持大量的最小粒度的对象。其他一些设计模式描述了将一个对象分解成许多小对象的特定方法。抽象工厂模式(Abstract Factory)和生成器模式(Builder)产生那些专门负责生成其他对象的对象。访问者模式(Visitor)和命令模式(Command)生成的对象专门负责实现对其他对象或对象组的请求。

3 指定对象接口

  对象声明的每一个操作指定操作名、作为参数的对象和返回值,这就是所谓的操作的型构(signature)。对象操作所定义的所有操作型构的集合被称为该对象的接口(interface)。

  类型(type)是用来标识特定接口的一个名字,当一个类型的接口包含另一个类型的接口时,可以说它是另一个类型的子类型(subtype),另一个类型则是它的超类型(supertype)

  当给对象发送请求时,所引起的具体操作既与请求本身有关又与接受对象有关,支持相同请求的不同对象可能对请求激发的操作有不同的实现。发送给对象的请求和它的相应操作在运行时刻的连接称之为动态绑定。动态绑定就是指发送的请求直到运行时刻才受具体的实现的约束,动态绑定允许你在运行时刻彼此替换有相同接口的对象。这种可替换性就称作多态

4 描述对象的实现

  针对接口编程,而不是针对实现编程。

  理解对象实例化,类继承,抽象类和具体类,混入类。

  类继承接口继承的比较,类继承根据一个对象的实现定义了另一个对象的实现,即代码和表示的共享机制。接口继承则描述了一个对象什么时候能被用来替代另一个对象。

  类继承实现了可复用,而继承拥有定义具有相同接口的对象族的能力,而多态则依赖于这种能力。只根据抽象类中定义的接口来操纵对象的好处:1.客户无须知道他们使用对象的特定类型,只须对象有客户所期望的接口。2.客户无须知道他们使用的对象是用什么类来实现的,他们只须知道定义接口的抽象类。

5 运用复用机制

继承和组合的比较

  优先使用对象组合,而不是类继承

  功能复用最常用的技术是类继承对象组合

  类继承这种通过生成子类的复用通常被称为白箱复用,白箱指可视性,即父类内部细节对子类可见。

  对象组合是类继承之外的另一种复用选择,新的复杂的功能可以通过组装或组合对象来获取。要求被组合的对象具有良好定义的接口,所以被称为黑箱复用,因为对象的内部细节是不可见的。

  继承和组合各有优缺点。

继承优点:

  1. 类继承在编译时静态定义,可以直接应用。
  2. 类继承比较方便的改变被复用的实现。

继承缺点:

  1. 类继承在编译时便确定,无法在运行时再改变继承自父类的实现。
  2. “继承破坏了封装性”,继承对子类揭露父类的内部细节。
  3. 父类任何变化必然导致子类发生变化。这种依赖关系限制了灵活性并最终会限制复用性。一个解决方案是只继承抽象类,因为抽象类提供较少的实现。

组合优点:

  1. 对象组合通过获得对其他对象的引用而在运行阶段动态定义。
  2. 组合要求对象遵守彼此的接口约定,进而促进接口制定的规范性。
  3. 这些接口并不会妨碍你将一个对象和其他对象一起使用。而且因为对象只能通过接口访问,所以并没有破坏封装性;
  4. 只要类型一致,运行阶段可以用一个对象替代另一个对象。

  继承实现复用往往比组合要容易许多,所以通常会结合起来使用。

委托

  委托是一种组合方法,使组合具有和继承一样的复用能力,是对象组合的一个特例,可以替代继承。

  在委托方式下,有两个对象参与处理一个请求,接受请求的对象将操作委托给他的代理者,类似于子类委托给父类。

  比如我们实现了一个矩形类来处理所有矩形操作,窗口类可以复用矩形类的操作,而不必成为矩形类的子类。

委托优点:便于在运行阶段组合对象操作,以及改变这些操作的组合方式。

委托缺点:

  1. 动态的、高度参数化的软件比静态软件更难于理解。
  2. 运行时效率相比会降低。

  所以委托适合于其使设计更加简单的情况,委托可以得到的效率与上下文有关,最适合符合特定程式的情景,即标准模式的情景。

  状态模式(State)、策略模式(Strategy)和访问者模式(Visitor)中都使用了委托。

  状态模式中,一个对象将请求委托给一个描述当前状态的State对象来处理。策略模式中,一个对象将一个特定的请求委托给一个描述请求执行策略的对象,一个对象只会有一个状态,但对于不同的请求可以有多个策略。而访问者模式中,对象结构的每个元素上的操作总是被委托到Visitor对象。

继承和参数化类型的比较

  参数化类型是另一种功能复用技术,是类属(generic)或模板(templates)。定义一个类型时并不去指定该类型所用到的其他所有类型,未经指定的类型在使用时通过参数的形式提供。

  参数化类型和继承都无法在运行阶段改变,但参数化类型允许改变类所用到的类型。

6 关联运行阶段和编译阶段的结构

  编译阶段的代码结构由继承关系固定的类组成。而运行阶段的结构则由快速变化的通信对象网络组成。

  对象间有聚合相识的关系。聚合表示一个对象拥有/包含另一个对象或其一部分。相识则表示一个对象仅知道另一个对象,可以说是关联或引用的关系,可以彼此请求但无需负责。聚合意味着对象有相同的生命周期,而相识则是比聚合耦合度更低的关系。

7 设计应支持变化

  1. 通过显式的指定一个类来创建对象。这样会使程序受特定实现的约束,而不是特定接口的约束,为了避免这种情况,应该间接的创建对象。(相关模式:Abstract Factory,Factory Method,Prototype)
  2. 对特殊操作的依赖。当为请求指定一个特殊化的操作时,就固定了请求的方式。为了避免写死代码,应该可以在编译或运行阶段可以方便的改变响应请求的方法。(相关模式:Chain of Responsibility,Command)
  3. 对硬件和软件平台的依赖。依赖于特定平台,很难进行移植。
    (相关模式:Abstract Factory,Bridge)
  4. 对对象表示或实现的依赖。知道对象将怎样表示、保存、定位或实现的客户在对象进行变化时也需要进行变化,最好隐藏这些信息以防连锁变化。
    (相关模式:Abstract Factory,Bridge,Memento,Proxy)
  5. 算法依赖。算法在开发和复用时常常被扩展、优化和替代,尽量避免依赖于某个特定算法。
    (相关模式:Builder,Iterator,Strategy,Template Method,Visitor)
  6. 紧耦合。紧耦合的类很难独立的被复用,因为他们之间相互依赖。松散耦合可以提高一个类本身被复用的可能性,使系统更易于学习、移植、修改和扩展。设计模式通过抽象耦合和分层技术来提高松散耦合。
    (相关模式:Abstract Factory,Command,Facade,Mediator,Observer,Chain of Responsibility)
  7. 通过生成子类来扩充功能。通常很难通过定义子类来定制对象,新类会有固定的实现开销,还需要对父类的深入了解。可以通过对象组合和委托技术,以新的方式来组合已有对象,而不是通过定义已存在类的子类的方式加入程序。
    (相关模式:Bridge,Chain of Responsibility,Composite,Decorator,Observer,Strategy)
  8. 不能方便的对类进行修改。有时不得不修改一个难以修改的类,如引用他人代码,但没有源码,或是修改一个类要同时修改关联的众多子类。
    (相关模式:Adapter,Decorator,Visitor)

七、怎样选择设计模式

  1. 思考设计模式是怎样解决设计问题
  2. 浏览模式的意图部分
  3. 研究模式怎样互相关联
  4. 研究目的相似的模式
  5. 检查重新设计的原因
  6. 考虑你的设计中哪些是可变的

设计模式所支持的设计的可变方面


八、怎样使用设计模式

  1. 大致浏览一遍模式
  2. 回头研究结构部分、参与者部分和协作部分
  3. 看代码示例部分,看看这个模式代码形式的具体例子
  4. 选择模式参与者的名字,使他们在应用上下文中有意义
  5. 定义类
  6. 定义模式中专用于应用的操作名称
  7. 实现执行模式中责任和协作的操作

参考博客和文章书籍等:

《设计模式:可复用面向对象软件的基础》

因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容