侧边栏壁纸
  • 累计撰写 98 篇文章
  • 累计创建 20 个标签
  • 累计收到 3 条评论

设计模式--装饰者模式

林贤钦
2020-05-10 / 0 评论 / 9 点赞 / 649 阅读 / 0 字
温馨提示:
本文最后更新于 2020-05-11,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

装饰者模式(Decorator Pattern)

1. 介绍

1.1 模式说明

装饰者模式(装饰器模式,包装模式)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

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

1.2 主要作用

一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果

装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。

2. 模式原理

2.1 装饰者模式原理类图

2.2 装饰者模式包含如下角色:

抽象构件Component

Component是一个抽象类或接口,是要包装的原始对象

具体构件ConcreteComponent

Component的实现类,最终要装饰的实际对象

装饰Decorator

是一个抽象类,继承或实现了Component的接口,同时它持有一个对Component实例对象的引用,也可以有自己的方法。

  • 具体装饰ConcreteDecorator

Decorator的实现类,是具体的装饰者对象,负责给ConcreteComponent附加责任。

2.3 典型的装饰者模式代码

抽象构件Component

public abstract class Component {
    public abstract void operate();
}

具体构件ConcreteComponent

public class ConcreteComponent extends Component {
    @Override
    public void operate() {
        System.out.println("ConcreteComponent 原始对象操作");
    }
}

装饰Decorator

public abstract class Decorator extends Component {
    private Component component;

    /**
     * 构造函数传递要装饰的对象
     * @param component 被装饰的对象
     */
    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operate() {
        //调用被装饰者的方法
        this.component.operate();
    }
}

具体装饰ConcreteDecorator

public class ConcreteDecoratorA extends Decorator {

    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operate() {
        super.operate();
        //调用自己的方法
        this.operateAMethod();
    }

    private void operateAMethod() {
        System.out.println("ConcreteDecoratorA添加的修饰方法");
    }
}

public class ConcreteDecoratorB extends Decorator {

    public ConcreteDecoratorB(Component component) {
        super(component);
    }

    @Override
    public void operate() {
        //调用自己的方法
        this.operateBMethod();
        super.operate();
    }

    private void operateBMethod() {
        System.out.println("ConcreteDecoratorB添加的修饰方法");
    }
}

装饰者的调用端Client

public class Client {
    public static void main(String[] args) {
        //创建原始对象
        Component component = new ConcreteComponent();
        System.out.println("创建原始对象");
        component.operate();
        //第一次装饰
        component = new ConcreteDecoratorA(component);
        System.out.println("第一次装饰");
        component.operate();
        //第二次装饰
        component = new ConcreteDecoratorB(component);
        //两次装饰后的操作
        System.out.println("第二次装饰");
        component.operate();
    }
}

结果

创建原始对象
ConcreteComponent 原始对象操作
第一次装饰
ConcreteComponent 原始对象操作
ConcreteDecoratorA添加的修饰方法
第二次装饰
ConcreteDecoratorB添加的修饰方法
ConcreteComponent 原始对象操作
ConcreteDecoratorA添加的修饰方法

3. 实例讲解

3.1 实例概况

星巴克咖啡订单项目(咖啡馆)

咖啡种类/单品咖啡

Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)

调料

Milk、Soy(豆浆)、Chocolate

要求在扩展新的咖啡种类时

具有良好的扩展性、改动方便、维护方便

使用 OO 的来计算不同种类咖啡的费用:

客户可以点单品咖啡,也可以单品咖啡+调料组合。

3.2实例uml类图

3.3代码实现

步骤一:抽象构件Component

Drink 类

public abstract class Drink {
    public String des; // 描 述
    private float price = 0.0f;

    public String getDes() {
        return des;
    }

    public void setDes(String des) {
        this.des = des;
    }

    public float getPrice() {
        return price;
    }

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

    //计算费用的抽象方法
    //子类来实现
    public abstract float cost();
}

步骤二:具体构件ConcreteComponent

Coffee

public class Coffee extends Drink {
    @Override
    public float cost() {
        return super.getPrice();
    }
}

步骤三:具体子构建

DeCaf & ShortBlack & LongBlack & Espresso

public class DeCaf extends Coffee {
    public DeCaf() {
        setDes(" 无因咖啡 ");
        setPrice(1.0f);
    }
}

public class ShortBlack extends Coffee {
    public ShortBlack() {
        setDes(" shortblack ");
        setPrice(4.0f);
    }
}

public class LongBlack extends Coffee {
    public LongBlack() {
        setDes(" longblack ");
        setPrice(5.0f);
    }
}

public class Espresso extends Coffee {
    public Espresso() {
        setDes(" 意大利咖啡 ");
        setPrice(6.0f);
    }
}

步骤四:装饰Decorator

public class Decorator extends Drink {

    private Drink obj;

    public Decorator(Drink obj) { //组合
        this.obj = obj;
    }


    @Override
    public float cost() {
        // getPrice 自己价格
        return super.getPrice() + obj.cost();
    }

    @Override
    public String getDes() {
        // obj.getDes() 输出被装饰者的信息
        return des + " " + getPrice() + " && " + obj.getDes();
    }
}

