Head First Design Mode(4)-装饰者模式

该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!

 

装饰者模式:

    装饰对象;本章可以称为“给爱用继承的人一个全新的设计眼界”;

    讨论典型的继承滥用问题;

    使用对象组合的方式,做到在运行时装饰类;

    一旦熟悉了装饰技巧,能在不修改底层代码的情况下,给对象赋予新的职责;

 

星巴克咖啡准本更新订单系统,以满足饮料的供应

 

类设计:

    Beverage(饮料)是一个抽象类,店内饮品均需继承此类;

    cost()是抽象方法,子类定义具体的实现,返回饮料的价钱;

    description实例变量 用来描述饮料;

 

 

场景:

    购买咖啡的时候,需要加入各种配料,如蒸奶、豆浆、摩卡… 我们简单表示为Burden1 Burden2…

    星巴克会根据锁加入的调料收取不同的费用;

 

一种尝试:

    创建新类表示加入配料后的饮料,继承自Beverage;

    如,Coffee1_Burden1 该类重写cost方法,计算出加上配料后的价钱;

 

可预见的问题:

    会导致很多类,真的很多;

    如果想修改某一调料的价格,或是新增一种调料,和已有的各个类搭配出新类,那简直就是一场灾难;

    同时这样也违反了我们之前的设计原则,而且很严重;

 

另一种尝试:

    将各种调料的有无,在超类中通过实例变量来标识;

    如果指定的配料存在,那么实例变量如hasMilk返回TRUE,依照此状态,在Beverage中的cost方法里,计算相应的价格;

    这些表示是否有配料的状态都有各自的set方法进行值得设置;

 

仍然存在的问题:

    调整价钱,必须修改现有的代码;

    一旦出现了新的配料,不得不加上新的方法,且需改变超类的cost()方法;

    如果加入新的Coffee类(新饮品),但是这个饮品并不适合加milk,那么这个类就继承了不可使的hasMilk()和setMilk()方法;

    如果想要Double milk,该怎么办…

 

大师指路:

    组合(composition)和委托(delegation)可以在运行时具有继承行为的效果;

    利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为;

    然而,如果能用组合的做法扩展对象的习惯那位,就可以在运行时动态的进行扩展;

    通过动态的组合,可以写新的代码,加入新的功能,而无需改变现有代码;可维护性提高,产生副作用的机会也将减少;

 

开放-关闭原则:

    类应该对扩展开放,对修改关闭;

 

这,最重要的设计原则之一;

    我们的目标是允许类容易扩展,在不修改代码的情况下,加入新的行为;

    这样的设计具有弹性应对改变,可以接受新的功能来应对改变的需求;

 

这听起来,可能感觉矛盾,但一些聪明的OO技巧,允许系统在不修改代码的情况下,进行功能扩展:

    比如观察者模式,通过新加入的观察者,可以在任何时候扩展主题,而不需要在主题中加入代码;

    还有很多经验的实证,可通过提供扩展的方法来保护代码免于被修改,如即将介绍的装饰者模式;

 

关于开-闭原则的使用:

    遵循开放-关闭原则,通常会引入新的抽象层次,增加代码的复杂度,而且难理解;

    所以需要把注意力集中在设计中最有可能改变的地方,然后再使用该原则;

 

认识装饰者模式:

    星巴克的问题:类数量爆炸、设计死板、以及新类加入的功能并不适用于所有的子类;

    不一样的做法:以饮料为主体,在运行时以调料来“装饰”(decorate)饮料;

 

加入Burden1和Burden2的Coffee1:

    取一个Coffee1对象;以Burden1装饰它;再以Burden2装饰它;调用cost()方法,并依赖委托将调料价钱加上去;

    

把装饰者对象当成“包装者”:

    Burden虽然是装饰者,但是他的类型与它所装饰的对象类型需一致;

    即Burden也有一个cost()方法,通过多态,经Burden包装过的Beverage(Coffee)依然是Beverage;

    在cost()方法中,调用最外层的装饰者的cost方法,会先委托他装饰的对象调用其cost()方法,再加上当前装饰者需要加上的价格;

 

 

已经认识到的装饰者模式:

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

    可以用一个或多个装饰者包装一个对象;

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

    对象可以在任何时候被装饰,所以可以在运行时动态的、不限量的使用需要和装饰者来装饰对象;

 

定义装饰者模式:

    动态地将责任附加到对象上;

    若要扩展功能,装饰者提供了比继承更有弹性的替代方案;

 

装饰者模式类图:

    ConcreteComponent扩展字Component;

    ConcreteDecorator有一个实例变量,可以记录所装饰的事物;

    Decorator在这里是抽象类,也可以是装饰者共同实现的接口;

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

 

 

