设计模式之美
目录
设计模式分类
模式的目的 (Purpose),即模式是用来完成什么工作的:
- 创建型 (Creational) 模式与对象的创建有关;
- 结构型 (Structural) 模式处理类或对象的组合;
- 行为型 (Behavioral) 模式对类或对象怎样交互和怎样分配职责进行描述;
模式的范围 (Scope),即模式主要用于类还是用于对象。
- 类模式 (Class),处理类和子类之间的关系,这些关系通过继承建立,在编译时即确定,具有静态性;
- 对象模式 (Object),处理对象间的关系,这些关系在运行时是可变化的,具有动态性;
创建型类模式将对象的部分创建工作延迟到子类;
创建型对象模式将对象的部分创建工作延迟到另一个对象中;
结构型类模式使用继承机制类组合类;
结构型对象模式描述了对象的组装方式;
行为型类模式使用继承描述算法和控制流;
行为型对象模式描述一组对象怎样写作完成单个对象无法完成的任务;
设计模式之间的关系
设计模式所支持的设计的可变方面
设计模式怎样解决设计问题
1. 寻找合适的对象
- 面向对象程序由对象组成,对象包括数据和对数据进行操作的过程,过程通常称为方法或操作。
- 对象在收到客户的请求(或消息)后,执行相应的操作。
- 客户请求是使对象执行操作的唯一方法,操作又是对象改变内部数据的唯一方法。
- 由于这些限制,对象的内部状态是被封装的,它不能被直接访问,它的表示对于对象外部是不可见的。
- 面向对象设计最困难的部分是将系统分解成对象集合。
- 因为要考虑许多因素:封装、粒度、依赖关系、灵活性、性能、演化、复用等,它们都影响着系统的分解,并且这些因素通常还是互相冲突的。
- 设计的许多对象来源于现实世界的分析模型。但是,设计结果所得到的类通常在现实世界中并不存在,有些是像数组之类的低层类,而另一些则层次较高。
- 设计模式帮你确定并不明显的抽象和描述这些抽象的对象。例如,描述过程或算法的对象现实中并不存在,但它们却是设计的关键部分。
2. 决定对象的粒度
- 对象在大小和数目上的变化极大。它们能表示下自硬件或上自整个应用的任何事物。
- 那么我们怎么决定一个对象应该是什么呢?设计模式很好地讲述了这个问题。
- Facade 模式描述了怎样用对象表示完整的子系统。
- Flyweight 模式描述了如何支持大量的最小粒度的对象。
- 其他一些设计模式描述了将一个对象分解成许多小对象的特定方法。
- Abstract Factory 和 Builder 模式产生那些专门负责生成其他对象的对象。
- Visitor 和 Command 生成的对象专门负责实现对其他对象或对象组的请求。
3. 指定对象接口
- 对象声明的每一个操作指定操作名、作为参数的对象和返回值,这就是所谓的操作的型构(Signature)。
- 对象操作所定义的所有操作型构的集合被称为该对象的接口(Interface)。
- 对象接口描述了该对象所能接受的全部请求的集合,任何匹配对象接口中型构的请求都可以发送给该对象。
- 设计模式通过确定接口的主要组成成分及经接口发送的数据类型,来帮助你定义接口。
- 设计模式指定了接口之间的关系。它们经常要求一些类具有相似的接口,或它们对一些类的接口做了限制。
3.1 类型(Type)是用来标识特定接口的一个名字。
-
- 当一个类型的接口包含另一个类型的接口时,我们就说它是另一个类型的子类型(Subtype),另一个类型称之为它的超类型(Supertype)。
- 我们常说子类型继承了它的超类型的接口。
- 在面向对象系统中,接口时基本的组成部分。对象只有通过它们的接口才能与外部交流。
- 对象接口与其功能实现是分离的,不同对象可以对请求做不同的实现。
3.2 多态(Polymorphism)
-
- 当给对象发送请求时,所引起的具体操作既与请求本身有关又与接受对象有关。
- 支持相同请求的不同对象可能对请求激发的操作有不同的实现。
- 发送给对象的请求和它的相应操作在运行时刻的连接就称之为动态绑定(Dynamic Binding)。
- 动态绑定是指发送的请求知道运行时刻才受你的具体的实现的约束。
- 动态绑定允许你在运行时刻彼此替换有相同接口的对象。这种可替换性就称为多态(Polymorphism)。
- 多态允许客户对象仅要求其他对象支持特定的接口,除此之外对其假设几近于无。
4. 描述对象的实现
- 对象通过实例化类来创建,此对象被称为该类的实例。
- 抽象类(Abstract Class)的主要目的是为它的子类定义公共接口。
- 一个抽象类将把它的部分或全部操作的实现延迟到子类中,因此,一个抽象类不能被实例化。
- 子类能够重定义(override)父类定义的操作,重定义使得子类能接管父类对请求的处理操作。
- 类继承允许你只需简单的扩展其他类就可以定义新类,从而可以很容易地定义具有相近功能的对象族。
- 类继承根据一个对象的实现定义了另一个对象的实现。简而言之,它是代码和表示的共享机制。
- 接口继承(或子类型化)描述了一个对象什么时候能被用来替代另一个对象。
- 尽管大部分程序设计语言并不区分类继承和接口继承,但使用中人们还是分别对待它们的。
- 面向对象的设计原则:针对接口编程,而不是针对实现编程。
5. 运用复用机制
- 面向对象系统中功能复用的两种最常用技术是类继承和对象组合(Object Composition)。
- 类继承允许你根据其他类的实现来定义一个类的实现。这种通过生成子类的复用通常被称为白箱复用(White-Box Reuse)。
- 新的更复杂的功能可以通过组装或组合对象来获得。这种复用风格被称为黑箱复用(Black-Box Reuse)。因为对象的内部细节是不可见的。
- 父类通常至少定义了部分子类的具体表示。因此继承对子类揭示了其父类的实现细节,所以继承常被认为“破坏了封装性”。
- 一个可用的解决方法就是只继承抽象类,因为抽象类通常提供较少的实现。
- 对象组合是通过获得对其他对象的引用而在运行时刻动态定义的。组合要求对象遵守彼此的接口约定,进而要求更仔细地定义接口。
- 因为对象只能通过接口访问,所以我们并不破坏封装性。
- 使用对象组合有助于保持每个类被封装并被集中在单个任务上,这样类和类继承层次会保持较小规模。
- 面向对象的设计原则:优先使用对象组合,而不是类继承。
6. 关联运行时和编译时的结构
- 代码不可能揭示关于系统如何工作的全部,系统的运行时结构更多地受到设计者的影响,而不是编程语言。
- 对象和它们的类型之间的关系必须更加仔细的设计,因为它们决定了运行时程序结构的好坏。
- 许多设计模式显式地记述了编译时和运行时结构的差别。
- Composite 和 Decorator 对于构造复杂的运行时结构特别有用。
- Observer 也与运行时结构有关。
- Chain of Responsibility 也产生了继承所无法展现的通信模式。
7. 设计应支持变化
- 获得最大限度复用的关键在于对新需求和已有需求发生变化时的预见性,要求你的系统设计要能够相应的改进。
- 一个不考虑系统变化的设计在将来就有可能需要重新设计。
- 设计模式可以确保系统能以特定方式变化,从而帮助你避免重新设计系统。
源代码
以上内容全部来自《Design Patterns: Elements of Reusable Object-Oriented Software》一书。