【问题标题】:Decorator pattern and C#装饰器模式和 C#
【发布时间】:2014-10-29 02:06:25
【问题描述】:

我尝试在 C# 中运行以下示例程序,我得到的输出是“You are getting a computer”而不是“You're getting a computer and a disk and a monitor and a KeyBoard”。

为什么会发生这种情况 C# 仅在 Java 中没有。 我 java 相同的代码,我得到了适当的输出。

如果我调试我发现创建的对象层次结构是正确的,但调用 computer.getComputer() 总是转到超类而不是驱动类,这就是问题所在。

请帮我解决这个问题。

namespace DecoratorTest1
{

    public class Computer
    {
        public Computer()
        {
        }

        public  String getComputer()
        {
            return "computer";
        }
    }


    public abstract class ComponentDecorator : Computer
    {
        public abstract String getComputer();
    }

    public class Disk : ComponentDecorator
    {
        Computer computer;
        public Disk(Computer c)
        {
            computer = c;
        }


        public override String getComputer()
        {
            return computer.getComputer() + " and a disk";
        }

    }

    public class Monitor : ComponentDecorator
    {
        Computer computer;
        public Monitor(Computer c)
       {
            computer = c;
        }
        public override String getComputer()
        {
            return computer.getComputer() + " and a Monitor";
        }

    }

    public class KeyBoard : ComponentDecorator
    {
        Computer computer;
        public KeyBoard(Computer c)
       {
            computer = c;
        }

        public  override String getComputer()
        {
            return computer.getComputer() + " and a KeyBoard";
        }

        public string call()
        {
            return "";
        }

    }



    class Program
    {
        static void Main(string[] args)
        {
            Computer computer = new Computer();
            computer = new Disk(computer);
            computer = new Monitor(computer);
            computer = new KeyBoard(computer);


            Console.WriteLine(" You are getting a " + computer.getComputer());
            Console.ReadKey();

        }
    }
}

【问题讨论】:

  • 您可以删除ComponentDecorator 并将Computer 基类中的getComputer 设置为virtual,因此,您可以使用默认行为并使其在子类中正常工作。跨度>
  • @FelipeOriani 这不违反他所询问的装饰器模式吗?整个想法是不要修改Computer,因为它可能不是您代码库的一部分。

标签: c# design-patterns decorator


【解决方案1】:

computer.getComputer() 在下一行

Console.WriteLine(" You are getting a " + computer.getComputer());

调用计算机版本的getComputer,因为它是编译时类型(因为方法不是虚拟的)。

如果需要多态行为,则需要在计算机类中将getComputer 标记为virtual。然后,您可以完全删除该 ComponentDecorator 类,它不会添加任何内容。

为什么这种情况发生在 C# 中,而在 Java 中却没有?

因为默认情况下所有方法在 java 中都是虚拟的(可以被覆盖)。在 c# 中不是。您需要明确标记为virtual

所以你的完整实现变成了

public class Computer
{
    public Computer()
    {
    }

    public virtual String getComputer()
    {
        return "computer";
    }
}

public class Disk : Computer
{
    Computer computer;
    public Disk(Computer c)
    {
        computer = c;
    }


    public override String getComputer()
    {
        return computer.getComputer() + " and a disk";
    }

}

public class Monitor : Computer
{
    Computer computer;
    public Monitor(Computer c)
    {
        computer = c;
    }
    public override String getComputer()
    {
        return computer.getComputer() + " and a Monitor";
    }
}

public class KeyBoard : Computer
{
    Computer computer;
    public KeyBoard(Computer c)
    {
        computer = c;
    }

    public override String getComputer()
    {
        return computer.getComputer() + " and a KeyBoard";
    }

    public string call()
    {
        return "";
    }
}

【讨论】:

  • 他在询问装饰器模式。这个想法是在不修改代码的情况下装饰Computer 类。
  • @InBetween 我知道。它必须在decorator pattern 中是虚拟的。否则 OP 需要使用字段类型为 ComputerDecorator 而不是 Computer
  • @InBetween 另请注意 OP 的问题是为什么这不会在 java 中发生?因为默认情况下所有方法在java中都是可覆盖的。因此,当将该代码转换为 c# 时,他必须添加虚拟
  • 关键是要引入一个接口IComputer。它定义了方法getComputer()Computer 可以实现接口,而 getComputer() 不必是虚拟的,装饰器也可以。
  • 显然他可以修改Computer,因为是他写的,所以Computer可以实现IComputer。如果你不能修改它,你也不能将getComputer()虚拟化。
