这是装饰器模式还是策略模式,或者两者都不是?

Posted

技术标签:

【中文标题】这是装饰器模式还是策略模式,或者两者都不是?【英文标题】:Is this a decorator or a strategy pattern, or neither of the two? 【发布时间】:2012-02-17 12:27:20 【问题描述】:

我有以下界面。

PowerSwitch.java

public interface PowerSwitch 
    public boolean powerOn();
    public boolean powerOff();
    public boolean isPowerOn();

上述接口应包含可以派生任何其他功能的最少方法集,以便尽可能轻松地添加其他 PowerSwitch 实现。

我想在运行时向 PowerSwitch 接口添加功能(装饰器所做的),方法是创建一个包含 PowerSwitch 实例的组合并添加新方法的类,例如两个 toggleOnOff() 方法如下。这样我只需要实现两个切换方法一次,它将适用于所有 PowerSwitch 实现。

这被认为是一种好/坏的做法吗?如果不好,还有其他建议吗?

它并不真正符合装饰器模式,因为它添加了额外的方法。它是一种策略模式,还是一种组合模式?还是它有另一个模式名称?有没有“界面装饰器”之类的东西?

PowerSwitchDecorator.java

public class PowerSwitchDecorator 
    private PowerSwitch ps;

    public PowerSwitchDecorator(PowerSwitch ps) 
        this.ps = ps;
    

    public void toggleOnOff(int millis) throws InterruptedException
        powerOn();
        Thread.sleep(millis);
        powerOff();
    

    public void toggleOnOff()
    powerOn();
    powerOff();
    

    public boolean powerOn() 
        return ps.powerOn();
    

    public boolean powerOff() 
        return ps.powerOff();
    

    public boolean isPowerOn() 
        return ps.isPowerOn();
    

【问题讨论】:

为什么PowerSwitchDecorator不实现PowerSwitch接口?它当然可以... 【参考方案1】:

您的 PowerSwitchDecorator 类不是装饰器。即使命令类似于Command_pattern,您的实现也接近Strategy_pattern

Decorator 模式中,Decorator 实际上实现了接口(即Component),而您的类并没有这样做。

看看类图

在上图中,Component 是一个接口。 Decorator 实现了Component 接口并包含Composition 的接口。查看成员变量 - component

请参阅以下问题以更好地理解:

Decorator Pattern for IO

Real World Example of the Strategy Pattern

【讨论】:

【参考方案2】:

事实上,任何想要使用toggleOnOff(int)toggleOnOff() 方法的代码都需要PowerSwitchDecorator 的实例,而不是PowerSwitch。这种方式违背了装饰器应该对客户端透明的目的。

如果您希望所有实现都具有这些方法,则应将它们包含在PowerSwitch 接口中。

然后,正如@Ani 建议的那样,您可以修改上面的PowerSwitchDecorator 以扩展PowerSwitch,这样您就可以这样做:

PowerSwitch switch = new PowerSwitchDecorator(new ConcretePowerSwitch());
switch.toggleOnOff();

现在您有了一个具有PowerSwitchDecorator 功能的PowerSwitch 类型的变量。

编辑:请注意,只有当它满足您的需要时,您才应该使用已建立的模式。如果它适合您,您可以使用您展示的方法。无需将其硬塞到特定的模式中。

你想传递什么类型的对象?你想在你的 API 中使用这样的方法吗:

void connect(PowerSwitch powerSwitch, Appliance appliance);

或者像这样的方法:

void connect(PowerSwitchDecorator powerSwitch, Appliance appliance);

(对不起,它们不是很好的例子)

如果您想要前者,那么每个人都将不得不手动“装饰”他们的PowerSwitch 以获得一些方便的方法。现在对你来说可能很方便,但我认为这对你的代码的用户来说会很不方便,他们可能不会为此烦恼。如果你想要后者,你必须在你的方法签名中使用PowerSwitchDecorator类型,这往往意味着你总是处理PowerSwitchDecorators而不是原始PowerSwitches。

【讨论】:

但这是否意味着我需要在所有电源开关实现中实现虚拟方法(toggleOnOff)?另外,如果我想稍后在装饰器中添加一个新方法(比如说 toggleOffOn),那么我需要在 PowerSwitch 接口中添加它并更新所有当前的 PowerSwitch 实现(因为它们将不再编译)?也可能是我无法访问所有 powerSwitch 实现的情况(可能是服务提供者接口的一部分)。 作为 etxalpo,我不同意。只要让装饰器实现装饰接口,它仍然很有用,因为 PowerSwitch 的所有客户端都可以使用 PowerSwitchDecorator。就像 Reader 的所有客户端都可以透明地使用 BufferedReader 一样。这不会阻止 BufferedReader 添加自己的方法(例如 readLine()),并且这种模式非常有用。 是的,没错。您可能需要一个具有基本切换方法的抽象类,您的所有实现都从该类继承。然后,如果你想要一种特定的切换方式,你可以装饰它。我编辑了我的答案,希望它有所帮助;) @JB 够公平的。这是完全正确的。我想这取决于预期的用途。 @JB 我的回答可能过于基于他提到的在所有实现中都需要它,而这只是为了方便。【参考方案3】:

我会说这不是模式。

装饰器模式

装饰器模式用于扩展现有实现的行为。以图形窗口为例,您可能希望有一个带有滚动条的窗口。然后你可以有一个类似的课程

public class ScrollBarsWindow : Window

     private Window windowToDecorate;
     public ScrollBarsWindow(Window windowToDecorate)
     
         this.windowToDecorate = windowToDecorate;
     

     public void Draw()
     
         windowToDecorate.Draw();
         DrawScrollBars();
     

     public void DrawScrollBars()
      Draw the scroll bars 
 

策略模式

策略模式用于根据所选策略执行不同的操作。假设您正在煮咖啡。你可以有类似的东西:

public interface IMakeCoffeeStrategy

    public Coffee MakeCoffee();


public class CappuccinoStrategy : IMakeCoffeeStrategy

    public Coffee MakeCoffee  make your cappuccion 


public class LatteStrategy : IMakeCoffeeStrategy

    public Coffee MakeCoffee  make your latte 


public class Context

    private IMakeCoffeeStrategy strategy;
    public Context(IMakeCoffeeStrategy strategy)
    
        this.strategy = strategy;
    

    public Coffee MakeSomeCoffee()
    
        return strategy.MakeCoffee();
    

并像使用它

public class MyCoffeeMachine

    public Coffee MakeCoffee(CoffeeType coffeeType)
    
        if(coffeeType == CoffeeType.Latte)
            return new Context(new LatteStrategy()).MakeSomeCoffee();
        else if(coffeeType == CoffeeType.Cappuccino)
            return new Context(new CappuccinoStrategy()).MakeSomeCoffee();

       ...
    

阅读以下链接了解更多信息:

Strategy Decorator

【讨论】:

【参考方案4】:

几种模式通常看起来相似,不同之处在于它们的使用意图。这是关于这个主题的更一般的线程:When and How Strategy pattern can be applied instead of decorator pattern?

使用您的代码,还没有真正的区别。它看起来像是一种策略,因为 PowerSwitchDecorator 只是将工作(即算法)委托给 PowerSwitch。如果这是您的意图,并且有以不同方式切换的备用电源开关,则策略模式是您的选择。

如果您有一组 PowerSwitch,每个都以轻微的方式增强切换(装饰切换)并且这些 PowerSwitch 可以嵌套,那么您正在实现一个装饰器。如前所述,在这种情况下,您还需要装饰器成为类型层次结构的一部分。

【讨论】:

【参考方案5】:

这两种模式都不是。

装饰器的目的是包装一个对象并扩展现有的功能,使接口对客户端透明。 BufferedInputstream 就是一个例子。请注意,装饰器应该实现与您要包装的类型相同的接口。

//Let Type be the interface that both the Decorator and DecoratedClass implements
Type yourInstance = new Decorator(new DecoratedClass());

注意与代理模式的区别,代理模式的主要目的是控制对对象的访问,不一定通过包装另一个对象。在这种情况下,您还可以让代理实现相同的接口。

【讨论】:

当您说“不是那个...”和“没有区别...”时,您的意思是“请注意...”和“请注意区别...”吗? 是的,抱歉打错了。谢谢!【参考方案6】:

如果装饰器也实现了 PowerSwitch 接口,那它就真的是一个装饰器了。我将其描述为对象聚合。

【讨论】:

【参考方案7】:

如果您需要在运行时增强 PowerSwitch 实例,这是一个很好的做法。我建议你在装饰器中实现 PowerSwitch 接口。装饰器模式的另一个名称是代理模式。

您可以在扩展您的 PowerSwitch 接口的另一个接口中定义扩展方法。当需要调用这些扩展方法时,这将避免对装饰器的依赖。

当您需要在编译时增强或重新定义行为时,扩展类是一种很好的做法。

【讨论】:

谢谢!我会让装饰器实现 PowerSwitch 接口

以上是关于这是装饰器模式还是策略模式,或者两者都不是?的主要内容,如果未能解决你的问题,请参考以下文章

Ruby on Rails 模式 - 装饰器与演示器

设计模式_装饰器模式

使用列表而不是装饰器模式?

装饰器模式(从放弃到入门)

设计模式策略模式责任链以及装饰器之间的区别

设计模式----装饰器模式