装饰者模式

一、基本概念

装饰者模式(Decorator Pattern)是一种结构型设计模式,它允许向一个现有的对象动态地添加新的功能,同时不改变其结构。它是继承的一种替代方案,可以实现在运行时动态地扩展对象的行为,而无需修改原有代码。

装饰者模式的主要角色有:
  • 抽象组件(Component):定义一个对象的接口,可以给这些对象动态地添加职责。
  • 具体组件(ConcreteComponent):实现抽象组件的接口,代表被装饰的原始对象。
  • 抽象装饰者(Decorator):继承或实现抽象组件,持有一个抽象组件的引用,可以调用被装饰对象的方法,并且可以在其前后增加新的功能。
  • 具体装饰者(ConcreteDecorator):实现抽象装饰者的方法,给被装饰对象增加具体的职责。
装饰者模式的优点有:
  • 可以在不修改原有对象的基础上给对象增加新的功能,遵循开闭原则。
  • 可以使用多个具体装饰者来组合出不同的功能,实现灵活性和可扩展性。
  • 可以根据需要动态地增加或删除功能,而不影响其他对象。
  • 可以保持被装饰对象的类型和接口不变,对客户端透明。
装饰者模式的缺点有:
  • 会增加系统的复杂度和类的数量,可能导致代码难以理解和维护。
  • 会增加对象之间的耦合度,可能引入循环引用的问题。
  • 会影响被装饰对象的效率和性能。
  • 动态地多层装饰时,调试和维护比较困难
装饰者模式的优化方法有:
  • 尽量减少装饰者的数量和层次,避免过度使用装饰者模式
  • 使用工厂方法或者建造者模式来创建装饰者对象,简化客户端的调用
  • 使用组合/聚合代替继承,降低装饰者和被装饰者之间的耦合度
装饰者模式和继承都可以用于扩展一个类的功能,但是它们有以下几点区别:
  • 继承是一种静态的关系,即在编译时就确定了子类和父类的关系 而装饰者模式是一种动态的关系,即在运行时可以动态地给一个对象添加或删除功能。

  • 继承是一种"是一个"的关系,即子类是父类的一种特殊形式 而装饰者模式是一种"有一个"的关系,即装饰者持有目标对象的引用,并不改变其本质。

  • 继承可能会导致类的数量过多,增加了代码的复杂度和维护难度 而装饰者模式可以通过组合不同的装饰者来实现多样化的功能,减少了子类的数量。

  • 继承可能会破坏父类的封装性,因为子类可以访问父类的内部状态和方法 而装饰者模式可以保持目标对象的封装性,因为装饰者只能调用目标对象的公开方法。

区别点 装饰者模式 继承
关系类型 动态的 静态的
关系性质 有一个 是一个
类的数量 可以减少 可能过多
封装性 可以保持 可能破坏
优点 实现开闭原则,灵活地增加功能,可以实现多重装饰,保持目标对象的封装性 实现代码复用,减少冗余代码,实现多态,提高程序的灵活性和可维护性,实现抽象化和封装化,提高程序的安全性和可读性
缺点 增加了系统的复杂性,可能导致调试困难,需要注意装饰顺序和装饰次数,可能与目标对象的接口不完全匹配 增加了系统的耦合度,降低了程序的灵活性和可扩展性,可能导致类层次过深或过宽,影响程序的结构和效率,可能破坏父类的封装性,暴露其内部细节和实现方式
使用场景 当需要动态地给一个对象增加额外的功能时;当需要给一个对象增加一些与其本身无关或与其它对象无关的功能时;当需要给一个对象增加多个功能,并且这些功能可以相互组合时 当需要表示"是一个"的关系时;当需要实现代码复用和多态时;当需要实现抽象化和封装化时

二、安卓源码中的实例

