设计模式概述
面向对象设计原则
- 单一职责原则(SRO)
每个类应该专注于做一件事情
可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;提高类的可读性,提高系统的可维护性;变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则 - 里氏替换原则(LSP)
超类存在的地方,子类是可以替换的
子类必须实现父类中声明的所有方法,即基类对象替换为子类对象,程序将不会产生任何错误和异常,反之则不成立;使用里氏替换原则时需要注意,子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现 - 依赖倒置原则(DIP)
实现尽量依赖抽象(接口)编程,而不是面对实现编程
具体依赖抽象,上层依赖下层。假设B是较A低的模块,但B需要使用到A的功能,这个时候,B不应当直接使用A中的具体类;而应当由B定义一抽象接口,并由A来实现这个抽象接口,B只使用这个抽象接口;这样就达到了依赖倒置的目的,B也解除了对A的依赖,反过来是A依赖于B定义的抽象接口。通过上层模块难以避免依赖下层模块,假如B也直接依赖A的实现,那么就可能造成循环依赖 - 接口隔离原则(ISP)
提供尽可能小的单独接口,而不要提供大的总接口
也就是要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的约定,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性 - 迪米特法则(LOD)
又叫最少知识原则,即一个软件实体应当尽可能少的与其他实体发生相互作用
对于被依赖的类来说,无论逻辑多复杂都要尽量封装在类的内部;每个对象都会与其他对象有耦合关系,我们称出现成员变量、方法参数、方法返回值中的类为直接的耦合依赖,而出现在局部变量中的类则不是直接耦合依赖,也就是说,不是直接耦合依赖的类最好不要作为局部变量的形式出现在类的内部;在一个类里能少用多少其他类就少用多少,尤其是局部变量的依赖类,能省略尽量省略。同时如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一方法的话,可以通过第三者转发这个调用 - 开闭原则(OCP)
对扩展开放、对修改封闭
意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。封装变化是实现开放封闭原则的重要手段,对于经常发生变化的状态,一般将其封装为一个抽象,拒绝滥用抽象,只将经常变化的部分进行抽象 - 组合聚合复用原则(CARP)
尽量使用合成/聚合达到复用,尽量少用继承。原则: 一个类中有另一个类的对象
在一个新的对象里面通过关联关系(包括组合关系和聚合关系)使用一些已有的对象,使之成为新对象的一部分,新对象通过委派调用已有对象的方法达到复用其已有功能的目的。也就是,要尽量使用类的合成复用,尽量不要使用继承。组合/聚合复用原则可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用
设计模式概念
模式要义
- 每个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复的劳动
- 对被用来在特定场景下解决一般设计问题的类和相互通信的对象的描述
- 一个设计模式命名、抽象和确定了一个通用设计结构的主要方面,这些设计结构能被用来构造可复用的面向对象设计。设计模式确定了所包含的类和实例,它们的角色、协作方式以及职责分配。每一个设计模式都集中于一个特定的面向对象设计问题或设计要点,描述了什么时候使用它,在另一些设计约束条件下是否还能使用,以及使用的效果和如何取舍
模式要素
- 模式名称(pattern name)
助记词,基于模式词汇表
在交流和讨论模式和编写文档时候用该词语 - 问题(problem)
描述应该再何时使用模式,解释了设计问题和问题存在的前因后果
可能描述了特定的设计问题,也可能描述了导致不灵活设计的类或对象结构。有时候会包括使用模式必须满足的一系列先决条件 - 解决方案(solution)
描述了设计的组成部分,它们之间的相互关系及各自的职责和协作方式
提供设计问题的抽象描述和怎样用一个具有一般意义的元素组合(类或对象组合)来解决这个问题 - 效果(consequences)
描述了模式应用的效果及使用模式赢权衡的问题
软件效果大多关注对时间和空间的衡量,它们也表述了语言和实现问题。因为复用是面向对象设计的要素之一,所以模式效果包括它对系统的灵活性、扩充性或可移植性的影响
设计模式分类
模式分类准则
- 目的准则
即模式是用来完成什么工作的。模式依据其目的可分为创建型、结构型、或行为型三种
创建型模式与对象的创建有关;
结构型模式处理类或对象的组合;
行为型模式对类或对象怎样交互和怎样分配职责进行描述。 - 范围准则
指定模式主要是用于类还是用于对象,分为类模式、对象模式
类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻便确定下来了;
对象模式处理对象间的关系,这些关系在运行时刻是可以变化的,更具动态性;
从某种意义上来说,几乎所有模式都使用继承机制,所以类模式只指那些集中于处理类间关系的模式,而大部分模式都属于对象模式的范畴
模式分类结果
模式分类分析
- 创建型类模式将对象的部分创建工作延迟到子类,而创建型对象模式则将它延迟到另一个对象中
- 结构型类模式使用继承机制来组合类,而结构型对象模式则描述了对象的组装方式
- 行为型类模式使用继承描述算法和控制流,而行为型对象模式则描述一组对象怎样协作
完成单个对象所无法完成的任务
模式其他分类
还有其他组织模式的方式。有些模式经常会被绑在一起使用,例如,Composite常和Iterator或Visitor起使用;
有些模式是可替代的,例如,Prototype常用来替代Abstract Factory;
有些模式尽管使用意图不同,但产生的设计结果是很相似的,例如,Composite和Decorator的结构图是相似的;
还有一种方式是根据模式所描述的它们怎样互相引用来组织设计模式。模式关系如下图示:
设计模式解决设计问题的方法
设计模式采用多种方法来解决面向对象设计遇到的问题:
寻找合适的对象
设计模式帮助确定并不明显的抽象和描述这些抽象的对象;设计的许多对象来源于现实世界的分析模型。但是,设计结果所得到的类通常在现实世
界中并不存在,决定对象的粒度
设计模式能帮助怎样用对象表示完整的子系统(Facade),描述了如何支持大量的最小粒度的对象(Flyweight),其他一些模式描述了将一个对象分解成许多小对象的特定方法
指定对象的接口
设计模式通过确定接口的主要组成成分及经接口发送的数据类型,来帮助你定义接口;
例如Memento模式规定了Memento对象必须定义两个接口:一个允许客户保持和复制Memento的限制接口,和一个只有原对象才能使用的用来储存和提取Memento中状态的特权接口
设计模式也指定了接口之间的关系,经常要求一些类具有相似的接口或它们对一些类的接口做了限制
例如,Decorator和Proxy模式要求Decorator和Proxy对象的接口与被修饰的对象和受委托的对象一致描述对象的实现
类继承和接口继承(C++纯虚函数);类继承根据一个对象的实现定义了另一个对象的实现。简而言之,它是代码和表示的共享机制。然而,接口继承描述了一个对象什么时候能被用来替代另一个对象
针对接口编程,而不是针对实现编程;不将变量声明为某个特定的具体类的实例对象,而是让它遵从抽象类所定义的接口运用复用机制
优先使用组合(黑箱)而不是继承(白箱)
委托(delegation)是一种组合方法,它使组合具有与继承同样的复用能力。在委托方式下,有两个对象参与处理一个请求,接受请求的对象将操作委托给它的代理者这类似于子类将请求交给它的父类处理
主要优点在于它便于运行时刻组合对象操作以及改变这些操作的组合方式,委托是对象组合的特例。它告诉你对象组合作为一个代码复用机制可以替代继承
参数化类型给我们提供除了类继承和对象组合外的第三种方法来组合面向对象系统中的
行为(C++模板)关联运行时刻和编译时刻的结构
一个面向对象程序运行时刻的结构通常与它的代码结构相差较大。代码结构在编译时刻
就被确定下来了,它由继承关系固定的类组成。而程序的运行时刻结构是由快速变化的通信
对象网络组成,两个结构是彼此独立的设计应该支持变化
设计模式可以确保系统能以特定方式变化,从而帮助你避免重新设计系统。每一个设计模式允许系统结构的某个方面的变化独立于其他方面,这样产生的系统对于某一种特殊变化将更健壮
导致重新设计原因可以有:- 通过显式地指定一个类来创建对象: Abstract Factory, Factory Method, Prototype
- 对特殊操作的依赖: Chain of Resposibility,Command
- 对硬件和软件平台的依赖: Abstract Factory, Bridge
- 对对象表示或实现的依赖: Abstract Factory, Bridge, Memento, Proxy
- 算法依赖: Builder, Iterator, Strategy, Template Method, Visitor
- 紧耦合: Abstract Factory, Command, Facade, Mediator, Observer, Chain of Resposibility
- 通过生成子类来扩充功能: Bridge, Chain of Resposibility, Composite, Decorator, Observer, Strategy
- 不能方便地对类进行修改: Adapter, Decorator, Visitor
设计模式的选择
- 模式怎样解决设计问题的
- 模式的意图和目的
- 模式之间的关联
- 相似目的模式的异同点
- 重新设计的原因
- 设计的可变点
目的 | 设计模式 | 可变点 |
---|---|---|
创建 | Abstract Factory | 产品对象家族 |
创建 | Builder | 如何创建一个组合对象 |
创建 | Factory Method | 被实例化的子类 |
创建 | Prototype | 被实例化的类 |
创建 | Singleton | 一个类的唯一实例 |
结构 | Adapter | 对象的接口 |
结构 | Bridge | 对象的实现 |
结构 | Composite | 一个对象的结构和组成 |
结构 | Decorator | 对象的职责,不生成子类 |
结构 | Facade | 一个子系统的接口 |
结构 | Flyweight | 对象的存储开销 |
结构 | Proxy | 如何访问一个对象; 该对象的位置 |
行为 | Chain of Responsibility | 满足一个请求的对象 |
行为 | Command | 何时、怎样满足一个请求 |
行为 | Interpreter | 一个语言的文法及解释 |
行为 | Iterator | 如何遍历、访问一个聚合的各个元素 |
行为 | Mediator | 对象的怎样交互、和谁交互 |
行为 | Memento | 一个对象哪些信息存放在对象之外即存储时机 |
行为 | Observer | 多个对象依赖另外一个对象,而这些对象如何保持一致 |
行为 | State | 对象的状态 |
行为 | Strategy | 算法 |
行为 | Template Method | 算法中的某些步骤 |
行为 | Visitor | 某些作用于一个或一组对象上的操作但不修改这些对象的类 |