设计模式怎样解决设计问题[笔记]

来源:互联网 发布:网络与新媒体考研学校 编辑:程序博客网 时间:2024/04/30 01:17

1、寻找合适的对象

面向对象程序由对象组成,对象包括数据和对数据进行操作的过程,过程通常称为方法或操作。对象在收到客户的请求(或消息)后,执行相应的操作。

面向对象设计最困难的部分是将系统分解成对象集合。因为要考虑许多因素:封装、粒度、依赖关系、灵活性、性能、演化、复用等等,它们都影响着系统的分解,并且这些因素通常还是互相冲突的。

 面向对象设计方法学支持许多设计方法,设计中的抽象对于产生灵活的设计是至关重要。

2、决定对象的粒度

对象在大小和数目上变化极大,根据需要选用不同的设计模式。

3 、指定对象接口

对象接口描述了该对象所能接受的全部请求的集合,任何匹配对象接口中型构的请求都可以发送给该对象。

对象只有通过它们的接口才能与外部交流,如果不通过对象的接口就无法知道对象的任何事情,也无法请求对象做任何事情。

4 、描述对象的实现

对象通过实例化类来创建,此对象被称为该类的实例。

抽象类(abstract class)的主要目的是为它的子类定义公共接口。一个抽象类将把它的部分或全部操作的实现延迟到子类中,因此,一个抽象类不能被实例化。

(1)类继承与接口继承的比较

一个对象的类定义了对象是怎样实现的,同时也定义了对象的内部状态和操作的实现。但是对象的类型只与它的接口有关,接口即对象能响应的请求的集合。

类继承根据一个对象的实现定义了另一个对象的实现。简而言之,它是代码和表示的共享机制。然而,接口继承(或子类型化)描述了一个对象什么时候能被用来替代另一个对象。

(2.)对接口编程,而不是对实现编程

不将变量声明为某个特定的具体类的实例对象,而是让它遵从抽象类所定义的接口。这是设计模式的一个常见主题。

5、运用复用机制

面向对象系统中功能复用的两种最常用技术是类继承和对象组合(object composition)。

面向对象设计的第二个原则:

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

类继承是在编译时刻静态定义的,且可直接使用,可以较方便地改变被复用的实现。但是继承在编译时刻就定义了,所以无法在运行时刻改变从父类继承的实现。。更糟的是,父类通常至少定义了部分子类的具体表示。因为继承对子类揭示了其父类的实现细节,所以继承常被认为“破坏了封装性”,子类中的实现与它的父类有如此紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。一个可用的解决方法就是只继承抽象类,因为抽象类通常提供较少的实现。

对象组合是通过获得对其他对象的引用而在运行时刻动态定义的。组合要求对象遵守彼此的接口约定,进而要求更仔细地定义接口,而这些接口并不妨碍你将一个对象和其他对象一起使用。这还会产生良好的结果:因为对象只能通过接口访问,所以我们并不破坏封装性;只要类型一致,运行时刻还可以用一个对象来替代另一个对象;更进一步,因为对象的实现是基于接口写的,所以实现上存在较少的依赖关系。

对象组合对系统设计还有另一个作用,即优先使用对象组合有助于你保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。另一方面,基于对象组合的设计会有更多的对象(而有较少的类),且系统的行为将依赖于对象间的关系而不是被定义在某个类中。

理想情况下,你不应为获得复用而去创建新的构件。你应该能够只使用对象组合技术,通过组装已有的构件就能获得你需要的功能。但是事实很少如此,因为可用构件的集合实际上并不足够丰富。使用继承的复用使得创建新的构件要比组装旧的构件来得容易。这样,继承和对象组合常一起使用。然而,我们的经验表明:设计者往往过度使用了继承这种复用技术。但依赖于对象组合技术的设计却有更好的复用性(或更简单)。你将会看到设计模式中一再使用对象组合技术。


委托

委托(d e l e g a t i o n)是一种组合方法,它使组合具有与继承同样的复用能力。在委托方式下,有两个对象参与处理一个请求,接受请求的对象将操作委托给它的代理者(d e l e g a t e)。这类似于子类将请求交给它的父类处理。使用继承时,被继承的操作总能引用接受请求的对象,C + +中通过t h i s成员变量, S m a l l t a l k中则通过s e l f。委托方式为了得到同样的效果,接受请求的对象将自己传给被委托者(代理人),使被委托的操作可以引用接受请求的对象。