安卓源码中经常使用到装饰者模式,例如:

  • ContextWrapper 类是一个抽象装饰者,它持有一个 Context 对象作为被装饰者,并且实现了 Context 接口。它可以在被装饰者的基础上增加新的功能或修改原有功能。例如,ContextThemeWrapper 类是一个具体装饰者,它可以给 Context 对象添加主题相关的功能。
  • InputStream 类是一个抽象组件,它定义了输入流对象的接口。它有很多子类和实现类,例如 FileInputStream 类是一个具体组件,它表示从文件中读取数据的输入流。BufferedInputStream 类是一个抽象装饰者,它持有一个 InputStream 对象作为被装饰者,并且继承了 InputStream 类。它可以给被装饰者增加缓冲区相关的功能。DataInputStream 类是一个具体装饰者,它也持有一个 InputStream 对象,并且继承了 BufferedInputStream 类。它可以给被装饰者增加读取基本数据类型相关的功能。
  • TextView 类是一个具体组件,它表示一个文本视图。它有很多子类和实现类,例如 EditText 类是一个具体组件,它表示一个可编辑的文本视图。TextViewCompat 类是一个抽象装饰者,它持有一个 TextView 对象作为被装饰者,并且提供了一些静态方法来给 TextView 对象增加兼容性相关的功能。例如,TextViewCompat.setAutoSizeTextTypeWithDefaults 方法可以给 TextView 对象设置自动调整字体大小的功能。
  • Drawable 类是一个抽象组件,它定义了可绘制对象的接口。它有很多子类和实现类,例如 BitmapDrawable 类是一个具体组件,它表示一个位图可绘制对象。DrawableWrapper 类是一个抽象装饰者,它持有一个 Drawable 对象作为被装饰者,并且实现了 Drawable 接口。它可以在被装饰者的基础上增加新的功能或修改原有功能。例如,RippleDrawable 类是一个具体装饰者,它可以给 Drawable 对象添加波纹效果的功能。
  • View 类是一个抽象组件,它定义了视图对象的接口。它有很多子类和实现类,例如 Button 类是一个具体组件,它表示一个按钮视图。ViewOutlineProvider 类是一个抽象装饰者,它持有一个 View 对象作为被装饰者,并且提供了一些方法来给 View 对象设置轮廓相关的属性。例如,ViewOutlineProvider.BACKGROUND 方法可以根据 View 对象的背景来设置其轮廓。

三、Kotlin、Java、C++实现

3.1 Kotlin实现

Kotlin是一种基于JVM的静态类型编程语言,它可以与Java互操作,并且支持函数式编程和面向对象编程的特性。Kotlin也可以用来实现装饰者模式,下面是一个简单的例子:

kotlin 复制代码
// 定义一个抽象组件,表示饮料
interface Beverage {
    // 返回饮料的描述
    fun getDescription(): String
    // 返回饮料的价格
    fun cost(): Double
}
kotlin 复制代码
// 定义一个具体组件,表示咖啡
class Coffee : Beverage {
    override fun getDescription(): String {
        return "咖啡"
    }

    override fun cost(): Double {
        return 10.0
    }
}
kotlin 复制代码
// 定义一个具体组件,表示茶
class Tea : Beverage {
    override fun getDescription(): String {
        return "茶"
    }

    override fun cost(): Double {
        return 5.0
    }
}
kotlin 复制代码
// 定义一个抽象装饰者,表示调料
abstract class CondimentDecorator(val beverage: Beverage) : Beverage {
    // 返回调料和饮料的描述
    override fun getDescription(): String {
        return "${beverage.getDescription()} + ${this.javaClass.simpleName}"
    }
}
kotlin 复制代码
// 定义一个具体装饰者,表示糖
class Sugar(beverage: Beverage) : CondimentDecorator(beverage) {
    // 返回糖和饮料的价格
    override fun cost(): Double {
        return beverage.cost() + 1.0
    }
}
kotlin 复制代码
// 定义一个具体装饰者,表示奶油
class Cream(beverage: Beverage) : CondimentDecorator(beverage) {
    // 返回奶油和饮料的价格
    override fun cost(): Double {
        return beverage.cost() + 2.0
    }
}
kotlin 复制代码
// 测试代码
fun main() {
    // 创建一个咖啡对象
    var coffee: Beverage = Coffee()
    println("${coffee.getDescription()} = ${coffee.cost()}")

    // 给咖啡加糖和奶油
    coffee = Sugar(coffee)
    coffee = Cream(coffee)
    println("${coffee.getDescription()} = ${coffee.cost()}")

    // 创建一个茶对象
    var tea: Beverage = Tea()
    println("${tea.getDescription()} = ${tea.cost()}")

    // 给茶加糖
    tea = Sugar(tea)
    println("${tea.getDescription()} = ${tea.cost()}")
}
ini 复制代码
// 输出结果
咖啡 = 10.0
咖啡 + Sugar + Cream = 13.0
茶 = 5.0
茶 + Sugar = 6.0

从上面的代码可以看出,Kotlin可以使用类和接口来实现装饰者模式,通过组合和委托的方式来给对象增加新的功能。Kotlin还有一些特性可以简化装饰者模式的实现,例如扩展函数和属性,委托属性等。

