设计模式之----装饰者模式

设计模式之装饰者模式

  • 装饰者模式定义
  • 意图,解决的问题及优缺点
  • 装饰者模式使用场景以及注意事项
  • 装饰者模式案例一
  • 装饰者模式案例二
  • JavaIO装饰者模式扩展案例

装饰者模式定义

  • 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
    这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
  • 装饰者模式动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

这里写图片描述
看类图结构
这里写图片描述


意图,解决的问题及优缺点

  • 动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
  • 一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
  • 目的就是要 在不想增加很多子类的情况下扩展类。
  • 优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
  • 缺点:多层装饰比较复杂。

装饰者模式案例一

要实现一个咖啡的点单系统,点单的时候,可以只点单品(有很多中),也可以加各种各样的调料,同时要能迅速的得到总的订单的描述,以及总的价格。

方案一

这里写图片描述
直接使用继承,这样的不好之处在于,如果品种不断的增加,各个品种和调料之间的组合,会产生类的爆炸。

方案二

这里写图片描述
第二种方案是使用一些bool型变量表示这个品种有没有调料,存在的问题是增加调料的时候,要修改类,已经添加多分调料的问题。

方案三

  • 这种方案就是使用装饰者模式来设计;
  • 首先组件(Component(也就是被装饰者包着的))就是各种各样的咖啡,他们都继承自Coffe类(也可以不继承,直接继承Drink类),这里只是一个中间类;
  • 然后就是各种装饰者(也就是各种调料),都继承Decorator;
  • 最后就是Coffee类和Decorator都继承Drink类;

这里写图片描述
在这种情况下某份订单是这样组成的
这里写图片描述
这里写图片描述
具体代码实现:
首先看Drink类

package decorator.custom.drink;

/**
 * Component的超类
 *
 * 单品和装饰者都要继承自这个类
 */
public abstract class Drink {

    private String description = ""; //一开始没有描述
    private double price = 0; //一开始价格为0

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) { //描述的时候顺便把价格描述一下
        this.description = description;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    /**
     * 抽象方法
     * 如果是单品的话就直接是自己的价格
     * 如果是装饰者的话就还要加上装饰品自己的价格
     * @return
     */
    public abstract double cost();

}

然后是各种咖啡
先看咖啡的父类

package decorator.custom.component;

import decorator.custom.drink.Drink;

/**
 * 这个是所有咖啡的父类 (相当于和Drink类有一个过渡的类)
 * Component
 */
public class Coffee extends Drink {

    @Override
    public double cost() {
        return getPrice();//super.getPrice()//这个就是父类的价格(自己什么也没加)
    }

    @Override
    public String getDescription() {
        return super.getDescription() + "-" + cost();
    }
}

然后看具体的各个品种

package decorator.custom.component;

public class Decaf extends Coffee {

    public Decaf() {
        super.setDescription("Decaf");
        super.setPrice(3); //3块钱
    }
}
package decorator.custom.component;

public class Espresso extends Coffee {

    public Espresso(){
        super.setDescription("Espresso");
        super.setPrice(4);
    }
}
package decorator.custom.component;

public class LongBlack extends Coffee {

    public LongBlack() {
        super.setDescription("LongBlack");
        super.setPrice(5);
    }
}
package decorator.custom.component;

public class ShortBlack extends Coffee {

    public ShortBlack() {
        super.setDescription("ShortBlack");
        super.setPrice(6);
    }
}

然后再看装饰者包
先有一个装饰者的父类

package decorator.custom.decorator;

import decorator.custom.drink.Drink;

public class Decorator extends Drink{

    /**
     *这个引用很重要,可以是单品,也可以是被包装过的类型,所以使用的是超类的对象
     * 这个就是要被包装的单品(被装饰的对象)
     */
    private Drink drink; //这里要拿到父类的引用,因为要控制另一个分支

    public Decorator(Drink drink) {
        this.drink = drink;
    }

    /**
     * 如果drink是已经被装包过的,那么就会产生递归调用  最终到单品
     * @return
     */
    @Override
    public double cost() {
        return super.getPrice() + drink.cost(); //自己的价格和被包装单品的价格
    }

    @Override
    public String getDescription() {
        return super.getDescription() + "-" + super.getPrice()
                + "&&" + drink.getDescription();
    }
}

然后就是各种装饰者

package decorator.custom.decorator;

import decorator.custom.drink.Drink;

/**
 * 这个是具体的装饰者() --> 继承自中间的装饰着Decorator
 */
public class Chocolate extends Decorator{
    public Chocolate(Drink drink) { //如果父类搞了一个 带参数的构造函数,子类必须显示的使用super调用
        super(drink);
        super.setDescription("Chocolate");
        super.setPrice(1);
    }
}
package decorator.custom.decorator;

import decorator.custom.drink.Drink;

public class Milk extends Decorator{
    public Milk(Drink drink) {
        super(drink); //调用父类的构造函数
        super.setDescription("Milk");
        super.setPrice(3);
    }
}
package decorator.custom.decorator;

