何时使用修饰模式?

我正在检查我的设计模式,有一个模式我还没有认真地在我的编码中使用,那就是修饰模式。

我理解这种模式,但我想知道的是,在现实世界中,修饰模式是最好的/最佳的/优雅的解决方案。在特殊情况下,修饰模式的使用非常方便。

谢谢。

42930 次浏览

这个修饰模式在数据流中使用得很多,你可以用一个数据流来包装一个数据流,以获得额外的功能。我见过这个。网络框架——据我所知,这种情况发生在其他地方。我最喜欢的是围绕 FileStream 使用 GZipStream,用于增加压缩。

我最近在一个 web 服务中使用了这个修饰模式,它使用了以下 CommandProcessor 接口:

public Command receive(Request request);
public Response execute(Command command);
public void respond(Response response);

基本上,CommandProcessor 接收请求并创建适当的 Command,执行 Command 并创建适当的 Response,然后发送 Response。当我想添加计时并记录它时,我创建了一个 TimerDecorator,它使用一个现有的 CommandProcessor 作为它的组件。TimerDecorator 实现 CommandProcessor 接口,但只是添加计时,然后调用它的目标,即真正的 CommandProcessor。就像这样:

public class TimerDecorator implements CommandProcessor {
private CommandProcessor target;
private Timer timer;


public TimerDecorator(CommandProcessor processor) {
this.target = processor;
this.timer = new Timer();
}


public Command receive(Request request) {
this.timer.start();
return this.target.receive(request);
}


public Response execute(Command command) {
return this.target.execute(command);
}


public void respond(Response response) {
this.target.response(response);
this.timer.stop();
// log timer
}


}

因此,真正的 CommandProcessor 包装在 TimerDecorator 中,我可以像对待目标 CommandProcessor 一样对待 TimerDecorator,但是现在添加了计时逻辑。

Decorator模式用于向现有的 object(即在运行时已经实例化的类)添加额外的功能,而不是对象的 class和/或子类。通过对对象的类进行子类化,可以很容易地向整个对象类添加功能,但是不可能以这种方式扩展单个对象。有了这个修饰模式,你可以向一个对象添加功能,而不修改其他类似的对象。

在 Java 中,一个经典的修饰模式是 Java I/O Streams 实现。

FileReader       frdr = new FileReader(filename);
LineNumberReader lrdr = new LineNumberReader(frdr);

前面的代码创建一个读取器—— lrdr——读取文件并跟踪行号。第1行创建一个文件读取器(frdr) ,第2行添加行号跟踪。

实际上,我强烈建议您查看 Java I/O 类的 Java 源代码。

装潢师很简单,但是非常强大。它是实现关注点分离的关键,也是开闭原则的重要工具。举一个常见的订购产品的例子:

IOrderGateway
{
void PlaceOrder(Order order);
{

主要实施方式: AmazonAffiliateOrderGateway

可能的装潢师有:

  • IncrementPerformanceCounterOrderGateway
  • QueueOrderForLaterOnTimeoutOrderGateway
  • EmailOnExceptionOrderGateway
  • InterceptTestOrderAndLogOrderGateway

来自 给你的一个更详细的例子说明了一种装饰工具,它可以为在完成订单时使用礼品卡创建的订单保存联系人:

class OrderWithGiftCardGateway extends OrderGatewayDecorator
{
...


public function createOrder(CreateOrderRequest $order)
{
if ($this->containsGiftCard($order))
{
$this->addContactToFolder($order);
}


return parent::createOrder($order);
}
}
  1. 动态和透明地向各个对象添加责任。
  2. 可以撤销的责任。
  3. 当通过子类化进行扩展是不切实际的时候。有时可能会有大量的独立扩展,并且会产生大量的子类来支持每种组合。

Decorator 模式在运行时动态地更改对象的功能,而不影响对象的现有功能。

关键用例:

  1. 动态添加其他功能/职责
  2. 动态删除功能/责任
  3. 避免过多的子类化 以增加额外的责任。

缺点:

  1. 过度使用开放式封闭原则 (开放供扩展,关闭供修改)。在最不可能更改代码的地方谨慎地使用此特性。
  2. 太多的小类会增加 维修费用

一个真实世界的例子: 计算饮料的价格,它可能包含多种口味。

abstract class Beverage {
protected String name;
protected int price;
public Beverage(){


}
public  Beverage(String name){
this.name = name;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
protected void setPrice(int price){
this.price = price;
}
protected int getPrice(){
return price;
}
protected abstract void decorateBeverage();


}
class Tea extends Beverage{
public Tea(String name){
super(name);
setPrice(10);
}
public void decorateBeverage(){
System.out.println("Cost of:"+ name +":"+ price);
// You can add some more functionality
}
}
class Coffee extends Beverage{
public Coffee(String name){
super(name);
setPrice(15);
}
public void decorateBeverage(){
System.out.println("Cost of:"+ name +":"+ price);
// You can add some more functionality
}
}
abstract class BeverageDecorator extends Beverage {
protected Beverage beverage;
public BeverageDecorator(Beverage beverage){
this.beverage = beverage;
setName(beverage.getName()+"+"+getDecoratedName());
setPrice(beverage.getPrice()+getIncrementPrice());
}
public void decorateBeverage(){
beverage.decorateBeverage();
System.out.println("Cost of:"+getName()+":"+getPrice());
}
public abstract int getIncrementPrice();
public abstract String getDecoratedName();
}
class SugarDecorator extends BeverageDecorator{
public SugarDecorator(Beverage beverage){
super(beverage);
}
public void decorateBeverage(){
super.decorateBeverage();
decorateSugar();
}
public void decorateSugar(){
System.out.println("Added Sugar to:"+beverage.getName());
}
public int getIncrementPrice(){
return 5;
}
public String getDecoratedName(){
return "Sugar";
}
}
class LemonDecorator extends BeverageDecorator{
public LemonDecorator(Beverage beverage){
super(beverage);
}
public void decorateBeverage(){
super.decorateBeverage();
decorateLemon();
}
public void decorateLemon(){
System.out.println("Added Lemon to:"+beverage.getName());
}
public int getIncrementPrice(){
return 3;
}
public String getDecoratedName(){
return "Lemon";
}
}


public class VendingMachineDecorator {
public static void main(String args[]){
Beverage beverage = new SugarDecorator(new LemonDecorator(new Tea("Assam Tea")));
beverage.decorateBeverage();
beverage = new SugarDecorator(new LemonDecorator(new Coffee("Cappuccino")));
beverage.decorateBeverage();
}
}

产出:

Cost of:Assam Tea:10
Cost of:Assam Tea+Lemon:13
Added Lemon to:Assam Tea
Cost of:Assam Tea+Lemon+Sugar:18
Added Sugar to:Assam Tea+Lemon
Cost of:Cappuccino:15
Cost of:Cappuccino+Lemon:18
Added Lemon to:Cappuccino
Cost of:Cappuccino+Lemon+Sugar:23
Added Sugar to:Cappuccino+Lemon

这个例子计算了在自动售货机中添加多种口味的饮料后的成本。

在上面的例子中:

茶的成本 = 10,柠檬 = 3,糖 = 5。如果你做糖 + 柠檬 + 茶,它的价格是18英镑。

咖啡成本 = 15,柠檬 = 3,糖 = 5。如果你做糖 + 柠檬 + 咖啡,它的价格是23英镑

通过使用相同的装饰两种饮料(茶和咖啡) ,分类的数量已经减少。如果没有修饰模式,你应该为不同的组合设置不同的子类。

组合如下:

SugarLemonTea
SugarTea
LemonTea


SugarLemonCapaccuino
SugarCapaccuino
LemonCapaccuino

等等。

两种饮品使用相同的 室内设计师,可减少分类的数目。这是可能的,因为 作文而不是 继承遗产的概念在这个模式中使用。

相关的 SE 问题:

输氧修饰模式

有用连结:

Design-pattern-decorator by dzone

通过源头制作装饰器

Oodesign article

修饰模式是由 C # 语言本身使用的。它用于修饰 C # 的 Stream I/O 类。修饰后的版本是 BufferedStream、 FileStrem、 MemoryStrem、 NetworkStream 和 CryptoStream 分类。

这些子类继承自 Stream 类,并且还包含 Stream 类的实例。

阅读更多 给你

给我举个真实的例子:

我不得不更新一个在项目中大量使用的类,但是这个类在库中,源代码丢失了。

我可以反编译整个库来创建另一个版本,或者使用装饰器设计模式,我已经这么做了。这使我能够添加缺少的功能,并简单地更新配置。

这是一个非常有用的模式时,负责任地使用。

这个特殊的案例启发我制作 这个视频,在那里我解释了这个模式。

当您想要向一个类/对象添加多个功能,并且想要在需要时灵活地添加它们时,Decorator 会派上用场。您可以扩展基类并添加您想要的更改,但是这样您可以得到许多子类,这些子类可能会让您大吃一惊。但是使用装饰器,你可以得到你想要的改变,但是仍然有一个简单的、理解的流程。 你的设计是很容易开放的扩展,但接近修改与真正简单的逻辑。 最好的例子可能是用 Java 和 C # 实现的 Stream 对象。 例如,你有一个文件流,在一个用例中,你想加密它,然后压缩它,然后记录它,最后用它做一些花哨的事情。然后在另一个类中,你决定做一些其他的事情,你想要转换它,然后加密它,然后得到计时,等等,等等。同样,在其他类中也有另一个流。 如果你想使用继承,你必须创建至少3个子类,如果需要任何其他需求,你必须添加更多的子类,在这种情况下(流) ,你将最终为小变化的子类数十个。

class EncryptZipLogStream{}
class ConvertEncryptTimeStream{}
class MoreStreamProcess{}
class OtherMoreStreamProcess{}
...

在每个用例中,您必须记住需要什么类并尝试使用它。 但是,假设您使用的是组合而不是继承,并且对于每个 Stream 进程都有 Decorator 类,那么您可以轻松地组合所需的包装器,并且以最少的工作量和最大的简单性拥有所需的任何进程。

class WhereIWannaUseUseCaseOne {
EncryptStream(ZipStream(LogStream(FileStream("file name)))));
// rest of the code to use the combines streams.
}

然后你想出另一个用例:

class WhereIWannaUseUseCaseSecond {
ConvertStream(TimeStream(LogStream(FileStream("file name)))));
// rest of the code to use the combines streams.
}

以此类推,您可以灵活地在运行时使用简单的流程和可理解的逻辑做任何您想做的事情。

GOF 定义:

动态地为一个对象附加额外的责任。

这种模式表明,类必须关闭以进行修改,但对扩展开放,可以添加新功能,而不会干扰现有功能。当我们想要为一个特定的对象而不是整个类添加特殊的功能时,这个概念是非常有用的。在这个模式中,我们使用对象组合的概念而不是继承。

通用 示例:

public abstract class Decorator<T> {
private T t;


public void setTheKernel(Supplier<? extends T> supplier) {
this.t = supplier.get();
}


public T decorate() {
return Objects.isNull(t) ? null : this.t;
}


}

执行

public interface Repository {
void save();
}


public class RepositoryImpl implements Repository{
@Override
public void save() {
System.out.println("saved successfully");
}
}


public class EnhancedRepository<T> extends Decorator<T> {
public void enhancedSave() {
System.out.println("enhanced save activated");
}
}


public class Main {
public static void main(String[] args) {
EnhancedRepository<Repository> enhanced = new EnhancedRepository<>();
enhanced.setTheKernel(RepositoryImpl::new);
enhanced.enhancedSave();
enhanced.decorate().save();
}
}