Head first设计模式(3)

装饰者模式

1、我曾经以为男子汉应该用继承处理一切,后来我领教到运行时扩展,远比编译时期的继承威力大,看看我现在光彩的样子

2、“给爱用继承的人一个全新的设计眼界”,我们即将再度讨论典型的继承滥用问题,如何使用对象组合的方式,做到在运行时装饰类。为什么呢?一旦你熟悉了装饰的技巧,你将能够在不修改任何底层代码的情况下,给你的(或别人的)对象赋予新的职责

星巴兹咖啡

1、这是一个以扩张速度最快而闻名的咖啡连锁店

2、因为扩张速度实在太快了,他们准备更新订单系统,以合乎他们的饮料供应要求

原先的类设计是这样的

1、Beverage(饮料)是一个抽象类,店内所提供的饮料都必须继承自此类

2、这个名为description(叙述)的实例变量,由每个子类设置,用来描述饮料,例如“超优深焙(Dark Roast)咖啡豆”

3、cost()方法是抽象的子类必须定义自己的实现

4、每个子类实现cost()来返回饮料的价钱

需求来了

购买咖啡时,也可以要求在其中加入各种调料,例如:蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha,也就是巧克力风味)或覆盖奶泡。星巴兹会根据所加入的调料收取不同的费用。所以订单系统必须考虑到这些调料部分

这是第一次尝试

1、每个cost()方法将计算出咖啡加上订单上各种调料的价钱

2、哇塞!这简直是“类爆炸”

3、很明显,星巴兹为自己制造了一个维护噩梦。如果牛奶的价钱上扬,怎么办?新增一种焦糖调料风味时,怎么办?

是呀,干嘛这几这么多类?利用实例变量和继承,就可以追踪这些调料呀!

好吧!就来试试看,先从Beverage基类下手,加上实例变量代表是否加上调料(牛奶、豆浆、摩卡、奶泡... ...)

1、各种调料的新的布尔值

2、现在,Beverage类中的cost()不再是一个抽象方法,我们提供了cost()的实现,让它计算要加入各种饮料的调料价钱。子类仍将覆盖cost(),但是会调用超类的cost(),计算出基本饮料加上调料的价钱

3、这些方法取得和设置调料的布尔值

1、现在加入子类,每个类代表菜单上的一种饮料

2、超类cost()将计算所有调料的价钱,而子类覆盖过的cost()会扩展超类的功能,把指定的饮料类型的价钱也加进来

3、每个cost()方法需要计算该饮料的价钱,然后通过调用超类的cost()实现,加入调料的价钱

通过思考设计将来可能需要的变化,可以看出来这种方法有一些潜在的问题

1、调料价钱的改变会使我们更改现有代码

2、一旦出现新的调料,我们就需要加上新的代码,并改变超类中的cost()方法

3、以后可能开发出新的饮料,对这些饮料而言(例如:冰茶),某些调料可能并不适合,但是在这个设计方式中,Tea(茶)子类仍将继承那些不适合的方法,例如:hasWhip()(加奶泡)

4、万一顾客想要双倍摩卡咖啡,怎么办?

大师和门徒

大师:我说蚱蜢呀!举例我们上次见面已经有些时日,你对于继承的冥想,可有精进?

门徒:是的,大师。尽管继承威力强大,但是我体会到它并不总是能够实现最优弹性和最好维护的设计

大师:啊!是的,看来你已经有所长进。那么,告诉我,我的门徒,不通过继承又能如何达到复用呢?

门徒:大师,我们已经了解到利用组合(composition)和委托(delegation)可以在运行时具有继承行为的效果

大师:好,好,继续... ...

门徒:利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展

大师:很好,蚱蜢,你已经开始看到组合得为例了

门徒:是的,我可以利用此技巧把多个新职责,甚至是设计超类时还没有想到的职责加在对象上。而且,可以不用修改原来的代码

大师:利用组合维护代码,你认为效果如何?