【解决方案2】:

Computer.getComputer 方法未标记为virtualComponentDecorator.getComputer 方法未标记为override。在 C# 中,您可以在派生类中创建与基类中的方法具有相同签名的方法,而不会出现编译器错误(尽管您会收到警告)。这样做的效果是派生类中的方法“隐藏”了基类中的方法,而不是覆盖它,因此,如果您通过类型为派生类的引用调用该方法,您将获得派生类的实现,但如果您通过类​​型为基类的引用调用该方法,您将获得基类实现。例如:

void Main()
{
    DerivedHide d1 = new DerivedHide();
    Console.WriteLine(d1.GetName()); // "DerivedHide"
    Base b = d1;
    Console.WriteLine(b.GetName());  // "Base"

    DerivedOverride d2 = new DerivedOverride();
    Console.WriteLine(d2.GetName());// "DerivedOverride"
    b = d2;
    Console.WriteLine(b.GetName()); // "DerivedOverride"
}

public class Base
{
    public virtual string GetName(){ return "Base"; }
}

public class DerivedHide : Base
{
    public string GetName() { return "DerivedHide"; } // causes compiler warning
}

public class DerivedOverride : Base
{
    public override string GetName() { return "DerivedOverride"; }
}

如果您将virtual 添加到Computer.getComputer 并将override 添加到ComponentDecorator.getComputer,您的代码将按预期工作。