举例来说,我们可以在窗口类中保存一个矩形类的实例变量来代理矩形类的特定操作,这样窗口类可以复用矩形类的操作,而不必像继承时那样定义成矩形类的子类。也就是说,一个窗口拥有一个矩形,而不是一个窗口就是一个矩形。窗口现在必须显式的将请求转发给它的矩形实例,而不是像以前它必须继承矩形的操作。

委托的主要优点在于它便于运行时刻组合对象操作以及改变这些操作的组合方式。假定矩形对象和圆对象有相同的类型,我们只需简单的用圆对象替换矩形对象,则得到的窗口就是圆形的。

委托与那些通过对象组合以取得软件灵活性的技术一样,具有如下不足之处:动态的、高度参数化的软件比静态软件更难于理解。还有运行低效问题,不过从长远来看人的低效才是更主要的。只有当委托使设计比较简单而不是更复杂时,它才是好的选择。

继承和参数化类型的比较

另一种功能复用技术(并非严格的面向对象技术)是参数化类型(parameterized type),也就是类属( g e n e r i c ) ( A d a、E i ff e l )或模板( t e m p l a t e s ) ( C + + )。它允许你在定义一个类型时并不指定该类型所用到的其他所有类型。未经指定的类型在使用时以参数形式提供。

对象组合技术允许你在运行时刻改变被组合的行为,但是它存在间接性,比较低效。继承允许你提供操作的缺省实现,并通过子类重定义这些操作。参数化类型允许你改变类所用到的类型。但是继承和参数化类型都不能在运行时刻改变。哪一种方法最佳,取决于你设计和实现的约束条件。

6、 关联运行时刻和编译时刻的结构

聚合意味着一个对象拥有另一个对象或对另一个对象负责。一般我们称一个对象包含另一个对象或者是另一个对象的一部分。聚合意味着聚合对象和其所有者具有相同的生命周期。


相识意味着一个对象仅仅知道另一个对象。有时相识也被称为“关联”或“引用”关系。相识的对象可能请求彼此的操作,但是它们不为对方负责。相识是一种比聚合要弱的关系,它只标识了对象间较松散的耦合关系。

是聚合还是相识是由你的意图而不是由显式的语言机制决定的。

7 、设计应支持变化

获得最大限度复用的关键在于对新需求和已有需求发生变化时的预见性,要求你的系统设计要能够相应地改进。为了设计适应这种变化、且具有健壮性的系统,你必须考虑系统在它的生命周期内会发生怎样的变化。


下面阐述了一些导致重新设计的一般原因:

1) 通过显式地指定一个类来创建对象 在创建对象时指定类名将使你受特定实现的约束而不是特定接口的约束。这会使未来的变化更复杂。要避免这种情况,应该间接地创建对象。
2) 对特殊操作的依赖 当你为请求指定一个特殊的操作时,完成该请求的方式就固定下来了。为避免把请求代码写死,你将可以在编译时刻或运行时刻很方便地改变响应请求的方法。
3) 对硬件和软件平台的依赖 外部的操作系统接口和应用编程接口( A P I )在不同的软硬件平台上是不同的。依赖于特定平台的软件将很难移植到其他平台上,甚至都很难跟上本地平台的更新。
4) 对对象表示或实现的依赖 知道对象怎样表示、保存、定位或实现的客户在对象发生变化时可能也需要变化。对客户隐藏这些信息能阻止连锁变化。
5) 算法依赖 算法在开发和复用时常常被扩展、优化和替代。依赖于某个特定算法的对象在算法发生变化时不得不变化。因此有可能发生变化的算法应该被孤立起来。
6) 紧耦合 紧耦合的类很难独立地被复用,因为它们是互相依赖的。紧耦合产生单块的系统,要改变或删掉一个类,你必须理解和改变其他许多类。这样的系统是一个很难学习、移植和维护的密集体。松散耦合提高了一个类本身被复用的可能性,并且系统更易于学习、移植、修改和扩展。设计模式使用抽象耦合和分层技术来提高系统的松散耦合性。
7) 通过生成子类来扩充功能
8) 不能方便地对类进行修改