零基础设计模式——结构型模式 - 装饰器模式

发布于:2025-05-31 ⋅ 阅读:(28) ⋅ 点赞:(0)

第三部分:结构型模式 - 装饰器模式 (Decorator Pattern)

在学习了组合模式如何将对象组合成树形结构后,我们来探讨装饰器模式。装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

  • 核心思想:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

装饰器模式 (Decorator Pattern)

“动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。” (Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.)

想象一下你在一家咖啡店点咖啡。你可以从一杯基础咖啡(如浓缩咖啡 Espresso)开始,然后选择添加各种调料(如牛奶、糖、巧克力酱、奶油等)。每加一种调料,咖啡的风味和价格都会改变,但它本质上还是一杯咖啡。

  • 基础对象 (Component):一杯简单的咖啡(Coffee)。
  • 装饰器 (Decorator):调料(MilkDecorator, SugarDecorator, ChocolateDecorator)。它们包装了咖啡对象,并添加了新的行为(如改变口味、增加成本)。

如果使用继承来实现,比如 CoffeeWithMilk, CoffeeWithMilkAndSugar, CoffeeWithChocolate… 调料的组合会非常多,导致类的数量爆炸。装饰器模式通过对象组合(包装)的方式,动态地给对象添加职责,避免了这个问题。

1. 目的 (Intent)

装饰器模式的主要目的:

  1. 动态添加职责:在不修改原有对象代码的情况下,为对象在运行时动态地增加新的功能。
  2. 避免子类爆炸:当需要扩展一个类的功能,并且这些功能可以有多种组合时,使用装饰器模式比创建大量的子类更灵活、更易于管理。
  3. 保持接口一致性:装饰后的对象与原对象具有相同的接口,客户端可以像使用原对象一样使用装饰后的对象。

2. 生活中的例子 (Real-world Analogy)

  • 给咖啡加调料 (经典例子):

    • 基础咖啡 (Component): SimpleCoffee
    • 调料装饰器 (Decorator): MilkDecorator, SugarDecorator, WhipDecorator
    • 你可以点一杯 SimpleCoffee,然后用 MilkDecorator 包装它得到牛奶咖啡,再用 SugarDecorator 包装牛奶咖啡得到加糖的牛奶咖啡。
  • 给手机贴膜、加手机壳

    • 裸机手机 (Component): Phone
    • 手机膜 (Decorator): ScreenProtectorDecorator
    • 手机壳 (Decorator): PhoneCaseDecorator
    • 你可以给手机贴膜,再加个手机壳,每一步都增加了新的功能(防刮、防摔),但手机的核心功能(打电话、上网)不变。
  • 穿衣服

    • 人 (Component): Person
    • 衬衫 (Decorator): ShirtDecorator
    • 外套 (Decorator): JacketDecorator
    • 帽子 (Decorator): HatDecorator
    • 你可以一层一层地穿衣服,每件衣服都增加了保暖或美观等功能。
  • Java I/O 流

    • InputStream (Component) 是一个抽象组件。
    • FileInputStream (ConcreteComponent) 是一个具体组件,用于从文件读取字节。
    • BufferedInputStream (Decorator) 包装一个 InputStream,为其添加缓冲功能,提高读取效率。
    • DataInputStream (Decorator) 包装一个 InputStream,为其添加读取基本数据类型(如 int, float)的功能。
      你可以这样组合:new DataInputStream(new BufferedInputStream(new FileInputStream("file.txt")))

3. 结构 (Structure)

装饰器模式通常包含以下角色:

  1. Component (组件接口):定义一个对象接口,可以给这些对象动态地添加职责。这是被装饰者和装饰者的共同父类或接口。
  2. ConcreteComponent (具体组件):定义了一个具体的对象,可以给这个对象添加一些职责。即被装饰的原始对象。
  3. Decorator (装饰器抽象类):持有一个 Component 对象的实例,并定义一个与 Component 接口一致的接口。它通常是一个抽象类,将请求转发给其 Component 对象,并可以在转发请求之前或之后执行某些附加操作。
  4. ConcreteDecorator (具体装饰器):向组件添加职责。它继承自 Decorator,并负责给 Component 对象添加新的功能。
    在这里插入图片描述
    工作流程
  • 客户端创建一个 ConcreteComponent 对象。
  • 然后,客户端可以用一个或多个 ConcreteDecorator 对象来包装(装饰)这个 ConcreteComponent 对象。
  • 每个 ConcreteDecorator 都持有一个指向它所包装的 Component 对象(可能是 ConcreteComponent 或另一个 ConcreteDecorator)的引用。
  • 当客户端调用最外层装饰器的 operation() 方法时,该调用会沿着装饰链向内层传递,直到最初的 ConcreteComponentoperation() 被调用。然后,结果会从内向外依次经过每个装饰器的附加行为处理后返回给客户端。

