Java装饰模式

装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
        

装饰模式的特点;

(1) 装饰对象和真实对象有相同的接口。这样客户端对象就可以以和真实对象相同的方式和装饰对象交互。
(2) 装饰对象包含一个真实对象的索引(reference)
(3) 装饰对象接受所有的来自客户端的请求。它把这些请求转发给真实的对象。
(4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。

装饰模式 VS 继承

 装饰模式                                                                 继承
用来扩展特定对象的功能                     用来扩展一类对象的功能
不需要子类                                             需要子类
动态地                                                     静态地
运行时分配职责                                     编译时分派职责
防止由于子类而导致的复杂和混乱     导致很多子类产生,在一些场合,报漏类的层次
更多的灵活性                                         缺乏灵活性

例子:

        让我们重新返回我们在工厂方法和单例模式log实用工具上,我们的模式主要由Logger 接口和两个它的实现类??FileLogger和ConsoleLogger??分别把信息出力到一个文件和屏幕中。另外,还有包括工厂方法的LoggerFactory类。
LoggerFactory没有出现在下图中,主要是因为它和现在讨论的例子没有直接联系。

让我们想象一些客户端需要以超出Logger Utility现在所提供的新的方式出力信息,客户端需要下面两种特征;
(1) 把出力的信息传唤为HTML文档
(2) 对出力信息进行逻辑转化的简单加密,在面向对象的设计中,不改变现存的类的代码,可以应用继承来增加新的功能。例如,子类化现在的类重载它的方法来增加所需要的新功能。

应用继承,我们要子类化FileLogger和ConsoleLogger类来增加新的功能,会有下面的一组新的子类:
子类 父类 功能
HTMLFileLogger FileLogger 转化出力信息为HTML文档,并存入一个Log文件
HTMLConsLogger ConsoleLogger 转化出力信息为HTML文档,并显示在屏幕上
EncFileLogger FileLogger 加密出力信息,并存入一个Log文件
EncConsLogger ConsoleLogger 加密出力信息,并显示在屏幕上

从类图可以看到,为了实现新的功能加入了一组新的子类。如果我们还有其他的Logger类型(例如:DBLogger出力信息到数据库中),这样会有更多子类。当一个新的特性需要被加入,子类的数量会有成倍数的增长,同时我们会有一个庞大的类层次。

装饰模式使我们从这种情景中解脱出来,装饰模式推荐通过对象的合成而不是继承来包装一个对象扩展它的功能。
应用装饰模式,让我们为Logger Utility定义一个有下列特征的默认根装饰类LoggerDecorator:

(1) LoggerDecorator包括一个Logger实例的引用。这个引用指向它包含的Logger对象。
(2) LoggerDecorator实现Logger借口、提供Log方法的基本的默认实现,他只是简单的转发调用给它包含的Logger 对象。每一个LoggerDecorator子类保证定义log方法。

 

public class LoggerDecorator implements Logger {
	Logger logger;

	public LoggerDecorator(Logger inp_logger) {
		logger = inp_logger;
	}

	public void log(String DataLine) {
		/*
		 * Default implementation to be overriden by subclasses.
		 */
		logger.log(DataLine);
	}
}// end of class


 

每一个logger的装饰定义log方法使很重要的,因为装饰对象必须提供和它包装的对象相同的借口。当客户端创建一个装饰类的实例,客户端以与装饰类交互方式和客户端与拥有相同接口原对象的交互方式是一致的。
让我们定义LoggerDecorator的两个子类,HTMLLogger和EncryptLogger。 #p# 具体的Logger 装饰类

HTMLLogger

HTMLLogger重载了log方法的默认实现。在log方法中,装饰类把出力信息转化为HTML文档,并且发送给可以出力的Logger实例。

 

public class HTMLLogger extends LoggerDecorator {
	public HTMLLogger(Logger inp_logger) {
		super(inp_logger);
	}

	public void log(String DataLine) {
		/*
		 * Added functionality
		 */
		DataLine = makeHTML(DataLine);
		/*
		 * Now forward the encrypted text to the FileLogger for storage
		 */
		logger.log(DataLine);
	}

	public String makeHTML(String DataLine) { 
		/* 
		Make it into an HTML document. 
		*/ 
		DataLine = "" + """ + DataLine + 
		" + ""; 
		return DataLine; 
	}
}// end of class


EncryptLogger

与HTMLLogger相似,EncryptLogger重载了log方法,在log方法中,EncryptLogger通过简单的将字符位置向右转移一位实现了加密逻辑,并且发送给可以出力的Logger实例。

public class EncryptLogger extends LoggerDecorator {
	public EncryptLogger(Logger inp_logger) {
		super(inp_logger);
	}

	public void log(String DataLine) {
		/*
		 * Added functionality
		 */
		DataLine = encrypt(DataLine);
		/*
		 * Now forward the encrypted text to the FileLogger for storage
		 */
		logger.log(DataLine);
	}

	public String encrypt(String DataLine) { 
		/* 
		Apply simple encryption by Transposition… 
		Shift all characters by one position. 
		*/ 
		DataLine = DataLine.substring(DataLine.length() − 1) + 
		DataLine.substring(0, DataLine.length() − 1); 
		return DataLine; 
	}
}// end of class


 

为了使用新设计装饰对象,客户端需要:

(1) 使用LoggerFactory工厂方法创建一个合适的Logger实例(FileLogger/ConsoleLogger)。
(2) 把第一步中创建的Logger实例作为参数转递给新创建的合适的LoggerDecorator实例的构造函数。
(3) 调用LoggerDecorator实例上的方法,

 

class DecoratorClient {
	public static void main(String[] args) {
		LoggerFactory factory = new LoggerFactory();
		Logger logger = factory.getLogger();
		HTMLLogger hLogger = new HTMLLogger(logger);
		// the decorator object provides the same interface.
		hLogger.log("A Message to Log");
		EncryptLogger eLogger = new EncryptLogger(logger);
		eLogger.log("A Message to Log");
	}
}// End of class

 

增加新的信息出力类型

在Logging Utility实例中,应用装饰模式对比使用继承不会因为类层次的增长而导致大量的子类,我们还有另外的Logger类型:DBLogger??出力信息到数据库中。为了将信息转化HTML格式或在出力到数据库以前对信息进行加密,客户端只需遵从上面提到的步骤,因为DBLogger是一种Logger类型,它可以作为构造函数的参数传递给HTMLLogger或EncryptLogger中任何一个类。

增加新的装饰

从例子中可以看到,LoggerDecorator实例包含了一个Logger类型了对象实例,在转发请求给Logger对象实例以前或以后,增加新的功能。因为LoggerDecorator类实现了Logger接口,LoggerDecorator实例或它的任何一个子类都可以作为一个Logger类型。因此LoggerDecortator包含它的任何子类的一个实例,并且将请求转发给它/。一般的一个装饰对象可以包含另一个装饰对象,并且可以向它转发请求。通过这种方式,新的装饰类,新的功能可以通过包装现存的装饰类来实现。

 

<think>好的,用户想了解Java中的装饰器模式,特别是示例和应用场景。首先,我需要回忆装饰器模式的定义和核心概念。装饰器模式属于结构型设计模式,允许动态地给对象添加职责,同时不改变其结构。这通常通过将对象包装在装饰中来实现。 用户可能已经有一些面向对象编程的基础,但可能对设计模式不太熟悉。我需要用简单明了的语言解释,并给出具体的Java示例。根据提供的引用,尤其是引用[3]提到Java的IO流库大量使用了装饰器模式,比如BufferedReader装饰InputStreamReader,这可以作为经典例子。 接下来,我需要解释装饰器模式的结构,包括组件接口、具体组件、装饰器基和具体装饰器。然后,通过一个实际的代码示例来展示如何实现。例如,咖啡订单系统,基础咖啡可以添加牛奶、糖等调料,这样的场景适合用装饰器模式扩展功能,而避免爆炸问题。 应用场景方面,引用[4]和[5]提到动态扩展功能、避免子膨胀、运行时添加职责等。需要将这些点具体化,比如IO流、GUI组件、日志增强等。同时,要对比装饰器模式和继承的区别,强调其灵活性。 可能用户会混淆装饰器模式和代理模式,或者装饰器模式与继承的优缺点,所以需要明确说明。此外,用户可能想知道在实际开发中何时选择装饰器模式,所以应用场景部分要具体,并结合Java标准库的例子,如IO流中的各种装饰。 最后,生成的相关问题要覆盖定义、优缺点、与其他模式的区别以及实际应用,这样用户可以有更全面的了解。需要确保引用正确,比如引用[1]、[3]、[4]、[5]都涉及装饰器模式的应用,要正确对应到回答中的各个部分。</think>### Java 装饰器模式详解 #### 一、模式定义 装饰器模式(Decorator Pattern)是一种结构型设计模式,通过将对象包裹在装饰中,**动态地为对象添加新功能**,且不改变原有结构。这种模式实现了功能扩展的层次化,比继承更灵活[^5]。 #### 二、模式结构 1. **组件接口**(Component):定义核心功能接口(如`Coffee`) 2. **具体组件**(ConcreteComponent):基础实现(如`SimpleCoffee`) 3. **装饰器基**(Decorator):继承组件接口,持有组件对象 4. **具体装饰器**(ConcreteDecorator):添加具体扩展功能(如`MilkDecorator`) #### 三、代码示例 ```java // 组件接口 interface Coffee { String getDescription(); double getCost(); } // 具体组件 class SimpleCoffee implements Coffee { public String getDescription() { return "基础咖啡"; } public double getCost() { return 10.0; } } // 装饰器基 abstract class CoffeeDecorator implements Coffee { protected Coffee decoratedCoffee; public CoffeeDecorator(Coffee coffee) { this.decoratedCoffee = coffee; } } // 具体装饰器1:加牛奶 class MilkDecorator extends CoffeeDecorator { public MilkDecorator(Coffee coffee) { super(coffee); } public String getDescription() { return decoratedCoffee.getDescription() + "+牛奶"; } public double getCost() { return decoratedCoffee.getCost() + 2.0; } } // 具体装饰器2:加糖 class SugarDecorator extends CoffeeDecorator { public SugarDecorator(Coffee coffee) { super(coffee); } public String getDescription() { return decoratedCoffee.getDescription() + "+糖"; } public double getCost() { return decoratedCoffee.getCost() + 1.0; } } // 使用示例 public class Main { public static void main(String[] args) { Coffee order = new SimpleCoffee(); order = new MilkDecorator(order); // 加牛奶 order = new SugarDecorator(order); // 加糖 System.out.println(order.getDescription() + " 总价:" + order.getCost()); // 输出:基础咖啡+牛奶+糖 总价:13.0 } } ``` #### 四、应用场景 1. **动态扩展功能**:需要运行时添加或撤销功能时(如Java IO流中的缓冲/加密装饰)[^3] 2. **避免子爆炸**:当通过继承会导致大量子时(如不同组合的咖啡调料)[^4] 3. **核心功能与扩展分离**:保持核心简洁,通过装饰器叠加功能(如日志增强、事务处理)[^5] 4. **标准库典型应用**: - `BufferedInputStream` 装饰 `FileInputStream` - `GZIPOutputStream` 装饰 `FileOutputStream` - `BufferedReader` 装饰 `InputStreamReader`[^1] #### 五、模式优势 | 优点 | 说明 | |-------|-------| | **灵活扩展** | 比继承更灵活,支持运行时动态组合功能 | | **单一职责** | 每个装饰器只关注特定功能扩展 | | **开闭原则** | 不修改原有代码即可扩展功能 | | **透明包装** | 装饰后的对象仍可被当作原型使用 | $$ \text{装饰器模式复杂度公式:} \quad O(n) = \sum_{k=1}^{m} C_k \quad (m=\text{装饰层数}) $$ §§ 相关问题 §§ 1. 装饰器模式与继承的主要区别是什么? 2. Java IO流中如何体现装饰器模式的层级结构? 3. 装饰器模式会造成哪些性能损耗? 4. 装饰器模式和代理模式的核心区别? 5. 如何防止装饰器被多次嵌套导致系统复杂化? : Java标准库中装饰器模式在IO系统的应用 : Java IO流的装饰器实现 : 装饰器模式的应用场景说明 : 装饰器模式的核心特性分析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值