设计模式之装饰者模式

装饰者模式

定义:在不改变原有对象的基础上,将功能附加到对象上,提供了比继承更有弹性的替代方案,扩展原有对象的功能

类型:结构型

适用场景

  • 扩展一个类的功能或给一个类添加附加的职责
  • 动态地给一个对象添加功能

优点

  • 继承的有力补充,比继承灵活,不改变原有对象的情况下给一个对象扩展功能(继承方式扩展功能,必须都是些可预见的功能,因为这些功能必须在编译时就确定,是静态的,而装饰者模式是由我们的应用层代码在运行过程中动态决定加入的方式和时间,同时也提供了一种即插即用的方法,可以在运行期间何时增加何种功能)
  • 通过使用不同的装饰类以及这些装饰类的排列组合,可以实现不同的效果
  • 符合开闭原则

缺点:会出现更多的代码,更多的类,增加程序的复杂度;动态装饰时,多层装饰时会更加复杂

UML类图:

Component:抽象构建角色,以规范准备接收附加责任的对象

ConcreteComponent:具体构件角色,定义一个将要接收复杂责任的类

Decorator:装饰者角色,持有一个构件(Component)对象,并定义一个与抽象构件接口一致的接口,这个角色不是必须的

ConcreteDecorator:负责给构件对象“贴上”附加的责任

示例

案例1:学生时代,我们每天的三餐基本是在学校的食堂解决,食堂上的每一样菜都有具体的价格,如一份青菜0.8元、土豆丝1.2元、煎蛋1元、鸡腿4.5元,除此之外,我们在排队点菜前,都会领到一份0.5元的白米饭,点完菜后,饭堂阿姨就会根据你的菜品算出最终的价格。每一天都有成百上千的的学生吃饭,每个人的饭菜喜好都有所不同,算出的价格都有所不同。对于这种不同搭配,我们可以使用装饰者模式去做,可以很轻松地组合各种菜品,计算出一份饭菜的价格

  • 这个场景中,我们需要先定义出一个抽象构件角色,这个是所有被装饰者(具体构件)和装饰者都需要实现的接口或抽象类
/**
 * 抽象构件
 */
public interface Food {

    /**
     * 食物描述
     * @return
     */
    String getFoodDesc();

    /**
     * 食物价格
     * @return
     */
    double getFoodPrice();

}
  • 定义一个抽象构建角色(装饰者都需要继承的)
public abstract class FoodDecorator implements Food{

    private Food food;//维护了一个被装饰者的实例对象

    public FoodDecorator(Food food){
        this.food = food;
    }

    @Override
    public String getFoodDesc() {
        return food.getFoodDesc();
    }

    @Override
    public double getFoodPrice() {
        return food.getFoodPrice();
    }
}
  • 定义具体构件角色,也即是被装饰者类,这里指的就是白米饭
public class Rice implements Food {

    @Override
    public String getFoodDesc() {
        return "一份白米饭";
    }

    @Override
    public double getFoodPrice() {
        return 0.5;
    }

}
  • 具体的装饰者类,这里特指青菜,土豆、鸡腿、青菜等一些配菜
public class Egg extends FoodDecorator {

    public Egg(Food food) {
        super(food);
    }

    @Override
    public double getFoodPrice() {
        return 1.0 +  super.getFoodPrice();
    }

    @Override
    public String getFoodDesc() {
        return  super.getFoodDesc() + " 1个煎蛋" ;
    }

}
public class Potato extends FoodDecorator{

    public Potato(Food food) {
        super(food);
    }

    @Override
    public double getFoodPrice() {
        return 1.2 +  super.getFoodPrice();
    }

    @Override
    public String getFoodDesc() {
        return  super.getFoodDesc() + " 1份土豆丝" ;
    }

}
public class Vegetables extends FoodDecorator {

    public Vegetables(Food food) {
        super(food);
    }

    @Override
    public double getFoodPrice() {
        return 0.8 +  super.getFoodPrice();
    }