4. 适用场景 (When to Use)

  • 在不想增加很多子类的情况下,扩展一个类的功能或给一个类添加附加职责。
  • 需要动态地给一个对象添加功能,这些功能可以再动态地撤销。
  • 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
  • 当一个对象的某些行为应该被可选地增强时。

5. 优缺点 (Pros and Cons)

优点:

  1. 比继承更灵活:装饰器模式提供了比继承更有弹性的扩展对象功能的方式。可以动态地添加和删除职责。
  2. 避免了子类爆炸:对于多种独立功能的组合,不需要创建大量的子类。
  3. 装饰器和被装饰对象可以独立变化:只要接口保持一致,可以自由组合。
  4. 符合开闭原则:可以在不修改现有代码(ConcreteComponent)的情况下添加新功能(通过新的 ConcreteDecorator)。

缺点:

  1. 产生许多小对象:使用装饰器模式会产生比使用继承更多的小对象,这些对象在某种程度上看起来都一样,增加了系统的复杂性。
  2. 调试困难:由于涉及到多个对象的包装,如果装饰链过长,调试时跟踪代码执行路径可能会比较困难。
  3. 接口一致性的要求:装饰器和被装饰者必须实现相同的接口。如果被装饰者的接口发生变化,所有装饰器也可能需要相应修改。

6. 实现方式 (Implementations)

让我们以文本格式化为例。我们有一个基础的文本对象,然后可以动态地给它添加粗体、斜体、下划线等格式。

组件接口 (Text - Component)
// text.go (Component)
package formatting

// Text 组件接口
type Text interface {
	GetContent() string
}
// Text.java (Component)
package com.example.formatting;

// 组件接口
public interface Text {
    String getContent();
}
具体组件 (PlainText - ConcreteComponent)
// plain_text.go (ConcreteComponent)
package formatting

// PlainText 具体组件
type PlainText struct {
	content string
}

func NewPlainText(content string) *PlainText {
	return &PlainText{content: content}
}

func (pt *PlainText) GetContent() string {
	return pt.content
}
// PlainText.java (ConcreteComponent)
package com.example.formatting;

public class PlainText implements Text {
    private String content;

    public PlainText(String content) {
        this.content = content;
    }

    @Override
    public String getContent() {
        return this.content;
    }
}
装饰器抽象类 (TextDecorator - Decorator)
// text_decorator.go (Decorator)
package formatting

// TextDecorator 装饰器抽象类
type TextDecorator struct {
	wrappedText Text // 持有被包装的 Text 对象
}

func NewTextDecorator(text Text) *TextDecorator {
	return &TextDecorator{wrappedText: text}
}

// GetContent 默认实现是调用被包装对象的 GetContent
// 具体装饰器会重写这个方法以添加自己的行为
func (td *TextDecorator) GetContent() string {
	if td.wrappedText != nil {
		return td.wrappedText.GetContent()
	}
	return ""
}
// TextDecorator.java (Decorator)
package com.example.formatting;

// 装饰器抽象类
public abstract class TextDecorator implements Text {
    protected Text wrappedText; // 持有被包装的 Text 对象

    public TextDecorator(Text text) {
        this.wrappedText = text;
    }

    // 默认实现是调用被包装对象的 getContent
    // 具体装饰器会重写这个方法以添加自己的行为
    @Override
    public String getContent() {
        return wrappedText.getContent();
    }
}
具体装饰器 (BoldDecorator, ItalicDecorator, UnderlineDecorator - ConcreteDecorator)
// bold_decorator.go (ConcreteDecorator)
package formatting

import "fmt"

// BoldDecorator 具体装饰器
type BoldDecorator struct {
	TextDecorator // 嵌入 TextDecorator
}

func NewBoldDecorator(text Text) *BoldDecorator {
	return &BoldDecorator{TextDecorator: *NewTextDecorator(text)}
}

func (bd *BoldDecorator) GetContent() string {
	originalContent := bd.wrappedText.GetContent() // 调用父类或被包装对象的 GetContent
	return fmt.Sprintf("<b>%s</b>", originalContent)
}

// italic_decorator.go (ConcreteDecorator)
package formatting

import "fmt"

// ItalicDecorator 具体装饰器
type ItalicDecorator struct {
	TextDecorator
}

func NewItalicDecorator(text Text) *ItalicDecorator {
	return &ItalicDecorator{TextDecorator: *NewTextDecorator(text)}
}

func (id *ItalicDecorator) GetContent() string {
	return fmt.Sprintf("<i>%s</i>", id.wrappedText.GetContent())
}

// underline_decorator.go (ConcreteDecorator)
package formatting

import "fmt"

// UnderlineDecorator 具体装饰器
type UnderlineDecorator struct {
	TextDecorator
}

func NewUnderlineDecorator(text Text) *UnderlineDecorator {
	return &UnderlineDecorator{TextDecorator: *NewTextDecorator(text)}
}

func (ud *UnderlineDecorator) GetContent() string {
	return fmt.Sprintf("<u>%s</u>", ud.wrappedText.GetContent())
}
// BoldDecorator.java (ConcreteDecorator)
package com.example.formatting;