门徒:这正是我要说的。通过动态地组合对象,可以写新的代码添加新功能,而无需修改现有代码。既然没有改变现有代码,那么引进bug或产生以外副作用的机会将大幅度减少

大师:非常好。蚱蜢今天的谈话就到这里。希望你能在这个主题上更深入... ... 牢记,代码应该如同晚霞中的睡莲一样地关闭(免于改变),如同晨曦中的莲花一样地开放(能够扩展)

开放——关闭原则

1、设计原则——类应该对扩展开放,对修改关闭

2、请进,现在"开放"中,欢迎用任何你想要的行为来扩展我们的类。如果你的需要或需求有所改变(我们知道这一定会发生的),那就来吧!动手扩展吧!

3、抱歉,现在是"关闭"状态。没错。我们花了许多时间得到了正确的代码,还解决了所有的bug,所以不能让你修改现有的代码。我们必须关闭代码以防止被修改。如果你不喜欢,可以找经理谈

4、我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。如能实现这样的目标,有什么好处呢?这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求

对扩展开放,对修改关闭?听起来很矛盾。设计如何兼顾两者?

1、这是一个很好的问题,咋听之下,的确感到矛盾,毕竟,越难修改的事物,就越难以扩展,不是吗?

2、但是,有些聪明的OO技巧,允许系统在不修改代码的情况下,进行功能扩展。想想观察者模式,通过加入新的观察者,我们可以在任何时候扩展Subject(主题),而且不需向主题中添加代码。以后,你还会陆续看到更多的扩展行为的其他OO设计技巧

好吧!我了解观察者模式,但是该如何将某件东西设计成可以扩展,又禁止修改?

许多模式是长期经验的实证,可通过提供扩展的方法来保护代码免于被修改。

装饰者模式就是遵循开放——关闭原则的一个很好的例子

我如何让设计的每个部分都遵循开放——关闭原则?

通常,你办不到。要让OO设计同时具备开放性和关闭性,又不修改现有的代码,需要花费许多时间和努力。一般来说,我们实在没有闲工夫把设计的每个部分都这么设计(而且,就算做得到,也可能只是一种浪费)。遵循开放——关闭原则,通常会引入新的抽象层次,增加代码的复杂度。你需要把注意力集中在设计中最有可能改变的地方,然后应用开放——关闭原则

我怎么知道,哪些地方的改变是更重要呢?

这牵涉到设计OO系统的经验,和对你工作领域的了解。多看一些其他的例子可以帮你学习如何辨别设计中的变化区

总结

虽然似乎有点矛盾,但是的确有些技术可以允许在不直接修改代码的情况下对其进行扩展

在选择需要被扩展的代码部分时要小心。每个地方都采用开放——关闭原则时一种浪费,也没必要,还会导致代码变得复杂且难以理解

认识装饰者模式

1、装饰者和被装饰对象有相同的超类型

2、你可以用一个或多个装饰者包装一个对象

3、既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它

4、装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的

5、对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象

定义装饰者模式

装饰者模式——动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案

1、每个组件都可以单独使用,或者被装饰器包起来使用

2、ConcreteComponent是我们将要动态地加上新行为的对象,它扩展自Component

3、每个装饰者都"有一个"(包装一个)组件,也就是说,装饰者有一个实例变量以保存某个Component的引用

4、装饰者功能共同实现的接口(也可以是抽象类)

5、ConcreteDecorator有一个实例变量,可以记录所装饰的事物(装饰者包着的Component)

6、装饰者可以扩展Component的状态

7、装饰者可以加上新的方法。就行为是通过在旧行为前面或后面做一些计算来添加的

装饰我们的饮料

哎呀!我有点混淆... ..我原以为在这个模式中不会使用继承,而是要利用组合取代继承

Sue:这话怎么说?

Mary:看看类图。CondimentDecorator扩展自Beverage类,这用到了继承,不是吗?