(顺便说一句,C# 中的约定(与 Java 不同)是以 PascalCase 而不是 camelCase 来编写方法名称,因此 Computer.GetComputer 将优于 Computer.getComputer。)

【讨论】:

    【解决方案3】:

    为了在您的示例中使用装饰器模式,您需要 Computer.GetComputer() 是虚拟的。在Java中,我认为所有方法默认都是虚拟的。在 C# 中不是这种情况,您需要通过 virtual 关键字将方法明确定义为虚拟方法。这就是为什么代码在 java 中有效,但在 C# 中无效。

    这仍然不是您代码中的唯一问题。即使您将Computer.GetComputer() 设为虚拟,输出也保持不变。另一个问题是您在ComponentDecorator 中有效地隐藏了基类Computer.GetComputer() 方法(C# 编译器允许您省略new 关键字,尽管它会给您一个警告)。要使方法保持虚拟,您需要将方法定义为public abstract override String getComputer();。尽管看起来很奇怪,abstract override 在 C# 中完全有效:What is the use of 'abstract override' in C#? 这同样适用于 Java,因为默认情况下 ComponentDecorator.GetComputer 也是虚拟的。

    通过这两个更改,您的代码将运行良好,尽管我同意其他答案,因为您最好直接继承 Computer 而不是使用 DecoratorComponent。如果Computer 不在您的代码库中并且方法GetComputer 不是虚拟的,那么您将不得不使用不同的模式。

    【讨论】:

    • 公共抽象覆盖 String getComputer();是我的问题的解决方案。因为我想装饰Computer,所以它应该是不变的,即使在我的情况下(我现在正在使用的真实应用程序)也不可能在Computer类中改变任何东西。
    【解决方案4】:

    如果我要实现装饰器模式,我会选择这样的东西 -

    public interface IComponent
    {
        String getComputer();
    }
    public class Computer : IComponent
    {
        public Computer()
        {
        }
    
        public virtual String getComputer()
        {
            return "computer";
        }
    }
    
    public interface IComponentDecorator : IComponent
    {
    }
    
    public class Disk : IComponentDecorator
    {
        IComponent computer;
        public Disk(IComponent c)
        {
            computer = c;
        }
    
    
        public String getComputer()
        {
            return computer.getComputer() + " and a disk";
        }
    
    }
    
    public class Monitor : IComponentDecorator
    {
        IComponent computer;
        public Monitor(IComponent c)
        {
            computer = c;
        }
        public String getComputer()
        {
            return computer.getComputer() + " and a Monitor";
        }
    
    }
    
    public class KeyBoard : IComponentDecorator
    {
        IComponent computer;
        public KeyBoard(IComponent c)
        {
            computer = c;
        }
    
        public String getComputer()
        {
            return computer.getComputer() + " and a KeyBoard";
        }
    
        public string call()
        {
            return "";
        }
    
    }
    
    
    
    class Program
    {
        static void Main(string[] args)
        {
            IComponent computer = new Computer();
            computer = new Disk(computer);
            computer = new Monitor(computer);
            computer = new KeyBoard(computer);
    
    
            Console.WriteLine(" You are getting a " + computer.getComputer());
            Console.ReadKey();
    
        }
    }
    

    输出? -

    You are getting a computer and a disk and a Monitor and a KeyBoard
    

    这里还有一些示例 - Decorator Pattern

    【讨论】:

    • 你为什么要让Computer实现ComponentDecoratorComputer 不是被装饰的类型,不是装饰器?如果其他类型实现ComponentDecorator而不是Computer派生,那将更有意义。
    • @SteveRuble,是的,之前的代码错了,我更正了。
    【解决方案5】:

    C# 中的装饰器

    使用decorator pattern 时,想法是让多个类实现相同的接口。其中之一是接口的正常具体实现,在您的情况下为Computer。其他人为Computer 的行为添加了一些东西。我们可以摆脱ComponentDecorator。您可以创建一个实现IComputer 接口的抽象装饰器类,但您不必这样做。

    开始

    我们首先创建接口并让您的具体Computer 实现它:

    public interface IComputer
    {
        string getComputer();
    }
    
    public sealed class Computer : IComputer
    {
        public string getComputer()
        {
            return "computer";
        }
    }
    

    Computer 这里是sealed。它不是必须的,但在这种情况下这样做是为了表明装饰器存在于您的具体类旁边,而不是从它派生

    没有装饰器的抽象基类

    装饰器实现IComputer而不是ComponentDecorator

    public class Disk : IComputer
    {
        IComputer _computer;
        public Disk(IComputer computer)
        {
            _computer = computer;
        }
    
        public String getComputer()
        {
            return _computer.getComputer() + " and a disk";
        }
    }
    
    public class Monitor : IComputer
    {
        IComputer _computer;
        public Monitor(IComputer computer)
        {
            _computer = computer;
        }
    
        public String getComputer()
        {
            return _computer.getComputer() + " and a Monitor";
        }
    }
    
    public class KeyBoard : IComputer
    {
        IComputer _computer;
        public KeyBoard(IComputer computer)
        {
            _computer = computer;
        }
    
        public  String getComputer()
        {
            return _computer.getComputer() + " and a KeyBoard";
        }
    }
    

    带有装饰器的抽象基类

    如果您确实选择使用抽象类来实现装饰器,请在构造函数中将IComputer 作为依赖项。此外,您应该使用base.getComputer() 而不是computer.getComputer(),如下所示:

    public abstract class ComputerDecorator : IComputer
    {
        private IComputer _computer;
        public ComputerDecorator(IComputer computer)
        {
            _computer = computer;
        }
    
        public virtual string getComputer()
        {
            return _computer.getComputer();
        }
    }
    
    public class Disk : ComputerDecorator
    {
        public Disk(IComputer computer) : base(computer)
        {
        }
    
        public override String getComputer()
        {
            return base.getComputer() + " and a disk";
        }
    }
    
    public class Monitor : ComputerDecorator
    {
        public Monitor(IComputer computer) : base(computer)
        {
        }
    
        public override String getComputer()
        {
            return base.getComputer() + " and a Monitor";
        }
    }
    
    public class KeyBoard : ComputerDecorator
    {
        public KeyBoard(IComputer computer) : base(computer)
        {
        }
    
        public override String getComputer()
        {
            return base.getComputer() + " and a KeyBoard";
        }
    }
    

    在这两种情况下,我们都可以用相同的方式将它们全部包装起来:

    class Program
    {
        public static void Main(string[] args)
        {
            IComputer computer = new KeyBoard(new Monitor(new Disk(new Computer())));
    
            Console.WriteLine(" You are getting a " + computer.getComputer());
        }
    }
    

    看看 withwithout 抽象装饰器的工作原理。

    如果我们不能改变基类怎么办

    用户InBetween 建议可能无法更改基类。如果基类已经实现了接口,那不是问题。所以让我们假设它不像你的代码那样。

    要在这种情况下实现装饰器,我们首先需要为我们的基类创建一个adapter,并在它旁边实现我们的装饰器。

    所以让我们假设基类是Computer 并且我们无法更改它:

    public sealed class Computer
    {
        public string getComputer()
        {
            return "computer";
        }
    }
    

    要创建适配器,我们像以前一样创建IComputer 接口,以及包装Computer 的类:

    public sealed class ComputerAdapter : IComputer
    {
        private Computer _computer;
        public ComputerAdapter(Computer computer)
        {
            _computer = computer;
        }
    
        public string getComputer()
        {
            return _computer.getComputer();
        }
    }
    

    装饰器与上一个示例保持不变,因为它们已经实现了IComputer。将其包装起来会有所改变,因为我们现在必须将 Computer 传递给我们的 ComputerAdapter 实例:

    class Program
    {
        public static void Main(string[] args)
        {
            Computer sealedComputer = new Computer();
            IComputer computer = new KeyBoard(new Monitor(new Disk(new ComputerAdapter(sealedComputer))));
    
            Console.WriteLine(" You are getting a " + computer.getComputer());
        }
    }
    

    但是结果是一样的,可以看到here

    为什么您的代码可以在 Java 中运行,但不能在 C# 中运行?

    虽然它实际上并没有实现装饰器,但如果 Computer.getComputer()virtual,你的代码就可以工作。在您的代码中,Main 中,computer 的类型为 Computer。由于getComputer() 不是virtual,因此调用Computer.getComputer() 而不是预期的KeyBoard.getComputer()。因为在 Java 中每个方法总是virtual,所以这个问题不会发生。

    您的 C# 编译器应该会警告您来自子类的 getComputer() 正在隐藏原始实现。警告表明您正在执行的操作会编译,但可能不会按照您的预期执行,这里就是这种情况。

    【讨论】:

      【解决方案6】:

      这里的代码示例都没有真正实现装饰器模式(好吧,当我写这个时它们没有实现......)。如果具体的类和装饰器是同一个继承树的一部分,那么它就失去了意义。在这种情况下,实际存储对具体对象的引用是没有意义的,因为您可以简单地调用base

      在装饰器模式中,您的具体类和装饰器应该实现一个通用接口。它不依赖于继承,也不依赖于多态性。

      public interface IComponent
      {
          String getComputer();
      }
      
      public class Computer : IComponent
      {
          public String getComputer()
          {
              return "computer";
          }
      }
      
      public abstract class ComponentDecorator
      {
          protected ComponentDecorator(IComponent component)
          {
              this.Component = component;
          }
      
          protected IComponent Component { get; private set; }
      }
      
      public class Disk : ComponentDecorator, IComponent
      {
          public Disk(IComponent c) : base(c)
          {
          }
      
          public String getComputer()
          {
              return this.Component.getComputer() + " and a disk";
          }
      }
      
      public class Monitor : ComponentDecorator, IComponent
      {
          public Monitor(IComponent c)
              : base(c)
          {
          }
      
          public String getComputer()
          {
              return this.Component.getComputer() + " and a monitor";
          }
      }
      
      class Program
      {
          static void Main(string[] args)
          {
              IComponent computer = new Monitor(new Disk(new Computer()));
      
              Console.WriteLine(" You are getting a " + computer.getComputer());
              Console.ReadKey();
          }
      }
      

      我提取了一个接口。不过,您可以完全使用抽象类。抽象装饰器只是为了重用存储组件引用,仅此而已。

      重点是装饰器不应该从具体类继承,因为这会破坏装饰器模式的要点。如您所见,我什至没有让基础装饰器实现接口来证明您绝对不需要后期绑定。

      顺便说一句:getComputer() 违反了 C# 约定。它应该是 C# 中的一个属性,并以大写字母开头。除了特定于语言的约定外,该方法的名称还隐瞒了它的意图。

      【讨论】:

        【解决方案7】:

        我的问题的真正解决方案将是

        public abstract override String getComputer() 
        

        在 InBetween 建议的 ComponentDecorator 中;因为我想装饰Computer,所以它应该是不变的,即使在我的情况下(我现在正在使用的真实应用程序)也不可能在Computer类中改变任何东西。

        【讨论】:

        • 这可能是使您的代码以最少的更改量工作的解决方案,但它实际上并没有实现装饰器。装饰器存在于它们所装饰的组件旁边。他们不会继承它。
        猜你喜欢
        • 2012-02-04
        • 1970-01-01
        • 2011-07-18
        • 2013-07-01
        • 2011-03-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多