装饰者模式

1. 装饰者模式
作用:“给爱用继承的人一个全新的设计眼界”,即解决继承滥用,能够在不修改任何底层代码的情况下,给你的(或别人的)对象赋予新的职责。
解决问题:类数量爆炸(指继承)、设计死板、基类加入新功能并不适用于所有子类。


2. 符合设计原则:开放-关闭原则
定义:类应该对扩展开放,对修改关闭。即在不修改现有代码的情况下,允许类的扩展。
说明:过度使用开放-关闭原则(选择需要被扩展的代码部分时)要小心,每个地方都使用该原则是一种浪费,因为该原则通常会引入新的抽象层次,导致代码变得复杂而难以理解。


3. 解释
(1)装饰者和被装饰者对象有相同的超类型,通常装饰者模式采用的是抽象类;
(2)你可以用一个或多个装饰者包装一个对象;
(3)既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)场合,可以用装饰过的对象代替它;
(4)装饰者可以在所委托被装饰者的行为之前/或之后,加上自己的行为,以达到特定的目的;
(5)对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象;


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




5. 例子
(1)将调料作为装饰者,将饮料作为被装饰对象;
(2)一种饮料可以填加多种调料,即可以被多种调料装饰;
(3)他们拥有共同的超类,即他们必须是一样的类型,这样好利用继承达到“类型匹配”,注意这里不是利用继承获得“行为”;


代码:

定义抽象组件-饮料类:

package com.example.test;

/**
 * <p>
 * <code>Beverage</code>
 * </p>
 * Description:饮料基类
 * (1)Beverage是一个抽象类,有两个方法:getDescription()及cost();
 * (2)getDescription()已经在此实现了,但是cost()必须在子类中实现;
 *
 * @author Mcchu
 * @date 2018/1/16 9:24
 */
public abstract class Beverage {
    String description = "Unknown Beverage";

    public String getDescription() {
        return description;
    }
    
    public abstract double cost();
}

定义调料抽象类,也就是装饰者类:

package com.example.test;

/**
 * <p>
 * <code>Condiment</code>
 * </p>
 * Description:调料装饰者类
 * (1)首先,必须让Condiment Decorator能够取代Beverage,所以将Condiment Decorator扩展自Beverage类;
 * (2)所有的调料装饰者都必须重新实现getDescription()方法;
 *
 * @author Mcchu
 * @date 2018/1/16 9:29
 */
public abstract class CondimentDecorator extends Beverage{

    public abstract String getDescription();
}

定义具体组件-饮料类:

浓缩咖啡

package com.example.test;

/**
 * <p>
 * <code>Espresso</code>
 * </p>
 * Description:具体饮料-浓缩咖啡
 * (1)Espresso类扩展自Beverage类,因为Espresso是饮料的一种
 * (2)构造器是为了设置饮料的描述,description实例变量继承自Beverage
 * (3)cost()方法不需要管调料的价钱,直接把Espresso的价格返回
 * 
 * @author Mcchu
 * @date 2018/1/16 9:36
 */
public class Espresso extends Beverage {
    
    public Espresso(){
        description = "Espresso";
    }
    
    @Override
    public double cost() {
        return 1.99;
    }
}

混合咖啡

package com.example.test;

/**
 * <p>
 * <code>HouseBlend</code>
 * </p>
 * Description:具体饮料-混合咖啡
 * (1)这里做法和浓缩咖啡类相似
 *
 * @author Mcchu
 * @date 2018/1/16 9:41
 */
public class HouseBlend extends Beverage {

    public HouseBlend(){
        description = "House Blend Coffee";
    }

    @Override
    public double cost() {
        return .89;
    }
}

下面我们定义具体的装饰者类:

package com.example.test;

/**
 * <p>
 * <code>Mocha</code>
 * </p>
 * Description:
 * (1)具体装饰者类-摩卡调料
 * (2)摩卡调料是一个装饰者类,扩展自CondimentDecorator,而CondimentDecorator扩展自Beverage类
 *
 * @author Mcchu
 * @date 2018/1/16 9:46
 */