Sue:的确是如此,但我认为,这么做的重点在于,装饰者和被装饰者必须是一样的类型,也就是有共同的超类,这是相当关键的地方。在这里,我们利用继承达到“类型匹配”,而不是利用继承获得“行为”

Mary:我知道为何装饰者需要和被装饰者(即被包装的组件)有相同的“接口”,因为装饰者必须能取代被装饰者,但是行为又是从哪里来的?

Sue:当我们将装饰者与组件组合时,就是在加入新的行为。所得到的新行为,并不是继承自超类,而是由组合对象的来的

Mary:好的。继承Beverage抽象类,是为了有正确的类型,而不是继承它的行为。行为来自装饰者和基础组件,或与其他装饰者之间的组合关系

Sue:正是如此

Mary:哦!我明白了。而且因为使用对象组合,可以把所有饮料和调料更有弹性地加以混合与匹配,非常方便

Sue:是的。如果依赖继承,那么类得行为只能在编译时静态决定。换句话说,行如果不是来自超类,就是子类覆盖后的版本。反之,利用组合,可以把装饰者混合着用... ...而且是在"运行时"

Mary:而且,如我所理解的,我们可以在任何时候,实现新的装饰者增加新的行为。如果依赖继承,每当需要新行为时,还的修改现有的代码

Sue:的确如此

Mary:我还剩下一个问题,如果我们需要继承的是component类型,为什么不Beverage类设计成一个接口,而是设计成一个抽象类呢?

Sue:关于这个嘛,还记得吗?当初我们从星巴兹拿到这个程序时,Beverage已经是一个抽象类了。通常装饰者模式是采用抽象类,但是在Java中可以使用接口。尽管如此,通常我们都努力避免修改现有的代码,所以,如果抽象类运作的很好,还是别去修改它

装饰者的告白

HeadFirst:欢迎装饰者模式,听说你最近情绪有点差?

装饰者:是的,我知道大家都认为我是一个有魅力的设计模式,但是,你知道吗?我也有自己的困扰,就和大家一样

HeadFirst:愿意让我们分担一些你的困扰吗?

装饰者:当然可以。你知道我有能力为设计注入弹性,这是毋庸置疑的,但是我也有“黑暗面”。有时候我会在设计中加入大量的小类,这偶尔会导致别人不同意了解我的设计方式

HeadFirst:你能够举个例子吗?

装饰者:以Java I/O库来说,人们第一次接触到这个库时,往往无法轻易地理解它。但是如果他们能认识到这些类都是用来包装InputStream的,一切都会变得简单多了

HeadFirst:听起来并不严重。你还是一个很好的模式,只需要一点点的教育,让大家知道怎么用,问题就解决了

装饰者:恐怕不止这些,我还有类型问题。有些时候,人们在客户代码中依赖某种特殊类型,然后忽然导入装饰者,却又没有周详地考虑一切。现在,我的一个优点是,你通常可以透明地插入装饰者,客户程序甚至不需要知道它是在和装饰者打交道。但是,如我刚刚所说的,有些代码会依赖特定的类型,而这样的代码一导入装饰者,嘭!出状况了!

HeadFirst:这个嘛,我相信每个人都必须了解到,在插入装饰者时,必须要小心谨慎。我不认为这是你的错!

装饰者:我知道,我也试着不这么想。我还有一个问题,就是采用装饰者在实例化组件时,将增加代码的复杂度。一旦使用装饰者模式,不只需要实例化组件,还要把此组件包装进装饰者中,天晓得有几个

HeadFirst:我下周会访谈工厂(Factory)模式和生成器(Builder)模式,我听说他们对这个问题有很大的帮助

装饰者:那倒是真的。我应该常和这些家伙聊聊

HeadFirst:我们都认为你是一个好的模式,适合用来建立有弹性的设计,维持开放——关闭原则,你要开心一点,别负面思考

装饰者:我尽量把。谢谢你

