装饰者模式

374 阅读4分钟

概述

装饰者模式的实现其实就是 继承+聚合。

定义:在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。

装饰(Decorator)模式中的角色:

  • 抽象构件(Component)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
  • 具体构件(Concrete Component)角色 :实现抽象构件,通过装饰角色为其添加一些职责。
  • 抽象装饰(Decorator)角色 : 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  • 具体装饰(ConcreteDecorator)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

在什么情况下我们会使用装饰者模式呢?

网上有一个非常经典的案例,比如咖啡店点餐或者小吃店点餐此类,这里拿小吃店点餐为例。

点餐势必要使用菜单,菜单上有菜单项,而每个菜单项都是由主菜+依附主菜的附加菜组成。

暂且不考虑附加菜是否能添加到每一个主菜这种关系,暂且理解为主菜和附加菜为平行关系,可以随意组合。

那么其实就是菜单项=主菜+附加菜。代码实现模式其实也非常的简单。

public class Item{
	Master master;
	List<Addition> additions;
}

好,现在就此打住,这和装饰者模式无关,这通常也被称作为组合模式,不过这也非常符合我们的日常开发习惯。

如果不用组合模式呢,按照我们oop思想的一切都是对象的角度来看,我们需要为每个 主菜+附加菜 的组合来定义一个类,形成一个 n*m 的格式,这不用多说都会形成一个类爆炸的形式。

因此,为了制止类大爆炸的情况,又不去额外的增加一个组合类,那么我们可以重新审视 主菜+附加菜 之间的关系。

我们可以发现,主菜,加不加上附加菜,其实都是主菜的,附加菜是无法依赖主菜存在的,所以其实附加菜只是对于主菜的一个附加,本质上这还是一个主菜。

也就是说,我们需要动态的为主菜添加功能,这也正是我们对于 装饰者模式 的定义。

实现大概如此,然后重写主菜中的每一个方法,为其添加自己的逻辑,并调用主菜原本的逻辑。


public class Addition extends Main{

	private Main main;

}

实践案例实现

用刚才的饭店举例,不过使用快餐店的形式。

//快餐接口
public abstract class FastFood {
    private float price;
    private String desc;

    public FastFood() {
    }

    public FastFood(float price, String desc) {
        this.price = price;
        this.desc = desc;
    }

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

    public float getPrice() {
        return price;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public abstract float cost();  //获取价格
}

//炒饭
public class FriedRice extends FastFood {

    public FriedRice() {
        super(10, "炒饭");
    }

    public float cost() {
        return getPrice();
    }
}

//炒面
public class FriedNoodles extends FastFood {

    public FriedNoodles() {
        super(12, "炒面");
    }

    public float cost() {
        return getPrice();
    }
}

//配料类
public abstract class Garnish extends FastFood {

    private FastFood fastFood;

    public FastFood getFastFood() {
        return fastFood;
    }

    public void setFastFood(FastFood fastFood) {
        this.fastFood = fastFood;
    }

    public Garnish(FastFood fastFood, float price, String desc) {
        super(price,desc);
        this.fastFood = fastFood;
    }
}

//鸡蛋配料
public class Egg extends Garnish {

    public Egg(FastFood fastFood) {
        super(fastFood,1,"鸡蛋");
    }

    public float cost() {
        return getPrice() + getFastFood().getPrice();
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

//培根配料
public class Bacon extends Garnish {

    public Bacon(FastFood fastFood) {

        super(fastFood,2,"培根");
    }

    @Override
    public float cost() {
        return getPrice() + getFastFood().getPrice();
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

//测试类
public class Client {
    public static void main(String[] args) {
        //点一份炒饭
        FastFood food = new FriedRice();
        //花费的价格
        System.out.println(food.getDesc() + " " + food.cost() + "元");

        System.out.println("========");
        //点一份加鸡蛋的炒饭
        FastFood food1 = new FriedRice();

        food1 = new Egg(food1);
        //花费的价格
        System.out.println(food1.getDesc() + " " + food1.cost() + "元");

        System.out.println("========");
        //点一份加培根的炒面
        FastFood food2 = new FriedNoodles();
        food2 = new Bacon(food2);
        //花费的价格
        System.out.println(food2.getDesc() + " " + food2.cost() + "元");
    }
}

源码

io

说到 FilterInputStream 大家可能不能意识到这是什么,但是说 BufferedInputStream 大家一定能知道。

BufferedInputStream 是io包下使用非常频繁的缓冲流,能加快io速度。

在java的io包下,像这样的为io类增加新功能,但是又不影响使用的类有很多:

  • PushbackInputStream:允许将读取的字节退回到输入流中,以便稍后再次读取。它提供了 unread() 方法来实现这个功能。
  • LineNumberInputStream:可以对输入流进行逐行编号,它提供了 getLineNumber() 方法来获取当前行号。
  • ObjectInputStream:用于从输入流中读取对象。它提供了反序列化的功能,可以将字节流转换为对象。
  • SequenceInputStream:可以将多个输入流组合成一个逻辑上的连续输入流。它按照它们被传递给构造函数的顺序依次读取这些输入流。

而这些类都是继承于FilterInputStream,我们点进FilterInputStream中去看,他继承了InputStream,又聚合了InputStream,在重写的方法中都是调用in中的函数,这便是经典的装饰者模式。