    @Override
    public String getFoodDesc() {
        return  super.getFoodDesc() + " 1份青菜" ;
    }

}
public class Chicken extends FoodDecorator {

    public Chicken(Food food) {
        super(food);
    }

    @Override
    public double getFoodPrice() {
        return 4.5 +  super.getFoodPrice();
    }

    @Override
    public String getFoodDesc() {
        return  super.getFoodDesc() + " 1个鸡腿" ;
    }

}
  • 客户端
public class Client {


    public static void main(String[] args) {
        System.out.println("同学1想吃一份青菜鸡腿饭");
        Food food = new Rice();
        food = new Chicken(food);
        food = new Vegetables(food);
        System.out.println("食物描述:" + food.getFoodDesc());
        System.out.println("计算食物价格:" + food.getFoodPrice());

        System.out.println("");

        System.out.println("同学2想吃一份煎蛋土豆鸡腿饭");
        Food food1 = new Rice();
        food1 = new Egg(food1);
        food1 = new Chicken(food1);
        food1 = new Potato(food1);
        System.out.println("食物描述:" + food1.getFoodDesc());
        System.out.println("计算食物价格:" + food1.getFoodPrice());

        System.out.println("");

        System.out.println("同学3想吃一份有3个鸡腿的饭");
        Food food2 = new Chicken(new Chicken(new Chicken(new Rice())));
        System.out.println("食物描述:" + food2.getFoodDesc());
        System.out.println("计算食物价格:" + food2.getFoodPrice());
    }

}

同学1想吃一份青菜鸡腿饭
食物描述:一份白米饭 1个鸡腿 1份青菜
计算食物价格:5.8

同学2想吃一份煎蛋土豆鸡腿饭
食物描述:一份白米饭 1个煎蛋 1个鸡腿 1份土豆丝
计算食物价格:7.2

同学3想吃一份有3个鸡腿的饭
食物描述:一份白米饭 1个鸡腿 1个鸡腿 1个鸡腿
计算食物价格:14.0

在这个案例中,我们首先拿到了白米饭这个被装饰的对象,然后以煎蛋、青菜、鸡腿、土豆等各种配菜去装饰它,并依赖委托对象food(抽象构件角色中以组合方式加入的成员对象)去将价格累加上去。从最终的效果来看,无论以何种方式添加配菜,都可以很容易的算出最终的价格。从客户端的调用代码可以看到,各种装饰对象和被装饰对象对于客户端来说都是Food抽象构件的实例,这便是装饰模式的精髓所在。

  • 在上面的例子中,如果被装饰者只有米饭,那么抽象构件Component其实是可以忽略的,可以让抽象装饰者类直接继承被装饰者类,代码和类图如下

public abstract class FoodDecorator extends Rice {

    private Rice rice;//这里没有使用抽象引用,因为确定只有一个被装饰对象

    public FoodDecorator(Rice rice){
        this.rice = rice;
    }

    @Override
    public String getFoodDesc() {
        return rice.getFoodDesc();
    }

    @Override
    public double getFoodPrice() {
        return rice.getFoodPrice();
    }

}
  • 我们也可以发现到,其实抽象角色类Decorator并不是必须的,我们完全可以去掉抽象装饰者角色,把Decorator和ConcreteDecorator的责任合并成一个类,原来的方式是在抽象装饰者角色类中维护一个被装饰者对象,现在可以将被装饰对象放在子类中去维护。下面是我们的装饰菜品煎蛋类的代码

public class Egg implements Food{

    private Food food; //被装饰者对象放在了具体的装饰者类中维护

    @Override
    public String getFoodDesc() {
        return food.getFoodDesc();
    }

    @Override
    public double getFoodPrice() {
        return food.getFoodPrice();
    }
    
}
  • 最极简的装饰者模式,去掉抽象构件接口(Component),去掉抽象装饰者角色类,(Decorator),但这样就不再是面向抽象或接口编程了,代码的灵活性就大大降低,代码如下
/**
 * 被装饰者没有实现抽象构件接口
 */
public class Rice {