步骤五:具体装饰ConcreteDecorator

Milk & Soy

public class Milk extends Decorator {
    public Milk(Drink obj) {
        super(obj);
        setDes(" 牛 奶 ");
        setPrice(2.0f);
    }
}

public class Soy extends Decorator{
    public Soy(Drink obj) {
        super(obj);
        setDes(" 豆浆    ");
        setPrice(1.5f);
    }
}

public class Chocolate extends Decorator {
    public Chocolate(Drink obj) { super(obj);
        setDes(" 巧克力 ");
        setPrice(3.0f); // 调味品 的价格
    }
}

步骤六:Client 类:装饰者模式的调用者

CoffeeBar

public class CoffeeBar {
    public static void main(String[] args) {
        // 装饰者模式下的订单:2 份巧克力+一份牛奶的 LongBlack

        // 1.  点一份 LongBlack
        Drink order = new LongBlack();
        System.out.println("费用 1=" + order.cost());
        System.out.println("描述=" + order.getDes());

        // 2. order 加入一份牛奶
        order = new Milk(order);
        System.out.println("order 加入一份牛奶 费用 =" + order.cost());
        System.out.println("order 加入一份牛奶 描述 = " + order.getDes());

        // 3. order 加入一份巧克力
        order = new Chocolate(order);
        System.out.println("order 加入一份牛奶  加入一份巧克力	费 用 =" + order.cost());
        System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + order.getDes());

        // 4. order 加入一份巧克力
        order = new Chocolate(order);
        System.out.println("order 加入一份牛奶  加入 2 份巧克力	费 用 =" + order.cost());
        System.out.println("order 加入一份牛奶 加入 2 份巧克力 描述 = " + order.getDes());

        System.out.println("-------------------------------------------------------");

        Drink order2 = new DeCaf();
        System.out.println("order2 无因咖啡	费 用 =" + order2.cost());
        System.out.println("order2 无因咖啡 描述 = " + order2.getDes());
        order2 = new Milk(order2);
        System.out.println("order2 无因咖啡  加入一份牛奶	费 用 =" + order2.cost());
        System.out.println("order2 无因咖啡 加入一份牛奶 描述 = " + order2.getDes());
    }
}

结果:

费用 1=5.0
描述= longblack
order 加入一份牛奶 费用 =7.0
order 加入一份牛奶 描述 = 牛 奶 2.0 && longblack
order 加入一份牛奶 加入一份巧克力 费 用 =10.0
order 加入一份牛奶 加入一份巧克力 描述 = 巧克力 3.0 && 牛 奶 2.0 && longblack
order 加入一份牛奶 加入 2 份巧克力 费 用 =13.0
order 加入一份牛奶 加入 2 份巧克力 描述 = 巧克力 3.0 && 巧克力 3.0 && 牛 奶 2.0 && longblack


order2 无因咖啡 费 用 =1.0
order2 无因咖啡 描述 = 无因咖啡
order2 无因咖啡 加入一份牛奶 费 用 =3.0
order2 无因咖啡 加入一份牛奶 描述 = 牛 奶 2.0 && 无因咖啡

4. 模式优缺点

优点

  • 继承的有力补充,比继承灵活,不改变原有对象的情况下给一个对象扩展功能

继承在扩展功能是静态的,必须在编译时就确定好,而使用装饰者可以在运行时决定,装饰者也建立在继承的基础之上的

  • 通过使用不同装饰类以及这些类的排列组合,可以实现不同的效果

  • 符合开闭原则

缺点

  • 会出现更多的代码,更多的类,增加程序的复杂性。

  • 动态装饰时,多层装饰时会更复杂。

    使用继承来拓展功能会增加类的数量,使用装饰者模式不会像继承那样增加那么多类的数量但是会增加对象的数量,当对象的数量增加到一定的级别时,无疑会大大增加我们代码调试的难度)

5. 应用场景

  • 扩展一个类的功能或者给一个类添加附加职责
  • 给一个对象动态的添加功能,或动态撤销功能。

例如

Java中的java.io包里面的InputStream类和OutputStream类就是装饰者模式


public static void main(String[] args) throws Exception{
DataInputStream dis = new DataInputStream(new FileInputStream("d:\\abc.txt")); System.out.println(dis.read());
dis.close();
}

说明

  1. InputStream是抽象类,

类似我们前面讲的 Drink, 抽象构件Component

  1. FileInputStream 是InputStream 子类

类似我们前面的 DeCaf, LongBlack

  1. FilterInputStream是InputStream 子类

类似我们前面 的 Decorator 修饰者

  1. DataInputStream是FilterInputStream 子类

具体的修饰者,类似前面的 Milk, Soy 等

  1. FilterInputStream 类 有protected volatile InputStream in;

即含被装饰者

模式扩展

  • 装饰者和代理模式

装饰者模式关注的是对象的动态添加功能。代理模式关注的是对对象的控制访问,对它的用户隐藏对象的具体信息。

  • 装饰者模式和适配器模式

装饰者模式和被装饰的类要实现同一个接口,或者装饰类是被装饰的类的子类。 适配器模式和被适配的类具有不同的接口。

9

评论区