当前位置: 首页 > news >正文

【HeadFirst 设计模式学习笔记】3 装饰模式

1.这个模式可以称为“给爱用继承的人一个全新的设计眼界”的模式。牵扯到第五个设计原则:“类应该对扩展开放,而对修改封闭”。但是要注意,遵循这一标准会带来更多层次上的抽象,增加代码的复杂度,所以并不是所有类都要这样设计。

2.文中举了一个为辛巴克咖啡馆写一个计算咖啡价格+调料价格的类,使用了装饰模式——动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更加有弹性的替代方案。我们就拿这个计算咖啡价格的东西举例子。

3.在原来的设计中,都是继承于Beverage这个超类中,多一项咖啡+调料组合就多一个子类,最后造成类的爆炸。而使用装饰模式,我们希望用装饰器(这里的调料)一层层的包含被装饰的咖啡,最后达到通过调用最外层的装饰者的cost()方法就可以委托其内部计算计算价钱。我们针对这个目标,从代表饮品的Beverage类下手,这是一个基类,代表一个逻辑上的抽象(但不一定要是抽象类,看是否有抽象方法来定),被装饰者和装饰者都使用该基类,在这个基类中定义了装饰者和被装饰者需要的方法,其中抽象方法是装饰者和被装饰者同时都需要的方法,而普通实现的方法则是装饰者覆盖使用的方法

public abstract class Beverage { 
    String description = "Unknown Beverage"; 
    public String getDescription() { 
        return description; 
    } 
    public abstract double cost(); 
}

我们将调料视为装饰器,这个装饰器超类为了能够将要被装饰的部分包起来,所以要继承自饮品这个超类:

public abstract class CondimentDecorator extends Beverage 
    public abstract String getDescription(); 
}