public class Mocha extends CondimentDecorator {

    /**
     * 用一个实例变量记录饮料,也就是被装饰者
     */
    Beverage beverage;

    /**
     * 想办法让被装饰者(饮料)被记录到实例变量中
     * 这里把饮料当作构造器的参数,再由构造器将此饮料记录在实例变量中
     * 
     * @param beverage 被装饰者
     */
    public Mocha( Beverage beverage ){
        this.beverage = beverage;
    }

    /**
     * 我们希望叙述不只是描述饮料,而是完整的连调料也描述出来
     * 这里首先利用委托的做法,得到一个饮料的叙述,然后在其后加上附加的叙述
     * 
     * @return 饮料描述+附加的调料描述
     */
    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Mocha";
    }

    /**
     * 计算带摩卡调料的饮料价钱
     * 这里先把调用委托给被装饰对象,以计算价钱,然后加上摩卡的价钱,得到最后结果
     * 
     * @return 摩卡调料价钱+被装饰对象的价钱
     */
    @Override
    public double cost() {
        return .20 + beverage.cost();
    }
}

同理,定义奶泡调料装饰者类:

package com.example.test;

/**
 * <p>
 * <code>Whip</code>
 * </p>
 * Description:装饰者类-奶油调料
 *
 * @author Mcchu
 * @date 2018/1/16 9:46
 */
public class Whip extends CondimentDecorator {

    /**
     * 用一个实例变量记录饮料,也就是被装饰者
     */
    Beverage beverage;

    /**
     * 想办法让被装饰者(饮料)被记录到实例变量中
     * 这里把饮料当作构造器的参数,再由构造器将此饮料记录在实例变量中
     *
     * @param beverage 被装饰者
     */
    public Whip(Beverage beverage ){
        this.beverage = beverage;
    }

    /**
     * 我们希望叙述不只是描述饮料,而是完整的连调料也描述出来
     * 这里首先利用委托的做法,得到一个饮料的叙述,然后在其后加上附加的叙述
     *
     * @return 饮料描述+附加的调料描述
     */
    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Whip";
    }

    /**
     * 计算带奶泡调料的饮料价钱
     * 这里先把调用委托给被装饰对象,以计算价钱,然后加上奶泡的价钱,得到最后结果
     *
     * @return 奶泡调料价钱+被装饰对象的价钱
     */
    @Override
    public double cost() {
        return .30 + beverage.cost();
    }
}

再定义豆浆装饰者类:

package com.example.test;

/**
 * <p>
 * <code>Soy</code>
 * </p>
 * Description:装饰者类-豆浆
 *
 * @author Mcchu
 * @date 2018/1/16 9:46
 */
public class Soy extends CondimentDecorator {

    /**
     * 用一个实例变量记录饮料,也就是被装饰者
     */
    Beverage beverage;

    /**
     * 想办法让被装饰者(饮料)被记录到实例变量中
     * 这里把饮料当作构造器的参数,再由构造器将此饮料记录在实例变量中
     *
     * @param beverage 被装饰者
     */
    public Soy(Beverage beverage ){
        this.beverage = beverage;
    }

    /**
     * 我们希望叙述不只是描述饮料,而是完整的连调料也描述出来
     * 这里首先利用委托的做法,得到一个饮料的叙述,然后在其后加上附加的叙述
     *
     * @return 饮料描述+附加的调料描述
     */
    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Soy";
    }

    /**
     * 计算带豆浆调料的饮料价钱
     * 这里先把调用委托给被装饰对象,以计算价钱,然后加上豆浆的价钱,得到最后结果
     *
     * @return 豆浆调料价钱+被装饰对象的价钱
     */
    @Override
    public double cost() {
        return .40 + beverage.cost();
    }
}

