avatar

java装饰模式

基本介绍

装饰模式

  • 装饰模式(Decorator),动态地给一个对象添加一些额外地指责,就增加功能来说。装饰模式比生成子类更加灵活

装饰模式类图

装饰模式中的角色:

  • Component:抽象构件,定义一个对象接口,可以动态的给这些对象添加职责
  • ConcreteComponent: 定义了一个具体构件,也可以给
  • Decorator: 抽象装饰类,其中聚合了Component,同时又继承了Component,这里采用继承Component并且将Component聚合进来是因为被装饰类也是一个Component,也可以再次被装饰,这可能就是继承加聚合得作用吧,这样能做到一层一层得包裹装饰类。
  • ConcreteDecorator: 具体装饰类

基本代码实现

首先我们定义一个组件Component

//这里可以使用抽象类也可以使用接口
public abstract class Component {
public abstract void Operation();
}

定义一个具体的ConcreteComponent

//这是一个未经过装饰的组件,显得非常朴素
public class ConcreteComponent extends Component {
@Override
public void Operation() {
System.out.println("实现具体的组件,但是没有经过装饰");
}
}

然后我们定义装饰类Decorator这是个具体的类,不是接口也不是抽象类

public class Decorator extends Component{
private Component component;

public void setComponent(Component component) {
this.component = component;
}

@Override
public void Operation() {
this.component.Operation(); //这里可能就是继承Component的原因,在装饰后能继续使用Component的方法
}
}

写具体的装饰类

public class ConcreteDecoratorA extends Decorator {
@Override
public void Operation() {
super.Operation();
AddBehavior();
}
private void AddBehavior()
{
System.out.println("增加装饰");
}
}

然后我们在客户端调用一下,装饰的方法是:首先使用ConcreteComponent实例化对象,然后用ConcreteDecoratorA实例化的对象包装,包装过后返回ConcreteDecoratorA

public class Client {
public static void main(String[] args)
{
ConcreteComponent c = new ConcreteComponent();//这是需要包装的类
ConcreteDecoratorA a = new ConcreteDecoratorA();//包装类
a.setComponent(c);//进行包装
a.Operation();//实现
}
}

案例

比如我们去星巴克买咖啡,咖啡做好以后呢我们需要加糖,加奶,加乱七八糟的东西,这样我们就需要用到装饰模式,去动态的装饰做好的咖啡

类图

代码实现

编写Drink抽象类

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

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

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

public String getDes() {
return des;
}

public float getPrice() {
return price;
}
//返回价格
public abstract float cost();
}

编写一个coffee类用于抽取咖啡的公共特点

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

然后编写真正的咖啡

public class Espresso extends Coffee{
public Espresso() {
setDes("意大利咖啡");
setPrice(6.0f);
}
}
public class LongBlack extends Coffee{
public LongBlack() {
setDes("美式咖啡");
setPrice(5.0f);
}
}
public class ShortBlack extends Coffee{
public ShortBlack() {
setDes("ShortBlack");
setPrice(7.0f);
}
}

然后我们编写装饰者

public class Decorator extends Drink{
private Drink drink;
public Decorator(Drink drink) {
this.drink = drink;
}
public void setDrink(Drink drink) {
this.drink = drink;
}
@Override
public float cost() {
//自己的价格加装饰的价格
return super.getPrice()+drink.cost();
}

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

编写实现类

public class Chocolate extends Decorator {
public Chocolate(Drink drink) {
super(drink);
setDes("巧克力");
setPrice(3.0f);
}
}
public class Milk extends Decorator {
public Milk(Drink drink) {
super(drink);
setDes("牛奶");
setPrice(2.0f);
}
}

写一个客户端进行实现

public class Client {
public static void main(String[] args) {
//这是使用set方法进行传入,
// Drink e = new Espresso();
// Milk milk = new Milk();
// milk.setDrink(e);
// System.out.println( milk.getDes());
// Chocolate chocolate =new Chocolate();
// chocolate.setDrink(milk);
// System.out.println(chocolate.getDes());
//使用构造器传入
Drink drink = new Espresso();
//加奶
drink = new Milk(drink);
//加巧克力
drink = new Chocolate(drink);
System.out.println(drink.getDes());
}
}

装饰者模式在JDK中的利用

装饰模式在JDK中最经典的实例是Java IO。

  • 以InputStream为例:

抽象装饰类:FilterInputStream

//之前
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
//之后

角色分配:

  • 抽象构件类:InputStream
  • 具体构件类:FileInputStream、ByteArrayInputStream等
  • 抽象装饰类:FilterInputStream
  • 具体装饰类:BufferedInputStream、DataInputStream等

客户端使用

FileInputStream inFS=new FileInputStream("temp/fileSrc.txt");    //FileInputStream继承了InputStream,这是被装饰者
BufferedInputStream inBS=new BufferedInputStream(inFS);//继承FilterInputStream,是一个装饰类,用于装饰FileInputStream
//定义一个字节数组,用于存放缓冲数据
byte[] data = new byte[1024];
inBS.read(data);

装饰者模式扩展

装饰模式的简化-需要注意的问题

  • 个装饰类的接口必须与被装饰类的接口保持相同,对于客户端来说无论是装饰之前的对象还是装饰之后的对象都可以一致对待。
  • 尽量保持具体构件类Component作为一个“轻”类,也就是说不要把太多的逻辑和状态放在具体构件类中,可以通过装饰类对其进行扩展。
  • 如果只有一个具体构件类而没有抽象构件类,那么抽象装饰类可以作为具体构件类的直接子类。

简化后得类图

透明装饰模式

在透明装饰模式中,要求客户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该声明具体构件类型和具体装饰类型,而应该全部声明为抽象构件类型。

Drink drink;
//加奶
drink = new Milk(drink);
//加巧克力
drink = new Chocolate(drink);

半透明装饰模式

大多数装饰模式都是半透明(semi-transparent)的装饰模式,而不是完全透明(transparent)的。即允许用户在客户端声明具体装饰者类型的对象,调用在具体装饰者中新增的方法。

Drink e = new Espresso();
Milk milk = new Milk();
milk.setDrink(e);
System.out.println( milk.getDes());
Chocolate chocolate =new Chocolate();
chocolate.setDrink(milk);
System.out.println(chocolate.getDes());

装饰模式总结

优点:

  • 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
  • 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
  • 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
  • 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
    缺点:
  • 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
  • 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
文章作者: zenshin
文章链接: https://zlh.giserhub.com/2020/04/13/DesignPattern/decorator/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 zenshin's blog
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论