首页 > 代码库 > 【设计模式】使用设计模式来解决设计问题
【设计模式】使用设计模式来解决设计问题
选择对象
面向对象设计最困难的部分是将系统分解成对象集合。因为要考虑许多因素:封装、粒度、依赖关系、灵活性、性能、演化、复用等等,它们都影响着系统的分解,并且这些因素通常还是互相冲突的。
设计的许多对象来源于现实世界的分析模型。但是,设计结果所得到的类通常在现实世界中并不存在。设计模式帮你确定并不明显的抽象和描述这些抽象的对象。例如,描述过程或算法的对象现实中并不存在,但它们却是设计的关键部分。Strategy模式描述了怎样实现可互换的算法族。State模式将实体的每一个状态描述为一个对象。这些对象在分析阶段,甚至在设计阶段的早期都并不存在,后来为使设计更灵活、复用性更好才将它们发掘出来。
对象的粒度
Facade模式描述了怎样用对象表示完整的子系统,Flyweight模式描述了如何支持大量的最小粒度的对象。其他一些设计模式描述了将一个对象分解成许多小对象的特定方法。Abstract Factory和Builder产生那些专门负责生成其他对象的对象。Visitor和Command生成的对象专门负责实现对其他对象或对象组的请求。
对象接口
对象接口描述了该对象所能接受的全部请求的集合,任何匹配对象接口中型构的请求都可以发送给该对象。
在面向对象系统中,接口是基本的组成部分。对象只有通过它们的接口才能与外部交流,如果不通过对象的接口就无法知道对象的任何事情,也无法请求对象做任何事情。对象接口与其功能实现是分离的,不同对象可以对请求做不同的实现,也就是说,两个有相同接口的对象可以有完全不同的实现。
动态绑定(dynamic binding)
当给对象发送请求时,所引起的具体操作既与请求本身有关又与接受对象有关。发送给对象的请求和它的相应操作在运行时刻的连接就称之为动态绑定 (dynamic binding)。
动态绑定是指发送的请求直到运行时刻才受你的具体的实现的约束。动态绑定允许你在运行时刻彼此替换有相同接口的对象。这种可替换性就称为多态,它是面向对象系统中的核心概念之一。多态简化了客户的定义,使得对象间彼此独立,并可以在运行时刻动态改变它们相互的关系。
设计模式指定对象接口
设计模式通过确定接口的主要组成成分及经接口发送的数据类型,来帮助你定义接口。
Memento模式描述了怎样封装和保存对象内部的状态,以便一段时间后对象能恢复到这一状态。它规定了Memento对象必须定义两个接口:一个允许客户保持和复制Memento的限制接口,和一个只
有原对象才能使用的用来储存和提取Memento中状态的特权接口。
设计模式也指定了接口之间的关系。特别地,它们经常要求一些类具有相似的接口;或
它们对一些类的接口做了限制。例如Decorator和Proxy模式要求Decorator和Proxy对象的接口与被修饰的对象和受委托的对象一致。而Visitor模式中,Visitor接口必须反映出Visitor能访问的对象的所有类。
对象的实现
对象通过实例化类来创建。当实例化类时,要给对象的内部数据(由实例变量组成)分配存储空间,并将操作与这些数据联系起来。对象的许多类似实例是由实例化同一个类来创建的。
新的类可以由已存在的类通过类继承(class inheritance)来定义。当 子类(subclass)继承父类(parent class) 时,子类包含了父类定义的所有数据和操作。子类的实例对象包含所有子类和父类定义的数据,且它们能完成子类和父类定义的所有操作。
抽象类
抽象类(abstract class)的主要目的是为它的子类定义公共接口。一个抽象类将把它的部分或全部操作的实现延迟到子类中,因此,一个抽象类不能被实例化。在抽象类中定义却没有实现的操作被称为抽象操作 (abstract operation) 。
子类能够重定义(override)父类定义的操作,重定义使得子类能接管父类对请求的处理操作。
对接口编程,而不是对实现编程
类继承是一个通过复用父类功能而扩展应用功能的基本机制。它允许你根据旧对象快速定义新对象。
当继承被恰当使用时,所有从抽象类导出的类将共享该抽象类的接口。这意味着子类仅仅添加或重定义操作,而没有隐藏父类的操作。这时,所有的子类都能响应抽象类接口中的请求,从而子类的类型都是抽象类的子类型。
只根据抽象类中定义的接口来操纵对象有以下两个好处:
- 客户无须知道他们使用对象的特定类型,只须对象有客户所期望的接口。
- 客户无须知道他们使用的对象是用什么类来实现的,他们只须知道定义接口的抽象类。
针对接口编程,而不是针对实现编程。
不将变量声明为某个特定的具体类的实例对象,而是让它遵从抽象类所定义的接口。
复用机制
对象组合
面向对象系统中功能复用的两种最常用技术是类继承和对象组合。
类继承允许你根据其他类的实现来定义一个类的实现。这种通过生成子类的复用通常被称为白箱复用 (white-box reuse) 。在继承方式中,父类的内部细节对子类可见。
对象组合要求被 组合 的对象具有良好定义的接口。这种复用风格被称为 黑箱复用(black-box reuse),因为对象的内部细节是不可见的。
优点:
- 类继承是在编译时刻静态定义的,且可直接使用。类继承可以较方便地改变被复用的实现。
不足:- 因为继承在编译时刻就定义了,所以无法在运行时刻改变从父类继承的实现。
- 因为继承对子类揭示了其父类的实现细节,所以继承常被认为“破坏了封装性”
- 子类中的实现与它的父类有如此紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。
对象组合是通过获得对其他对象的引用而在运行时刻动态定义的。组合要求对象遵守彼此的接口约定,进而要求更仔细地定义接口,而这些接口并不妨碍你将一个对象和其他对象一起使用。
这种方式带来的好处:因为对象只能通过接口访问,所以我们并不破坏封装性;只要类型一致,运行时刻还可以用一个对象来替代另一个对象;更进一步,因为对象的实现是基于接口写的,所以实现上存在较少的依赖关系。
参数化类型
另一种功能复用技术 (并非严格的面向对象技术)是参数化类型。
参数化类型允许你在定义一个类型时并不指定该类型所用到的其他所有类型。未经指定的类型在使用时以参数形式提供。
对象组合技术允许你在运行时刻改变被组合的行为,但是它存在间接性,比较低效。继承允许你提供操作的缺省实现,并通过子类重定义这些操作。参数化类型允许你改变类所用到的类型。但是继承和参数化类型都不能在运行时刻改变。
支持变化的设计
获得最大限度复用的关键在于对新需求和已有需求发生变化时的预见性,要求你的系统设计要能够相应地改进。
设计模式可以确保系统能以特定方式变化,从而帮助你避免重新设计系统。每一个设计模式允许系统结构的某个方面的变化独立于其他方面,这样产生的系统对于某一种特殊变化将更健壮。
设计模式在开发主要软件中起的作用
应用程序
软件的内部复用性、可维护性和可扩充性在开发应用程序时要优先考虑。内部复用性确保你不会做多余的设计和实现。设计模式通过减少依赖性来提高内部复用性。松散耦合也增强了一类对象与其他多个对象协作的可能性。
当设计模式被用来对系统分层和限制对平台的依赖性时,它们还会使一个应用更具可维护性。通过显示怎样扩展类层次结构和怎样使用对象复用,它们可增强系统的易扩充性。同时,耦合程度的降低也会增强可扩充性。如果一个类不过多地依赖其他类,扩充这个孤立的类还是很容易的。
工具箱
一个应用经常会使用来自一个或多个被称为工具箱(Toolkit)的预定义类库中的类。工具箱是一组相关的、可复用的类的集合,这些类提供了通用的功能。工具箱强调的是代码复用,它们是面向对象环境下的“子程序库” 。
工具箱的设计比应用设计要难得多,因为它要求对许多应用是可用的和有效的。再者,工具箱的设计者并不知道什么应用使用该工具箱及它们有什么特殊需求。这样,避免假设和依赖就变得很重要,否则会限制工具箱的灵活性,进而影响它的适用性和效率。
框架
框架(Framework) 是构成一类特定软件可复用设计的一组相互协作的类。
框架规定了你的应用的体系结构。它定义了整体结构,类和对象的分割,各部分的主要责任,类和对象怎么协作,以及控制流程。框架预定义了这些设计参数,以便于应用设计者或实现者能集中精力于应用本身的特定细节。框架记录了其应用领域的共同的设计决策。因而框架更强调设计复用,尽管框架常包括具体的立即可用的子类。
【设计模式】使用设计模式来解决设计问题