import decorator.custom.drink.Drink;

/**
 * 具体的装饰者二
 * 大豆奶饮品
 */
public class Soy extends Decorator {
    public Soy(Drink drink) {
        super(drink);
        super.setDescription("Soy");
        super.setPrice(2);
    }
}

总的测试类:

package decorator.custom.test;

import decorator.custom.component.Decaf;
import decorator.custom.component.LongBlack;
import decorator.custom.decorator.Chocolate;
import decorator.custom.decorator.Milk;
import decorator.custom.drink.Drink;

public class MyTest {
    public static void main(String[] args) {
        //只点一个单品
        Drink order = new Decaf();
        System.out.println("order description : " + order.getDescription());
        System.out.println("order price : " + order.cost());

        System.out.println("---------------加了调料的----------------");

        order = new LongBlack();
        order = new Milk(order);
        order = new Chocolate(order);
        order = new Chocolate(order);
        System.out.println("order description : " + order.getDescription());
        System.out.println("order price : " + order.cost());

    }
}

运行结果
这里写图片描述
注意上面的cost和description在装饰者的一层一层的包裹下计算的时候有递归的调用。


装饰者模式案例二

再来看一个更加简单的例子,模拟的是游戏中英雄学习技能;比如某个英雄盲僧可以学习各种技能,和各种技能搭配(装饰盲僧)
这里写图片描述

Hero接口(总的Compoent)

/**
 * 英雄接口
 */
public interface Hero {

    void learnSkills();
}

具体的英雄(被修饰的)

/**
 * 具体的一个英雄
 */
public class BlindMonk implements Hero{

    private String name;

    public BlindMonk(String name) {
        this.name = name;
    }

    @Override
    public void learnSkills() {
        System.out.println(name + "学习了以上技能!");
    }
}

装饰者

/**
 * 技能栏
 * 这个就是装饰者,装饰某个英雄的
 */
public class Skills implements Hero{
    //持有一个英雄对象接口 注意这里一定要有Hero接口的对象
    private Hero hero;

    public Skills(Hero hero) {
        this.hero = hero;
    }

    @Override
    public void learnSkills() {
        if(hero != null) hero.learnSkills();
    }
}

具体的两个装置者:

/**
 * 装饰者1
 */
public class Skill_Q extends Skills {

    private String skillName;

    public Skill_Q(Hero hero,String skillName) {
        super(hero);
        this.skillName = skillName;
    }

    @Override
    public void learnSkills() {
        System.out.println("学习了技能Q:" +skillName);
        super.learnSkills();
    }
}
/**
 * 装饰者2
 */
public class Skill_W extends Skills{

    private String skillName;

    public Skill_W(Hero hero,String skillName) {
        super(hero);
        this.skillName = skillName;
    }

    @Override
    public void learnSkills() {
        System.out.println("学习了技能W:" + skillName);
        super.learnSkills();
    }
}

测试


public class MyTest {

    public static void main(String[] args) {
        Hero  mang = new BlindMonk("盲僧");

        mang = new Skill_Q(mang,"神龙摆尾");
        mang = new Skill_W(mang,"猛虎下山");

        mang.learnSkills();
    }
}

输出:

学习了技能W:猛虎下山
学习了技能Q:神龙摆尾
盲僧学习了以上技能!

JavaIO装饰者模式扩展案例

  • Java中的IO流设计就是使用的装饰者模式;
  • 我们可以自己扩展,设计自己的输入流;

这里写图片描述
如下面的例子,使用输入流将读入的字符转化成大写,我们重新装饰流,这里的FilterInputStream就是我们的Decorator;

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

/**
 * 自己定义的输入流  
 * 扩展FilterInputStream(这个类就是我们的Decorator) 中间装饰者  
 * 所以我们只要继承这个就可以扩展自己的输入流装饰者 
 */
public class UpperCaseInputStream extends FilterInputStream{

    protected UpperCaseInputStream(InputStream in) {  //这个InputStream就是我们的Drink 类(超类)
        super(in);
    }


    //重写  相当于cost和description
    @Override
    public int read() throws IOException {
        int index = super.read(); //读取一个字节
        return index == -1 ? index : Character.toUpperCase((char)(index));  //小写转换成大写
    }

    //字节数组
    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int index = super.read(b, off, len);
        for(int i = 0; i < index; i++){
            b[i] = (byte)Character.toUpperCase((char)(b[i]));
        }
        return index;
    }
}

测试

import java.io.*;

public class MyTest {

    public static void main(String[] args) throws IOException {
        InputStream in = new UpperCaseInputStream(new BufferedInputStream(new FileInputStream("/home/zxzxin/Java_Maven/DesignPatterns/src/main/java/decorator/java/in.txt")));
        int len;
        while((len = in.read()) >= 0){
            System.out.print((char)(len));
        }
        in.close();
    }
}

效果
这里写图片描述

猜你喜欢

转载自blog.csdn.net/zxzxzx0119/article/details/81330259
今日推荐