public class BoldDecorator extends TextDecorator {
    public BoldDecorator(Text text) {
        super(text);
    }

    @Override
    public String getContent() {
        return "<b>" + super.getContent() + "</b>"; // 调用父类的getContent获取原始(或已部分装饰的)文本
    }
}

// ItalicDecorator.java (ConcreteDecorator)
package com.example.formatting;

public class ItalicDecorator extends TextDecorator {
    public ItalicDecorator(Text text) {
        super(text);
    }

    @Override
    public String getContent() {
        return "<i>" + super.getContent() + "</i>";
    }
}

// UnderlineDecorator.java (ConcreteDecorator)
package com.example.formatting;

public class UnderlineDecorator extends TextDecorator {
    public UnderlineDecorator(Text text) {
        super(text);
    }

    @Override
    public String getContent() {
        return "<u>" + super.getContent() + "</u>";
    }
}
客户端使用
// main.go (示例用法)
/*
package main

import (
	"./formatting"
	"fmt"
)

func main() {
	// 1. 创建一个普通文本对象
	plain := formatting.NewPlainText("Hello, Decorator Pattern!")
	fmt.Printf("Original: %s\n", plain.GetContent())

	// 2. 用 BoldDecorator 装饰它
	boldText := formatting.NewBoldDecorator(plain)
	fmt.Printf("Bold: %s\n", boldText.GetContent())

	// 3. 再用 ItalicDecorator 装饰已经加粗的文本
	boldItalicText := formatting.NewItalicDecorator(boldText)
	fmt.Printf("Bold and Italic: %s\n", boldItalicText.GetContent())

	// 4. 也可以直接链式创建
	complexFormattedText := formatting.NewUnderlineDecorator(
		formatting.NewItalicDecorator(
			formatting.NewBoldDecorator(
				formatting.NewPlainText("All formats!"),
			),
		),
	)
	fmt.Printf("All Formats: %s\n", complexFormattedText.GetContent())

    // 5. 只加下划线
    underlineOnly := formatting.NewUnderlineDecorator(formatting.NewPlainText("Underline only"))
    fmt.Printf("Underline Only: %s\n", underlineOnly.GetContent())
}
*/
// Main.java (示例用法)
/*
package com.example;

import com.example.formatting.*;

public class Main {
    public static void main(String[] args) {
        // 1. 创建一个普通文本对象
        Text plain = new PlainText("Hello, Decorator Pattern!");
        System.out.println("Original: " + plain.getContent());

        // 2. 用 BoldDecorator 装饰它
        Text boldText = new BoldDecorator(plain);
        System.out.println("Bold: " + boldText.getContent());

        // 3. 再用 ItalicDecorator 装饰已经加粗的文本
        Text boldItalicText = new ItalicDecorator(boldText);
        System.out.println("Bold and Italic: " + boldItalicText.getContent());

        // 4. 也可以直接链式创建
        Text complexFormattedText = new UnderlineDecorator(
                new ItalicDecorator(
                        new BoldDecorator(
                                new PlainText("All formats!")
                        )
                )
        );
        System.out.println("All Formats: " + complexFormattedText.getContent());

        // 5. 只加下划线
        Text underlineOnly = new UnderlineDecorator(new PlainText("Underline only"));
        System.out.println("Underline Only: " + underlineOnly.getContent());
    }
}
*/

7. 与代理模式的区别

装饰器模式和代理模式(Proxy Pattern)在结构上有些相似,因为它们都包装了另一个对象并向其委托请求。但它们的意图不同:

  • 装饰器模式 (Decorator)

    • 意图:动态地为对象添加额外的职责或行为。关注的是“增强”对象。
    • 接口:装饰器和被装饰对象实现相同的接口。
    • 客户端:客户端通常知道它正在使用一个装饰过的对象(尽管它通过通用接口与之交互)。客户端通常负责创建装饰链。
  • 代理模式 (Proxy)

    • 意图:控制对另一个对象的访问。关注的是“访问控制”或“管理”对象,如延迟加载、权限检查、远程访问等。
    • 接口:代理和真实主题(被代理对象)也实现相同的接口。
    • 客户端:客户端通常不知道它是在与代理交互还是与真实主题交互,代理对客户端是透明的。代理通常由工厂或依赖注入框架创建和管理。

简单来说:

  • 装饰器:我要给这个对象“穿上新衣服”(增加功能)。
  • 代理:我要找个人“替”这个对象办事,或者“管着”这个对象(控制访问)。

8. 总结

装饰器模式是一种非常实用的设计模式,它允许我们在运行时动态地为一个对象添加新的功能,而无需修改其原始类的代码。通过将对象包装在一系列装饰器中,我们可以灵活地组合不同的行为。这种模式在需要多种功能组合且不希望通过大量子类来实现时特别有用,如Java I/O库中的流处理。

记住它的核心:动态添加职责,灵活组合,避免子类爆炸