现在我们就构造一些饮品(被装饰者

public class Espresso extends Beverage { 
    public Espresso() { 
        description = "Espresso";//这个变量是继承而来 
    } 
    public double cost() { 
        return 1.99; 
    } 
}

public class HouseBlend extends Beverage { 
    public HouseBlend() { 
        description = "House Blend Coffee"; 
    } 
    public double cost() { 
        return .89; 
    } 
}

 

然后我们构建具体的装饰者,比如代表摩卡的Mocha类:

public class Mocha extends CondimentDecorator { 
    Beverage beverage;
//用一个实例变量记录饮品,然后通过构造函数将饮品记录在实例变量中完成装饰的过程。 
    public Mocha(Beverage beverage) { 
        this.beverage = beverage; 
    } 
    public String getDescription() {//这两个方法都调用了被装饰部分的相关方法, 
        return beverage.getDescription() + ", Mocha"; 
    } 
    public double cost() { 
        return .20 + beverage.cost(); 
    } 
}

现在我们测试一下这些代码,喝杯比较多样化的咖啡:

public class StarbuzzCoffee { 
    public static void main(String args[]) { 
        Beverage beverage = new Espresso(); 
        System.out.println(beverage.getDescription() 
                + " $" + beverage.cost()); 
        Beverage beverage3 = new HouseBlend(); 
       beverage3 = new Soy(beverage3);
//一直针对抽象组建类型编程 
        beverage3 = new Mocha(beverage3); 
        beverage3 = new Whip(beverage3); 
        System.out.println(beverage3.getDescription() 
                + " $" + beverage3.cost()); 
    } 
}

4.总结一下装饰模式特点:

  • 装饰者和被装饰对象有相同的基类--都是来自Beverage这个类。
  • 继承关系:基类->被装饰者(就是具体的一些基类延伸),基类->装饰器->装饰者(传入基类)-对应上面的代码。
  • 你可以用一个或多个装饰者包装一个对象--看看beverage3这个对象就知道了。
  • 在任何需要被包装者的场合可以用装饰过的对象代替它--比如首先我们在咖啡上加豆浆,然后我们在加豆浆的咖啡上想再加摩卡的话,我们可以直接在这个加过豆浆的咖啡对象上加摩卡。
  • 装饰者可以在所委托的被装饰者的行为上加上自己的行为,达到特定目的--getDescription和cost方法充分证明了这一点。

5.JDK中的装饰模式

最典型的就是IO系统了,比如BufferedInputStream及LineNumberInputStream都扩展自FilterInputStream——这个类是一个抽象的装饰类。而最高的抽象组件是InputStream类。

我们也可以以假乱真写一个输入流类

public class LowerCaseInputStream extends FilterInputStream {

    public LowerCaseInputStream(InputStream in) { 
        super(in); 
    } 
    public int read() throws IOException { 
        int c = super.read(); 
        return (c == -1 ? c : Character.toLowerCase((char)c)); 
    } 
    public int read(byte[] b, int offset, int len) throws IOException { 
        int result = super.read(b, offset, len); 
        for (int i = offset; i < offset+result; i++) { 
            b[i] = (byte)Character.toLowerCase((char)b[i]); 
        } 
        return result; 
    } 
}

public class InputTest { 
    public static void main(String[] args) throws IOException { 
        int c;

        try { 
            InputStream in = 
                new LowerCaseInputStream( 
                    new BufferedInputStream( 
                        new FileInputStream("test.txt")));

            while((c = in.read()) >= 0) { 
                System.out.print((char)c); 
            }

            in.close(); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
}

6.装饰模式的一些缺陷:

产生各种小类,维护不便。有些代码会依赖特定的类型,而这样的代码一导入装饰者就出问题了。

 

总结:

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

FAQ:

为什么在一定要有Decorator这个类?

基于上边的这个例子我们可以看到Decorator这个类隔离了Component中可能出现的具体实现(比如上例中的getDescription方法),之所以要隔离室因为装饰者对这个方法的实现逻辑和高层的Component类实现的逻辑是不同的。在Decorator将一个方法退化为一个抽象方法,有助于督促具体的装饰器必须实现这个方法,而不会无意使用Component继承而来的方法(如果没有Decorator而直接继承自Component可能会因为实现逻辑不同而出现没有实现新逻辑而误用旧逻辑的情况,这会导致出现运行中错误,而退化为抽象方法后,具体装饰器则不得不实现这个方法,否则不可能编译通过,这就将错误压制在了编译阶段,显然,这相对于运行中出错是要好得多)。




本文转自gnuhpc博客园博客,原文链接http://www.cnblogs.com/gnuhpc/archive/2012/12/17/2822401.html,如需转载请自行联系原作者

相关文章:

  • Windows Phone 7 学习笔记2:感应设备的方向
  • ssh 断开处理
  • 关于JavaScript 的事件[下]
  • table-layout
  • 双系统引导故障排除
  • python 守护进程(daemon)
  • Gradle与Makefile构建工具的对比
  • 如何读取Access里的OLE类型的图片
  • 让全世界的中小企业都可以用上的全方位上网行为管理系统。
  • 20171127-构建之法:现代软件工程-阅读笔记
  • Mysql实现企业级主从复制和互为主从模式架构
  • topcoder srm 495 div1
  • sbt编译spark程序提示value toDF is not a member of Seq()
  • 基于DotNet构件技术的企业级敏捷软件开发平台 - AgileEAS.NET平台开发指南 - 开发流程...
  • 文本处理工具
  • 《微软的软件测试之道》成书始末、出版宣告、补充致谢名单及相关信息
  • 【跃迁之路】【641天】程序员高效学习方法论探索系列(实验阶段398-2018.11.14)...
  • 【跃迁之路】【733天】程序员高效学习方法论探索系列(实验阶段490-2019.2.23)...
  • ➹使用webpack配置多页面应用(MPA)
  • 2017前端实习生面试总结
  • Android单元测试 - 几个重要问题
  • css选择器
  • ECS应用管理最佳实践
  • emacs初体验
  • IP路由与转发
  • Java反射-动态类加载和重新加载
  • JSDuck 与 AngularJS 融合技巧
  • js中的正则表达式入门
  • Octave 入门
  • 第2章 网络文档
  • 动态魔术使用DBMS_SQL
  • 如何在 Tornado 中实现 Middleware
  • 使用API自动生成工具优化前端工作流
  • 推荐一款sublime text 3 支持JSX和es201x 代码格式化的插件
  • 消息队列系列二(IOT中消息队列的应用)
  • 延迟脚本的方式
  • 再谈express与koa的对比
  • 在weex里面使用chart图表
  • # 飞书APP集成平台-数字化落地
  • #我与Java虚拟机的故事#连载16:打开Java世界大门的钥匙
  • $GOPATH/go.mod exists but should not goland
  • (4) PIVOT 和 UPIVOT 的使用
  • (附程序)AD采集中的10种经典软件滤波程序优缺点分析
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (简单) HDU 2612 Find a way,BFS。
  • (一)appium-desktop定位元素原理
  • (一)VirtualBox安装增强功能
  • (原創) 如何優化ThinkPad X61開機速度? (NB) (ThinkPad) (X61) (OS) (Windows)
  • (转)Oracle存储过程编写经验和优化措施
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • ***linux下安装xampp,XAMPP目录结构(阿里云安装xampp)
  • .NET C#版本和.NET版本以及VS版本的对应关系
  • .NET Core WebAPI中使用swagger版本控制,添加注释
  • .NET Standard、.NET Framework 、.NET Core三者的关系与区别?
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...