3.2 Java实现

Java是一种面向对象的编程语言,它也可以用来实现装饰者模式,下面是一个简单的例子:

csharp 复制代码
// 定义一个抽象组件,表示饮料
public abstract class Beverage {
    // 返回饮料的描述
    public abstract String getDescription();
    // 返回饮料的价格
    public abstract double cost();
}
scala 复制代码
// 定义一个具体组件,表示咖啡
public class Coffee extends Beverage {
    @Override
    public String getDescription() {
        return "咖啡";
    }

    @Override
    public double cost() {
        return 10.0;
    }
}
scala 复制代码
// 定义一个具体组件,表示茶
public class Tea extends Beverage {
    @Override
    public String getDescription() {
        return "茶";
    }

    @Override
    public double cost() {
        return 5.0;
    }
}
scala 复制代码
// 定义一个抽象装饰者,表示调料
public abstract class CondimentDecorator extends Beverage {
    // 持有一个被装饰者的引用
    protected Beverage beverage;

    public CondimentDecorator(Beverage beverage) {
        this.beverage = beverage;
    }

    // 返回调料和饮料的描述
    @Override
    public String getDescription() {
        return beverage.getDescription() + " + " + this.getClass().getSimpleName();
    }
}
scala 复制代码
// 定义一个具体装饰者,表示糖
public class Sugar extends CondimentDecorator {

    public Sugar(Beverage beverage) {
        super(beverage);
    }

    // 返回糖和饮料的价格
    @Override
    public double cost() {
        return beverage.cost() + 1.0;
    }
}
scala 复制代码
// 定义一个具体装饰者,表示奶油
public class Cream extends CondimentDecorator {

    public Cream(Beverage beverage) {
        super(beverage);
    }

    // 返回奶油和饮料的价格
    @Override
    public double cost() {
        return beverage.cost() + 2.0;
    }
}
scss 复制代码
// 测试代码
public class Test {

    public static void main(String[] args) {
        // 创建一个咖啡对象
        Beverage coffee = new Coffee();
        System.out.println(coffee.getDescription() + " = " + coffee.cost());

        // 给咖啡加糖和奶油
        coffee = new Sugar(coffee);
        coffee = new Cream(coffee);
        System.out.println(coffee.getDescription() + " = " + coffee.cost());

        // 创建一个茶对象
        Beverage tea = new Tea();
        System.out.println(tea.getDescription() + " = " + tea.cost());

        // 给茶加糖
        tea = new Sugar(tea);
        System.out.println(tea.getDescription() + " = " + tea.cost());
    }
}
ini 复制代码
// 输出结果
咖啡 = 10.0
咖啡 + Sugar + Cream = 13.0
茶 = 5.0
茶 + Sugar = 6.0

从上面的代码可以看出,Java可以使用抽象类和接口来实现装饰者模式,通过继承和组合的方式来给对象增加新的功能。Java还有一些特性可以简化装饰者模式的实现,例如匿名内部类,Lambda表达式等。

3.3 C++实现

C++是一种面向对象的编程语言,它也可以用来实现装饰者模式,下面是一个简单的例子:

csharp 复制代码
// 定义一个抽象组件,表示饮料
class Beverage {
public:
    // 返回饮料的描述
    virtual std::string getDescription() = 0;
    // 返回饮料的价格
    virtual double cost() = 0;
    // 虚析构函数,方便子类析构
    virtual ~Beverage() {}
};

// 定义一个具体组件,表示咖啡
class Coffee : public Beverage {
public:
    std::string getDescription() override {
        return "咖啡";
    }

    double cost() override {
        return 10.0;
    }
};

// 定义一个具体组件,表示茶
class Tea : public Beverage {
public:
    std::string getDescription() override {
        return "茶";
    }

    double cost() override {
        return 5.0;
    }
};

// 定义一个抽象装饰者,表示调料
class CondimentDecorator : public Beverage {
protected:
    // 持有一个被装饰者的指针
    Beverage* beverage;

public:
    CondimentDecorator(Beverage* beverage) : beverage(beverage) {}

    // 返回调料和饮料的描述
    std::string getDescription() override {
        return beverage->getDescription() + " + " + typeid(*this).name();
    }

    // 虚析构函数,释放被装饰者的内存
    virtual ~CondimentDecorator() {
        delete beverage;
    }
};