装饰者模式中,我们使用了继承达到“类型匹配”的目的,而不是利用继承获得“行为”;

    我们的装饰者和组件组合时,就可以加上新的行为,这些行为是由对象组合来的,而不是继承自超类;

    换句话说,行为来自装饰者和基础组件,或与其他装饰者之间的组合关系;

    

 

装饰我们的饮料:

Beverage抽象类:

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

Decorator抽象类:

public abstract class Decorator extends Beverage{
    public abstract String getDescription();
}

Beverage具体类:Coffee1 Coffee2

public class Coffee1 extends Beverage{
    public Coffee1(){
        description = "Coffee1";
    }
    
    public double cost(){
        return 5;
    }
}

public class Coffee2 extends Beverage{
    public Coffee2(){
        description = "Coffee2";
    }
    
    public double cost(){
        return 5.5;
    }
}

Decorator具体类:Burden1 Burden2

public class Burden1 extends Decorator{
    
    Beverage beverage;
    
    public Burden1(Beverage beverage){
        this.beverage = beverage;
    }
    
    public String getDescription(){
        return beverage.getDescription() + "_Burden1";
    }
    
    public double cost(){
        return 4 + beverage.cost();
    }
}

public class Burden2 extends Decorator{
    
    Beverage beverage;
    
    public Burden2(Beverage beverage){
        this.beverage = beverage;
    }
    
    public String getDescription(){
        return beverage.getDescription() + "_Burden2";
    }
    
    public double cost(){
        return 3 + beverage.cost();
    }
}

DriverDecorator程序入口类:

public class DriveDecorator{
    
    public static void main(String[] args){
        Beverage beverage1 = new Coffee1();
        Beverage beverage2 = new Coffee2();
 
        System.out.println(beverage1.getDescription() + "$" + beverage1.cost());
        System.out.println(beverage2.getDescription() + "$" + beverage2.cost());
        
        beverage2 = new Burden1(beverage2);
        System.out.println(beverage2.getDescription() + "$" + beverage2.cost());

        beverage2 = new Burden2(beverage2);
        System.out.println(beverage2.getDescription() + "$" + beverage2.cost());
    }

}

 

编译运行程序:

bogon:第三章装饰者模式 huaqiang$ javac *.java
bogon:第三章装饰者模式 huaqiang$ java DriveDecorator
Coffee1$5.0
Coffee2$5.5
Coffee2_Burden1$9.5
Coffee2_Burden1_Burden2$12.5

装饰者该做的事,就是增加行为到被包装对象上;

 

思考:在菜单上增加咖啡的容量大小,小杯(tall)中杯(grande)大杯(venti);

 

真实世界的装饰者:Java I/O

    java.io包内的类很多,很多;

    如果你知道了装饰者模式,就很好理解了,因为其中很多了都是装饰者;

 

Java I/O类图:    

    LineNumberInputStream是一个具体的装饰者,它加上了计算行数的能力;

    BufferedInputStream是一个具体的装饰者,可以利用缓冲输入来改进性能,用readLine()方法来增强接口;

    FileInputStream是被装饰的“组件”,Java I/O组件类都提供了最基本的字节读取功能;

    FilterInputStream相当于一个抽象的装饰类(实际在文档中这是一个具体的类,并不是一个抽象类);

java中输入流/输出流的设计方式也类似(Reader/Writer流);

 

装饰者模式会引入一个缺点:

    利用装饰者模式,尝尝造成设计中有大量的小类,数量会很多;

    因此,装饰者通常是用类似工厂(Factory)或生成器(Builder)这样的模式创建的,他们会封装的很好,方便使用;

 

编写自己的Java I/O装饰者:

    编写一个装饰者,把输入流内的所有大写字符转成小写;

test.txt文本:Hello World!

LowerCaseInputStream:

import java.io.*;

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;
    }
}

DriverInoutText:

import java.io.*;

public class DriverInoutText{
    
    public static void main(String[] args) throws IOException{
        int c = 0;
        
        try{
            InputStream in = new BufferedInputStream(new FileInputStream("test.txt"));
            
            in = new LowerCaseInputStream(in);
            
            while((c = in.read()) >= 0){
                System.out.print((char)c);
            }
            
            in.close();
            
        }catch(IOException e){
            System.out.println((char)c);
        }
        System.out.println("");

    }
    
    
}

 

编译运行程序:

hello world!bogon:JavaIO huaqiang$ javac *.java
bogon:JavaIO huaqiang$ java DriverInoutText
hello world!

 

总结:

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

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

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

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

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

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

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

8.可以用无数个装饰者包装一个组件;

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

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

 

OO基础:

    抽象;

    封装

    继承;

    多态;

OO原则:

    封装变化

    多用组合,少用继承

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

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

    ——对扩展开放,对修改关闭;

OO模式:

    策略模式:定义算法族,分别封装起来,让他们之间互相替换,此模式让算法的变化独立于使用算法的客户;

    观察者模式:在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新;

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

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值