至此,饮料基类,装饰者基类,具体饮料类(被装饰),具体调料类(装饰者)已定义完毕,如果引入了一种新的饮料或调料,只需增加具体的类即可,下面是测试:

package com.example.test;

/**
 * <p>
 * <code>StarbuzzCoffee</code>
 * </p>
 * Description: 测试类
 *
 * @author Mcchu
 * @date 2018/1/16 10:06
 */
public class StarbuzzCoffee {

    public static void main(String[] args) {
        // 一杯Espresso,不加调料,打印描述与价钱
        Beverage beverage = new Espresso();
        System.out.println( "描述:"+beverage.getDescription() );
        System.out.println( "价钱:"+ "$" + beverage.cost() );

        // 一杯HouseBlend,加两份摩卡,加一份奶泡,再加一份豆浆,打印描述与价钱
        Beverage beverage2 = new HouseBlend();
        beverage2 = new Mocha(beverage2);   //用Mocha装饰HouseBlend
        beverage2 = new Mocha(beverage2);  //再用Mocha装饰HouseBlend
        beverage2 = new Whip(beverage2);  //用奶泡Whip装饰HouseBlend
        beverage2 = new Soy(beverage2);  //用豆浆Soy装饰HouseBlend
        System.out.println( "描述:"+beverage2.getDescription() );
        System.out.println( "价钱:"+ "$" + beverage2.cost() );
    }
}

6. JDK中的装饰者: JAVA I/O

我们看一个类图就明白了,很像上面我们的例子


上面是JDK封装的输入流(input...)设计,其实输出流(output...)的设计也是类似,还有Writer流(作为基于字符数据的输入输出)等。


7. 装饰者模式引出的缺点

利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能会造成使用此API的程序员的困扰。


8. 模拟JDK中JAVA I/O的设计,自己编写一个装饰者,把输入的所有大写字符转小写

package com.example.test;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * <p>
 * <code>LowerCaseInputStream</code>
 * </p>
 * Description: 自定义java i/o装饰者
 * 扩展FilterInputStream类,这是所有InputStream的抽象装饰者
 *
 * @author Mcchu
 * @date 2018/1/16 10:37
 */
public class LowerCaseInputStream extends FilterInputStream {

    /**
     * Creates a <code>FilterInputStream</code>
     * by assigning the  argument <code>in</code>
     * to the field <code>this.in</code> so as
     * to remember it for later use.
     *
     * @param in the underlying input stream, or <code>null</code> if
     *           this instance is to be created without an underlying stream.
     */
    protected LowerCaseInputStream(InputStream in) {
        super(in);
    }

    /**
     * 针对字节:将大写转成小写
     *
     * @return 小写字节
     * @throws IOException IO异常
     */
    public int read() throws IOException {
        int c = super.read();
        return ( c == -1 ? c : Character.toLowerCase( (char) c ) );
    }

    /**
     * 针对字节数组:将大写转成小写
     *
     * @param b 传入字节数组(the buffer into which the data is read)
     * @param offset 起始位移(the start offset in the destination array)
     * @param len 长度(the maximum number of bytes read)
     * @return 小写字节数组(the total number of bytes read into the buffer)
     * @throws IOException IO异常
     */
    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;
    }
}

测试

package com.example.test;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * <p>
 * <code>TestLowerCaseInputStream</code>
 * </p>
 * Description: 测试我们自定义的LowerCaseInputStream
 *
 * @author Mcchu
 * @date 2018/1/16 10:47
 */
public class TestLowerCaseInputStream {

    public static void main(String[] args) throws IOException{
        int c;
        
        try {
            // 设置FileInputStream
            FileInputStream fis = new FileInputStream("text.txt");
            
            // 先用BufferedInputStream装饰她,再用我们新定义的LowerCaseInputStream装饰她
            InputStream in = new LowerCaseInputStream( new BufferedInputStream( fis ));
            
            while( ( c = in.read() ) >= 0 ){
                System.out.println( (char)c );
            }
            
            in.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}



附录:以上来自Head First第三章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值