设计模式 - 装饰器模式

745 阅读5分钟

设计模式 - 装饰器模式

一、引入

当我们需要在一个对象的基础上,动态地添加一些额外的功能时,装饰器模式就能派上用场。这种模式允许你在不改变原始对象的结构的情况下,对其进行功能的扩展。

比如说,你有一杯普通的咖啡,你可以选择在里面加入牛奶、糖、甚至是一些香料,使得它变成拿铁咖啡、卡布奇诺等等,但这些变化并不会改变咖啡本身的基本属性。

在程序中,装饰器模式可以让你像叠加图层一样,一层一层地给一个对象添加新的功能,而不需要修改原始对象的代码。

举个例子,假如你有一个基本的文本编辑器,你可以通过装饰器模式来添加各种功能,比如加粗、斜体、下划线等等,而不需要改动编辑器的核心代码。这样一来,你可以根据需要随意地组合这些功能,得到一个个性化定制的文本编辑器,而不需要为每个组合都写一套新的代码。

总的来说,装饰器模式让你可以灵活地扩展一个对象的功能,同时保持了对象的简单和可重用性。

二、概念

装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许在不修改现有对象结构的情况下,动态地给对象添加额外的功能。

在装饰器模式中,通常会定义一个抽象的装饰器接口,它与被装饰的对象实现相同的接口或继承相同的抽象类。然后,具体的装饰器类继承自抽象装饰器类,并持有一个被装饰的对象实例,它们可以在调用被装饰对象的同时,添加额外的功能。

装饰器模式的关键点在于它允许动态地给一个对象添加新的功能,而且可以在多个装饰器的情况下进行嵌套使用。

三、基本结构

  • 抽象组件(Component):定义了一个接口,可以是一个抽象类或者接口,它是被装饰对象和装饰器共同实现的接口。
  • 具体组件(Concrete Component):实现了抽象组件接口,是被装饰的对象。
  • 抽象装饰器(Decorator):继承自抽象组件,并包含一个指向抽象组件的引用,它可以在调用抽象组件的方法之前或之后执行一些额外的操作。
  • 具体装饰器(Concrete Decorator):实现了抽象装饰器接口,它是具体的装饰逻辑,可以对被装饰对象进行增强或修改。

四、示例代码

/**
 * 抽象组件 : 咖啡
 */
public interface Coffee {
    double cost();
}

/**
 * 具体组件 : 简单咖啡
 */
public class SimpleCoffee implements Coffee {
    @Override
    public double cost() {
        return 2.2;
    }
}
/**
 * 抽象装饰器 : 咖啡装饰器
 */
public class CoffeeDecorator implements Coffee {
    private Coffee coffee;

    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    @Override
    public double cost() {
        return coffee.cost();
    }
}

/**
 * 具体装饰器 : 牛奶咖啡
 */
public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double cost() {
        return super.cost() + 0.1;
    }
}

/**
 * 具体装饰器 : 加糖咖啡
 */
public class SugarDecorator extends CoffeeDecorator {

    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double cost() {
        return super.cost() + 2;
    }
}
//客户端
public class Client {
    public static void main(String[] args) {
        SimpleCoffee simpleCoffee = new SimpleCoffee();
        System.out.println("简单咖啡价格:" + simpleCoffee.cost());

        MilkDecorator milkDecorator = new MilkDecorator(simpleCoffee);
        System.out.println("牛奶咖啡价格:" + milkDecorator.cost());


        SugarDecorator sugarDecorator = new SugarDecorator(simpleCoffee);
        System.out.println("加糖咖啡价格:" + sugarDecorator.cost());


        SugarDecorator sugarDecorator1 = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
        System.out.println("加奶加糖咖啡价格:" + sugarDecorator1.cost());
        
    }
}

五、用途

  1. 动态添加功能: 允许在不改变原始对象的结构的情况下,动态地给对象添加新的功能。
  2. 避免类的继承关系过于复杂: 通过装饰器模式,可以避免类之间出现过多的子类,减轻了继承关系的复杂性。
  3. 灵活组合功能: 可以通过组合不同的装饰器,实现不同的功能组合,从而提供灵活的定制化功能。
  4. 保持对象简单和可重用性: 装饰器模式允许在不修改对象的情况下,动态地添加或修改功能,保持了对象的简洁性和可重用性。

在实际开发中,装饰器模式经常应用于以下场景:

  1. I/O流处理: Java中的I/O流就是典型的装饰器模式的应用,通过层层包装来实现对数据的处理。
  2. 缓冲流: 在读写数据时,可以通过装饰器模式来增加缓冲功能,提高读写效率。
  3. Web开发中的过滤器: 在Web开发中,可以使用装饰器模式来实现过滤器,对请求或响应进行处理。
  4. 日志记录: 通过装饰器模式可以在不改变原有日志功能的基础上,动态地添加新的日志记录功能。

六、总结

优点:

  1. 动态扩展功能: 装饰器模式允许在运行时动态地给对象添加新的功能,而且可以根据需要添加多个装饰器,实现功能的组合。
  2. 避免继承的缺点: 相比继承,装饰器模式避免了创建过多子类的问题,使得类的层次结构更加灵活和简洁。
  3. 保持原始对象不变: 装饰器模式不会改变原始对象的结构,而是在其基础上进行功能扩展,保持了原始对象的稳定性和可重用性。
  4. 符合开闭原则: 可以在不修改已有代码的情况下,动态地增加新的功能。
  5. 更容易控制对象的功能: 可以灵活地组合装饰器,按需选择添加或移除功能,实现精细化的功能控制。

缺点:

  1. 增加了复杂度: 使用装饰器模式会引入许多新的类和对象,增加了系统的复杂度,降低了代码的可读性。
  2. 可能会造成设计上的混乱: 如果使用不当,可能会造成过度装饰,导致设计上的混乱和困难。

总的来说,装饰器模式是一种非常灵活和强大的设计模式,适用于需要动态地扩展对象功能的场景。但在使用时需要注意控制装饰器的数量和组合关系,以保持代码的清晰和可维护性。

关注公众号回复“设计模式源码”即可获取源码,请君笑纳

微信公众号.png