再看OO原则

1、封装变化

2、多用组合,少用继承

3、针对接口编程,不针对实现编程

4、为交互对象之间的松耦合设计而努力

5、对扩展开放,对修改关闭

 

装饰者模式定义

动态地将责任附加到对象上。想要扩展功能,装饰者提供有别于继承的另一种选择

要点

1、继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式

2、在我们的设计中,应该允许行为可以被扩展,而无须修改现有的代码

3、组合和委托可用于在运行时动态地加上新的行为

4、除了继承,装饰者模式也可以让我们扩展行为

5、装饰者模式意味着一群装饰者类,这些类用来包装具体组件

6、装饰者类反应出被装饰的组件类型(事实上,他们具有相同的类型,都经过接口或继承实现)

7、装饰者可以在被装饰者的行为前面与/后后面加上自己的行为,甚至将被装饰者的额行为整个取代掉,而达到特定的目的

8、你可以用无数个装饰者包装一个组件

9、装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型

10、装饰者会导致设计中出现许多小对象,如果过度使用,会让你的程序变得很复杂

时间: 2024-10-10 08:41:57

Head first设计模式(3)的相关文章

Java设计模式——创建型模式之单例模式

一.概述 作为第一个出场的设计模式,有必要先介绍一下设计模式(引用自百度百科): 设计模式(Design Pattern)是一套被反复使用.多数人知晓的.经过分类的.代码设计经验的总结. 使用设计模式的目的:为了代码可重用性.让代码更容易被他人理解.保证代码可靠性. 设计模式使代码编写真正工程化:设计模式是软件工程的基石脉络,如同大厦的结构一样. 设计模式概念的介绍,参见:http://blog.jobbole.com/101076/ 其中涉及的设计原则的概念,参见随笔:http://www.c

10大APP界面框架设计模式详解

随着移动互联网的发展,移动app已经成为了每个互联网公司的标配了,那作为产品经理,我们如何设计出更加符合用户体验的app产品呢?今天和大家分享的就是10中最常见的app界面光甲设计模式,一起来看看吧. 1.标签导航 标签导航是十大界面框架设计里最常用的界面框架设计,也是被业界之内公认的一种普遍使用的页面框架设计.那么这种页面框架设计在作业方面对一个用户来说也是最常见的一种页面框架设计,比如说微博.微信.手机百度.支付宝.淘宝,这些我们所谓的超级APP都是运用的标签导航,无一例外.从这个角度也可以

[js高手之路]设计模式系列课程-组合模式+寄生组合继承实战新闻列表

所谓组合模式,就是把一堆结构分解出来,组成在一起,现实中很多这样的例子,如: 1.肯德基套餐就是一种组合模式, 比如鸡腿堡套餐,一般是是由一个鸡腿堡,一包薯条,一杯可乐等组成的 2.组装的台式机同理,由主板,电源,内存条,显卡, 机箱,显示器,外设等组成的 把一个成型的产品组成部件,分成一个个独立的部件,这种方式可以做出很多灵活的产品,这就是组合模式的优势 比如:家用台式机电脑,要求配置比较低, 这个时候只需要主板+电源+内存条+机箱+显示器+外设就可以了,不需要配置独立显卡 鸡腿堡+鸡翅+紫薯

Happy 设计模式之适配器模式(JAVA)

设计模式-适配器模式 适配器模式定义 适配器模式,将一个类的的接口转换成客户或者产品希望的接口形式,就是原本不兼容或者甚至不相干的接口不能一起工作的接口一起工作,完成需求或者客户的需求. 适配器模式的使用场景 1.当你使用一个已经存在的类,而他的接口不符合你的需求. 2.你想要创建一个复用的类,该类可以与其相关的类或者不可见的类协同工作. 适配器角色 Target:目标接口 TargetImpl:目标实现类 Adapter:适配器 Adaptee:被适配者 代码解析: package com.d

设计模式 2/23 工厂模式

