设计模式~装饰模式

147 阅读4分钟

1. 介绍

  • 也称包装模式,结构型设计模式之一,使用一种对客户端透明的方式来动态地扩展对象地功能,同时也是继承关系的一种替代方案之一。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

2. 定义

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

3. UML类图

image.png

  • Component:抽象组件;可以是一个接口或一个抽象类,充当被抽象的原始对象
  • ConcreteComponent:抽象组件具体实现类;Component的具体实现类,也是装饰的具体对象
  • Decorator:抽象装饰类;职责就是装饰组件对象,内部持有组件对象的引用。通常它作为一个抽象类,有具体的子类去实现不同的装饰逻辑,当然,如果装饰逻辑比较单一,也可以省略实现子类而直接作为具体装饰着
  • ConcreteDecoratorA/ConcreteDecoratorB:抽象装饰类的具体实现类

4. 使用场景

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 需要动态地给一个对象增加功能,这些功能可以动态的撤销。
  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。

5. 简单实现

  • 所谓人靠衣装,可以把人当作一个抽象类,穿衣的行为定义为一个抽象方法,而穿不同的衣服则是装饰类
  • 抽象原始对象:人
/**
 * 人当作抽象类,拥有穿衣的抽象方法
 * @author BTPJ  2023/1/9
 */
public abstract class Person {
    /**
     * 穿衣
     */
    public abstract void dressed();
}
  • 人的具体实现类:小明
/**
 * 小明集成Person抽象类,本身只穿了内衣内裤
 *
 * @author BTPJ  2023/1/9
 */
public class XiaoMing extends Person {

    @Override
    public void dressed() {
        System.out.println("小明穿了内衣内裤");
    }
}
  • 人的装饰抽象类,他会持有原始抽象对象(被装饰对象)的引用
/**
 * 用户装饰类,穿各种品牌的衣服
 *
 * @author BTPJ  2023/1/9
 */
public class PersonDecorator extends Person {
    // 持有对抽象类的引用
    protected Person person;

    public PersonDecorator(Person person) {
        this.person = person;
    }

    @Override
    public void dressed() {
        person.dressed();
    }
}
  • 装饰抽象类的具体实现:LiNing
/**
 * 穿李宁品牌衣服的具体装饰类
 *
 * @author BTPJ  2023/1/9
 */
public class LiNingDecorator extends PersonDecorator {
    public LiNingDecorator(Person person) {
        super(person);
    }

    @Override
    public void dressed() {
        super.dressed();
        dressedLiNing();
    }

    /**
     * 穿一套李宁的运动装备
     */
    private void dressedLiNing() {
        System.out.println("穿李宁的运动装备");
    }
}
  • 装饰抽象类的具体实现:Nike
/**
 * 穿李宁品牌衣服的具体装饰类
 *
 * @author BTPJ  2023/1/9
 */
public class NikeDecorator extends PersonDecorator {
    public NikeDecorator(Person person) {
        super(person);
    }

    @Override
    public void dressed() {
        super.dressed();
        dressedNike();
    }

    /**
     * 穿一套李宁的运动装备
     */
    private void dressedNike() {
        System.out.println("穿Nike的运动装备");
    }
}
  • 客户端调用
/**
 * 客户端调用
 *
 * @author BTPJ  2023/1/9
 */
public class Client {

    public static void main(String[] args) {
        Person person = new XiaoMing();

        // 穿李宁
        PersonDecorator decorator = new LiNingDecorator(person);
        decorator.dressed();

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

        // 穿Nike
        PersonDecorator decorator2 = new NikeDecorator(person);
        decorator2.dressed();
    }
}

执行结果:
小明穿了内衣内裤
穿李宁的运动装备
---------------------------------
小明穿了内衣内裤
穿Nike的运动装备

6. 源码中的使用场景

  • ContextWrapper装饰了Context,给Context提供了startActivity(Intent intent)等方法

7. 优缺点

  • 优点:
    • 通过组合而非继承的方式,动态地来扩展一个对象的功能,在运行时选择不同的装饰器,从而实现不同的行为。
    • 有效地避免了使用继承的方式来扩展对象功能从而带来灵活性差,子类无限制扩张的问题。
    • 具体组件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体组件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
  • 缺点:
    • 装饰链一旦过长,会影响效率。
    • 因为所有对象都是继承于Component,所以如果Component内部结构发生改变,则不可避免地影响所有子类(装饰者和被装饰者),如果基类改变,势必影响对象的内部。
    • 比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐,所以只在必要的时候使用装饰者模式。