2014年9月11~17日(设计模式)
- 到这一部分结束时,读者讲明白什么是设计模式,为什么它们有用,并且熟悉这四个特定的模式。
- 设计模式简介
- 设计模式产生于建筑学和人类学。
- Alexander认为建筑学系统中的确存在这样的客观依据。“评价一个建筑物是否美观”并不仅仅是一个品味的问题。我们可以通过可以衡量的客观标准来描述美观程度。
- Alexander开始寻找共同点:建筑结构彼此之间互不相同,即使它们类型相同。但是,尽管它们互不相同,它们仍然可以都是高质量的。
- 结构不可以与它们要解决的问题相分离。
![]()
- Alexander发现:观察解决相似问题的不同解决方案。洞悉优质设计之间的相似之处。这些相似之处被称为模式
- 他把模式定义为“在某一个情景下的问题解决方案”。
- Alexander认为一个模式的说明应该包括四个项目:模式的名称;模式的目的,它要解决的问题;我们如何实现它;为了实现它我们必须考虑的限制和约束。
- 将Alexander的思想应用于软件。
- 学习设计模式理由:复用解决方案(不必再为普通、重复的问题重新设计解决方案);建立通用的术语(便于沟通);提供了一个更高层次的视角(这样的视角将你从“过早处理细节”的“暴政”中解放出来。。。呜呜。。。等了好久,总算有救星了);改善代码的可修改性;
- Facade(外观)模式
- 意图:Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
- 促成Facade模式的一个例子:学习如何使用我们的复杂的系统。
- 实用范围:“使用系统能力的一个子集”或“用特殊的方式与系统交互”。
- 意图:希望简化现有系统的使用方法。你需要定义自己的接口。
- 问题:只需要使用一个复杂系统的一个子集。或者,需要用一种特殊的方式与系统交互。
- 解决方案:Facade向客户展现使用现有系统的一个新的接口。
- 参与者与协作者:向用户展现一个定制的接口,让客户更容易地使用现有系统。
- 效果:Facade模式简化了对所需子系统的使用。但是,由于Facade并不完整,因此某些功能对客户可能是不可用得。
- 实现:定义一个(或一组)新的类来提供所需的接口;让新的类使用现有的系统。
![]()
- Facade另一个作用:用于隐藏或包装原有的系统。Facade可以把原有系统作为自己的私有成员。
- Facade模式适用于一下情况:不需要使用一个复杂系统的所有功能,并且可以创建一个新的类来百荣访问原有系统所使用的所有规则;希望包装或隐藏原有系统;希望使用原有系统的功能,并且希望增加新的功能;“编写一个新的类”的代价小于“让所有人学会使用原有系统”或“在未来维护整个系统”所需的代价。
- Adapter(适配器)模式
- Adapter模式是一个非常有用的模式,它可以与其他很多模式共同使用。
- 模式的意图是:将一个类的接口转换成客户希望的另一个接口。Adapter模式使原本由于接口不兼容而不能一起工作的那些类可以一起工作。我们需要一种方法,为一个内容合适但接口不匹配的对象创建一个新的接口。
现在客户要求我添加一个Circle。。。。所以我就貌似要编写display、fill、undisplay。。。- 但是我发现了同事的一个类,几乎包含了我所有方法
![]()
- 如何 实现Shape的接口而不必重写XXCircle类中圆形的实现代码?
![]()
- Circle类派生自Shape类;Circle对象包含XXCircle对象;Circle对象将收到的请求转发给内部的XXCircle对象。
- Adapter模式:关键特征。
- 意图:讲一个无法控制的现有对象(XXCircle)与一个特定的接口想匹配。
- 问题:一个系统拥有正确的数据和行为,但是接口确实错误的。典型用途:你必须把某些东西实现为我们定义或已经拥有的抽象类的派生类。
- 解决方案:Adapter模式对我们需要的接口对无法修稿的类进行包装。
- 参与者与协作者:Adapter对Adapter的接口进行适配,使它与Target(Adapter派生自它)相匹配。让Client把Adapter当作Target的一个类型来使用它;
- 效果:Adapter模式让现存的对象适应新的类结构,而不受他们的接口限制。
- 实现:将现存的类包含在另一个类之中。包容类与需要的接口匹配,并调用被包容类的方法。
![]()
- 新的问题:被适配的对象可能不具备我们想要的所有东西。
- 解决办法:现存类已实现的那些功能 可以被适配;现存对象没有实现的那些功能可以在适配对象中实现。
- Adapter模式两种类型:对象Adapter模式(适配对象包含一个被适配对象);类Adapter模式(使用多重继承)。
![]()
- 结论:Facade模式简化接口,而Adapter模式将接口转换成另一个现有的接口。
- Adapter模式非常有用,它将一个类(或一组)的接口转换成另一个我们需要的接口。他这样实现这一点:创建一个拥有所需接口的心累,然后包装原来类的方法,从而有效地包容被适配的对象。
- 拓展我们的视野
- 本章意在用新的视角看待旧的思考。
- 对象
- 原来的观点:伴随有方法的数据。(实现的视角) 新的观点:拥有责任的事物。(概念的视角)
- 我们需将注意力集中在“对象应该做什么”而不是“如何实现它们”
- 构建软件分两部:(在设计层面上思考)
- 建立一个初步的设计,不必担心设计的任何细节。
- 实现前面一个步骤得到的设计。
- 关注动机而非实现。
- 封装
- 原先的观点:数据影藏 新的观点:任何形式的影藏
![]()
- 数据的封装(Point、Line对象中的数据);方法的封装(Circle类的setLocation);子类的封装(Shape类的客户不会看到Point、Line等);其他对象的封装(XXCircle对象);
- 发现并封装变化点
- 使用一个标志和可能建立在此基础上的switch语句都可能引起紧耦合。如果这个标志还暗示着其他的区别。无论如何,这样的代码都很可能是凌乱的。
- 使用派生类的话,如何处理同时拥有俩个派生类特性的对象?
- 运用对象处理行为中的变化点
![]()
- 在面向对象程序设计中,任何东西都是对象。即使内建数据类型也是对象,它们的行为就是算法。
![]()
- “规格”描述了如何与一组概念上相似的对象沟通。这些对象的每一个都表现出共有概念的变化情况。规格将成为实现层次上的抽象类或接口。
- Bridge(桥接)模式
- Bridge模式比前面的Facade和Adapter模式都更为复杂,但却更有用。
- Bridge模式的意图是“将抽象部分与它的实现部分分离,使它们都可以独立地变化”。
- 这里的实现部分是指“抽象类的对象和用来实现抽象类的派生类的对象”
- 还不理解? 看看下面吧
- 一个有用的结论:在需求定义的过程中,尽早,经常地研究变化!
- 新的问题:用两种不同的程序DP1和DP2来画矩形。。。集合对象(矩形的客户对象)不希望自己考虑究竟是用什么画图程序。。。
- 我们能够迅速提出
![]()
- 需求总是在变化。。。用户要求我们支持另一种形状圆形。。。集合对象还是不希望知道Rectangle和Circle之间的差异
- 我们还是能够提出
![]()
- 但是,你没发现吗? 这是一种紧耦合的方式
- 假如我们新添了另一套画图程序,第三行类便会增加为6个。。。
- 加入我们在获得一个新的形状,第三行不久变为了9个类?
- 典型的组合型类爆炸。。。因为抽象部分(Shape的子类型)与它的实现部分(画图程序)是紧耦合的
![]()
- 可是还是有点不死心。。。
![]()
- 这样子虽然避免了DP1和DP2报之间的冗余,但是却无法避免两个Rectangle之间和两个Circle之间的冗余。。。
- 策略:发现并封装变化点;优先使用对象组合,而不是类继承。。。
- 此题的变化点:“不同类型的形状”;“不同类型的画图程序”;(观察到这点是相当重要的)
![]()
- 下面两种可能性:Shape类使用Drawing类,或是Drawing类使用Shape。。。如果是后者,那么就违反了对象的一个基本原则:对象应该只对自己负责。还会破坏封装。Drawing对象必须知道Shape的特定信息才能呢个画出它们。
![]()
- 重复是有害的。。。(这次的search类可是害死我很多次)。。。记住:"一条规则,一个地方"
draw的调用。。。肯定里面有Adapter模式撒、、、-
1 class Client{ 2 public static void main 3 (String argv[]){ 4 Shape r1,r2; 5 Drawing dp; 6 7 dp = new V1Drawing(); 8 r1 = new Rectangle(dp,1,1,2,2); 9 10 dp = new V2Drawing(); 11 r2 = new Circle(dp,2,2,3); 12 13 r1.draw(); 14 r2.draw(); 15 } 16 }
- Bridge模式:关键特征
- 意图:将一组实现部分从另一组使用它们的对象中分离出来。
- 问题:一个抽象类的派生类必须使用多种实现部分,但有不能引起类数量的爆炸。
- 解决方案:为所有的实现部分顶一个接口,让抽象类的所有派生类使用这个接口。(draw)
- 参与者与协作者:Abstraction为正在实现的对象定义接口。Implementor为特定的实现部分类定义接口。Abstraction的派生类使用Implementor的派生类,而不必知道自己使用的特定ConcreteImplementor。
- 效果:“实现部分与使用它的对象分离”增加灵活性。
- 实现:将实现部分封装在一个抽象类中;在被实现的抽象部分基类中包含一个实现部分基类的句柄。
![]()
- 但是,假如:我们需要画一个椭圆。现在的Drawing类没有适合画椭圆的方法。这就必须要动实现部分了。。。但是至少有一个良好的修改过程(修改Drawing接口,在修改派生类)。。
- 结论:模式并不是总能提供我那没的解决方案。但是,因为模式体现的是许多设计者多年集合而成的经验,所以它们通常比我们自己你呢个提出的解决方案要好。
- Bridge面向对象原则:
- 对象对自己负责:我们拥有不同子类型的Shape,但它们都可以画出自己。Drawing类对象对画图元素负责。
- 抽象类:我们用抽象类表示概念。Shape类表示形状的概念。
- 通过抽象类进行封装:一个使用Bridge模式的客户将只看到一个Shape类的派生对象。优点:在未来需要新的子类型,它将不会影响到客户对象;Drawing类向Shape类影藏了不同的派生类。
- 一条规则一个地方:抽象类经常让自己的方法实际使用实现部分对象。抽象部分的派生类将调用这些方法。
- Abstract Factory(抽象工厂)模式
- Abstract Factory模式的意图是“提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类”。
- 协调对象的实例化
- 一个例子:设计一个计算机系统,显示并打印来自数据库的几何形状。(但要适应不同的计算机型号。。。)
![]()
- 我们立马能想到switch的方式。。。
- 紧耦合 低内聚(“判断使用哪个驱动程序的规则”与“实际使用被混在一起了”)
- 紧耦合、低内聚也许不会立即成为问题,但是维护的代价高。。。
- 另一种方案:继承
![]()
- 缺点:组合爆炸(对于未来每个新系列都需要创建新的具体类);含义不明(作为结果产生的类无助于阐明自己的目的);需要优先使用组合(违反了“优先使用对象组合而非类继承”)
- 往往使用switch的地方就能够使用抽象类。。。
- 另一种方式
![]()
- 这样便会产生新的问题,谁来产生对象组合呢?
- 如果,我们使用一个“工厂”对象负责将我需要的对象实例化,我们就完成了。。。
- ApControl让ResFactory来负责"留意"使用那些驱动。。。ApControl有责任知道如何与合适的对象协同工作。我们可以使用不同的工厂对象乃至只使用一个工厂对象(他可能使用switch语句)。。。。但是这种方案造成了类聚性(程序中,两个操作之间的紧密程度)的提高。。。
cool...这样不久避免了switch语句了么?- AbstractFactory策略
- 发现并封装变化点:“使用哪个驱动对象”的选择是变化的。所以,可以将其封装在ResFactory中。
- 优先使用对象组合,而不是类继承:将变化点放在一个独立的对象---ResFactory对象。
- 针对接口设计,而不是针对实现设计:ApControl知道怎样向ResFactory请求实例化对象---它不知道(或不关心)ResFactory对象如何实际响应。
-
abstract class ResFactory{ abstract public DisplayDriver getDispDrvr(); abstract public PrintDriver getPrtDrvr(); } class LowResFact extends ResFactory{ public DisplayDriver getDispDrvr(){ return new LRDD(); } public PrintDriver getPrtDrvr(){ return new LRPD(); } } class HighResFact extends ResFactory{ public DisplayDriver getDispDrvr(){ return new HRDD(); } public PrintDriver getPrtDrvr(){ return newHRPD(); } }
- ResFactory是一个抽象类,“对ResFactory实现细节的隐藏”正式这个模式工作的源动力。因此,这个模式有了“Abstract Factory”这个名字。
![]()
- 问题又出现了。。。假如LRDD和HRDD不是派生自同一个抽象类呢???
- 解决方案:使用Adapter模式。。。
![]()
- 可以这么说,Adapter模式成就了Abstract Factory模式。。。
- Abstract Factory模式关键特征
- 意图:你需要为特定的客户(或情况)提供特定系列的对象。
- 问题:一系列相关的对象需要被实例化。
- 解决方案:协调不同系列对象的创建过程。提供一种方法保持“如何创建需要的每个系列的对象”定义接口。
- 效果:这个模式将“使用哪些对象”的规则与“如何使用这些对象”(使用接口)的逻辑隔离。
- 实现:定义一个抽象类来指定哪些对象将被创建。然后为每个系列实现一个具体类。
![]()
- 跨平台程序,可以运用这种模式撒。。。多版本。。。多种用户



现在客户要求我添加一个Circle。。。。所以我就貌似要编写display、fill、undisplay。。。












draw的调用。。。肯定里面有Adapter模式撒、、、



cool...这样不久避免了switch语句了么?


浙公网安备 33010602011771号