工厂模式是最常用的设计模式之一,用好了,代码优雅,可维护性高,对系统设计会上一个台阶 为什么这么说,因为工厂模式可以牵扯出抽象工厂模式,也有大家都会聊到的简单工厂模式 我们要了解一点,23中设计模式中,不包含简单工厂模式,之所以大家会去聊这个,四个字,渐进明细 通过对简单工厂模式的了解,我们引入工厂这个词,不然一个写代码的,天天给他讲工厂,工厂,工厂,西厂,东厂,会晕 同时,通过逐步的深入,从简单工厂,到工厂模式,再到抽象工厂,渐进明细的过程,逐步深入的理解,比较优劣,择优而为我们所用. 试想我

Java设计模式学习笔记,一:单例模式

开始学习Java的设计模式,因为做了很多年C语言,所以语言基础的学习很快,但是面向过程向面向对象的编程思想的转变还是需要耗费很多的代码量的.所有希望通过设计模式的学习,能更深入的学习. 把学习过程中的笔记,记录下来,只记干货. 第一部分:单例模式的内容 单例模式:类只能有一个实例. 类的特点:1.私有构造器:2.内部构造实例对象:3.对外提供获取唯一实例的public方法. 常见的单例模式实现有五种形式: 1.饿汉式. 2.懒汉式. 3.双重检查锁式. 4.静态内部类式. 5.枚举式. 以下分别

设计模式之单列模式

设计模式之单列模式 1,何为单列模式? 即singleton 在某个类采用了单列模式之后  其只能有一个实列对象 ,并且这个实列对象只能有内部自己创建并提供给外部的调用. 2.实现单列模式的方法 分为 :饿汉式 ,懒汉式 下面为饿汉式实现代码: public calss Singleton1{ //将构造函数私有化 防止外部通过new来创建对象 private Singleton1(){ } //创建一个私有静态变量并直接初始化 类加载的时候直接创建对象 private static Singl

设计模式之原型模式(Prototype)

1.初识原型模式 大家都知道连锁机构是现在灰常流行的商业模式,比如咖啡之翼,那么假设咖啡之翼要在长春新建立一个分店,所经营的产品和以前在其他的城市已经存在的店经营的产品差不多,那么面向对象开发的角度怎么解决这个问题呢?难道要重新的实例化一个咖啡之翼的店??这显然不太好吧,咖啡之翼里面经营的产品(假设是属性吧)都需要重新写,这就是在做大量的重复工作啊,这显然是不符合OO开发思想的.遇到这样的情况,并不是重新建立一个类来解决这样的问题,而是通过设计模式中的"原型模式"来解决这种问题.是这种

对设计模式的总结之简单工厂与策略模式

前言 面向对象编程追求的本质-提高扩展性.可维护性.灵活性和复用性.合理利用面向对象6个原则,能够很好的达到要求.如何利用好就是至关重要的了,前人总结了23+个设计模式能够让初学者更容易学到其中的精髓,本文就说说我对本人对简单工厂模式.策略模式的见解. 简单工厂模式与策略模式 简单工厂模式 工作中,常常遇到需要做一个功能(鸭子),这个功能中含有可控个数的子操作功能(鸭子叫,鸭子跑,鸭子飞),而且子功能在不同的情况下处理方式又不相同(成年鸭子/小鸭子叫,成年鸭子/小鸭子跑,成年鸭子/小鸭子飞).我

设计模式——介绍与工厂模式(扁平管理模式VS职业经理人模式)

本文主要对设计模式进行大概解说.特别是对工厂模式进行简明的解析: 一.设计模式的分类 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元模式. 行为型模式,共十一种:策略模式.模板方法模式.观察者模式.迭代子模式.责任链模式.命令模式.备忘录模式.状态模式.訪问者模式.中介者模式.解释器模式. 事实上还有两类:并发型模式和线程池模式. 二.设计模式的六大原则 1.开闭原则(Op