// 定义一个具体装饰者,表示糖
class Sugar : public CondimentDecorator {
public:
    Sugar(Beverage* beverage) : CondimentDecorator(beverage) {}

    // 返回糖和饮料的价格
    double cost() override {
        return beverage->cost() + 1.0;
    }
};

// 定义一个具体装饰者,表示奶油
class Cream : public CondimentDecorator {
public:
    Cream(Beverage* beverage) : CondimentDecorator(beverage) {}

    // 返回奶油和饮料的价格
    double cost() override {
        return beverage->cost() + 2.0;
    }
};
// 测试代码
int main() {
    // 创建一个咖啡对象
    Beverage* coffee = new Coffee();
    std::cout << coffee->getDescription() << " = " << coffee->cost() << std::endl;

    // 给咖啡加糖和奶油
    coffee = new Sugar(coffee);
    coffee = new Cream(coffee);
    std::cout << coffee->getDescription() << " = " << coffee->cost() << std::endl;

    // 创建一个茶对象
    Beverage* tea = new Tea();
    std::cout << tea->getDescription() << " = " << tea->cost() << std::endl;

    // 给茶加糖
    tea = new Sugar(tea);
    std::cout << tea->getDescription() << " = " << tea->cost() << std::endl;

    // 释放内存
    delete coffee;
    delete tea;

    return 0;
}
kotlin 复制代码
// 输出结果
咖啡 = 10
咖啡 + class Sugar + class Cream = 13
茶 = 5
茶 + class Sugar = 6

从上面的代码可以看出,C++可以使用类和虚函数来实现装饰者模式,通过继承和组合的方式来给对象增加新的功能。C++还有一些特性可以简化装饰者模式的实现,例如模板类,智能指针等。

四、优缺点、使用场景

4.1 优缺点

装饰者模式的优点有:

  • 可以在不修改原有对象的基础上给对象增加新的功能,遵循开闭原则。
  • 可以使用多个具体装饰者来组合出不同的功能,实现灵活性和可扩展性。
  • 可以根据需要动态地增加或删除功能,而不影响其他对象。
  • 可以保持被装饰对象的类型和接口不变,对客户端透明。

装饰者模式的缺点有:

  • 会增加系统的复杂度和类的数量,可能导致代码难以理解和维护。
  • 会增加对象之间的耦合度,可能引入循环引用的问题。
  • 会影响被装饰对象的效率和性能。

4.2 使用场景

装饰者模式适用于以下场景:

  • 当需要给一个现有的类添加额外的职责或功能时,而又不能修改原有类的代码或结构时。
  • 当需要给一个对象动态地增加或删除功能时,而又不想影响其他对象或系统时。
  • 当需要给一个对象组合出多种不同的功能时,而又不想使用大量的子类或条件语句时。

五、和其他相似设计模式比较

设计模式 目的 使用场景 优点 缺点
装饰者模式 动态地扩展目标对象的功能 给组件添加样式或动画效果 实现开闭原则,灵活地增加功能 增加了子类的数量,可能增加复杂度
适配器模式 使不兼容的接口能够协作 封装第三方库或API 实现接口转换,解决兼容问题 不能改变被适配对象的行为
代理模式 控制对目标对象的访问 实现缓存、拦截、验证等功能 实现访问控制,增加安全性和效率 可能降低目标对象的性能
相关推荐
哆啦A梦的口袋呀8 小时前
基于Python学习《Head First设计模式》第十章 状态模式
学习·设计模式
newki13 小时前
【NDK】项目演示-Android串口的封装工具库以及集成的几种思路
android·c++·app
电子科技圈13 小时前
IAR开发平台升级Arm和RISC-V开发工具链,加速现代嵌入式系统开发
arm开发·嵌入式硬件·设计模式·性能优化·软件工程·代码规范·risc-v
昕冉14 小时前
利用 Axrue9 中继器实现表格数据的查询
设计模式·设计
摘星编程18 小时前
建造者模式深度解析与实战应用
设计模式·建造者模式·代码重构·对象构建·java实战
周某某~19 小时前
六.原型模式
java·设计模式·原型模式
周某某~20 小时前
五.建造者模式
java·设计模式·建造者模式
iOS阿玮1 天前
排雷,金融类型产品14天封号倒计时脱困。
uni-app·app·apple
qqxhb1 天前
零基础设计模式——行为型模式 - 命令模式
java·设计模式·go·命令模式
SAP-nkGavin1 天前
ABAP设计模式之---“童子军法则(The Boy Scout Rule)”
设计模式·sap·abap