【问题标题】: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接口?它当然可以...

标签: java design-patterns decorator strategy-pattern


【解决方案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();
    
           ...
        }
    }
    

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

    【讨论】:

      【解决方案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 接口
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-05-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-12-12
          相关资源
          最近更新 更多