    public String getFoodDesc() {
        return "一份白米饭";
    }

    public double getFoodPrice() {
        return 0.5;
    }

}
/**
 * 装饰者直接继承于被装饰者
 */
public class Egg extends Rice {

    private Rice rice;

    public Egg(Rice rice){
        this.rice = rice;
    }

    @Override
    public String getFoodDesc() {
        return "一份煎蛋 " + rice.getFoodDesc();
    }

    @Override
    public double getFoodPrice() {
        return 1.0 + rice.getFoodPrice();
    }
}
public static void main(String [] args){
    Rice rice = new Egg(new Rice());
    System.out.println(rice.getFoodDesc());
    System.out.println(rice.getFoodPrice());
    //一份煎蛋 一份白米饭
	//1.5
}

关于装饰模式的透明性

装饰模式的本意是在不改变接口的前提下,增强所考虑的类的性能,也就是说,程序不要声明一个ConcreteComponent类型的变量,而应当声明一个Component类型的变量。换句话而言,就是被装饰后的对象,仅仅增强的是抽象构件接口中定义的方法,这种就是比较纯粹、透明的装饰者模式。而实际在增强性能的时候,往往需要建立新的公开方法,这种就是半透明装饰者模式,比如,我们上面例子中,如果是使用半透明的装饰者模式,那经过鸡腿装饰者装饰后的实例,就不仅仅只有抽象构件角色定义的getFoodDescgetFoodPrice方法,还可能会有获取鸡腿味道getChickenSmell方法,这是鸡腿装饰类额外的增强方法,若客户端需要使用到该方法,那么就要求返回实例是Chicken具体装饰角色类型,而不是抽象构件角色Food类型

public class Chicken extends FoodDecorator {

    public Chicken(Food food) {
        super(food);
    }

    @Override
    public double getFoodPrice() {
        return 4.5 +  super.getFoodPrice();
    }

    @Override
    public String getFoodDesc() {
        return  super.getFoodDesc() + " 1个鸡腿" ;
    }
	
    //额外的新方法
    public String getChickenSmell(){
        return "鸡肉味";
    }

}

public static void mian(String[] args){
    Food food = new Rice();
    Chicken chicken = new Chicken(food);
    chicken.getChickenSmell();   
}

相关的设计模式

  • 装饰者模式与代理模式

装饰者模式关注在一个对象上动态添加方法,相当于对功能的增强,而代理模式关注于控制对对象的的访问,代理模式的代理类可以向他的客户隐藏具体对象的信息,在代码使用上,代理模式会在代理类中增加一个被代理对象的实例,而在装饰者模式中,通常会把原始对象当做一个方法参数传递给装饰者的构造器

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

装饰者模式和适配器模式都可以叫做包装模式,Wraper模式,装饰者和被装饰者可以实现相同的接口或者装饰者是被装饰者的子类,也就是装饰者继承于被装饰者。在适配器中,适配器和被适配的类,具有不同的接口,当然,也有可能部分接口是重合的。装饰者模式也可以退化为半透明装饰者模式,装饰者除了提供被装饰类的接口外,还提供了其他的方法

使用典范

  • java中的I/O标准库

由于Java I/O库需要很多性能的各种组合,如果这些性能都是用继承的方法实现的,那么每一种组合都需要一个类,这样就会造成大量性能重复的类出现。而如果采用装饰模式,那么类的数目就会大大减少,性能的重复也可以减至最少。因此装饰模式是Java I/O库的基本模式。

抽象构件(Component)角色:由InputStream扮演。这是一个抽象类,为各种子类型提供统一的接口。

具体构件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等类扮演。它们实现了抽象构件角色所规定的接口。

抽象装饰(Decorator)角色:由FilterInputStream扮演。它实现了InputStream所规定的接口。

具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是BufferedInputStream、DataInputStream以及两个不常用到的类LineNumberInputStream、PushbackInputStream。

参考

posted @ 2020-04-19 19:41  didi516  阅读(160)  评论(0编辑  收藏  举报