[英]Decorator pattern and C#
我尝试在C#中运行以下示例程序我得到输出“你正在获得一台计算机”而不是“你正在获得一台计算机和一个磁盘,一台显示器和一个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();
}
}
}
使用装饰器模式时 ,想法是让几个类实现相同的接口。 其中一个是接口的正常具体实现,在您的情况下是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());
}
}
用户InBetween建议可能无法更改基类。 如果基类已经实现了接口,那不是问题。 所以我们假设它不像你的代码那样。
要在这种情况下实现装饰器,我们首先需要为我们的基类创建一个适配器 ,并在它旁边实现我们的装饰器。
因此,我们假设基类是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());
}
}
但结果是一样的,如这里所见。
虽然它实际上并没有实现装饰器,但如果Computer.getComputer()
是virtual
,那么您的代码将起作用。 在您的代码中,在Main
, computer
属于Computer
类型。 由于getComputer()
不是virtual
,因此调用Computer.getComputer()
而不是预期的KeyBoard.getComputer()
。 由于在Java中,每个方法都是virtual
,因此不会发生这种问题。
你的C#编译器应该给你一个警告,即来自子类的getComputer()
隐藏了原始实现。 警告表明您正在进行的操作将编译,但可能无法按预期执行,这就是这种情况。
以下行中的computer.getComputer()
Console.WriteLine(" You are getting a " + computer.getComputer());
调用计算机的getComputer
版本,因为它是编译时类型(因为方法不是虚拟的)。
如果需要多态行为,则需要将计算机类中的getComputer
标记为virtual
。 然后,您可以完全删除不添加任何内容的ComponentDecorator
类。
为什么这种情况只发生在C#中?
因为默认情况下,所有方法都是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.getComputer
方法未标记为virtual
,并且ComponentDecorator.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
。)
为了在示例中使用Decorator Pattern,您需要Computer.GetComputer()
为虚拟。 在Java中,我认为默认情况下所有方法都是虚拟的。 在C#中并非如此,您明确需要通过virtual
关键字将方法定义为虚拟方法。 这就是为什么代码在java中工作但在C#中不工作的原因。
这仍然不是代码中唯一的问题。 即使你使Computer.GetComputer()
虚拟,输出仍然是相同的。 另一个问题是你有效地在ComponentDecorator
隐藏了基类Computer.GetComputer()
方法(C#编译器允许你忽略new
关键字,尽管它会给你一个警告)。 要使方法保持虚拟,您需要将方法定义为public abstract override String getComputer();
。 虽然看起来很奇怪,但abstract override
在C#中完全有效:C# 中“抽象覆盖”的用途是什么? 这也适用于Java,因为默认情况下ComponentDecorator.GetComputer
也是虚拟的。
通过这两个更改,您的代码将运行良好,但我同意其他答案,您最好只是直接继承Computer
而不是使用DecoratorComponent
。 如果Computer
不在您的代码库中且方法GetComputer
不是虚拟的,那么您将不得不使用不同的模式。
如果我要实现装饰模式,我会用这样的东西 -
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
这里还有一些样本 - 装饰模式
这里的代码示例都没有实际实现装饰器模式(好吧,当我写这个时它们没有...)。 如果具体的类和装饰器是同一继承树的一部分,它就会失败。 在这种情况下,实际存储对具体对象的引用是没有意义的,因为您可以简单地调用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();
}
}
我提取了一个界面。 但是,您可以使用完全相同的抽象类。 抽象装饰器只是重用存储组件引用,但仅此而已。
关键是装饰器不应该从具体类继承,因为这会破坏装饰器模式的点。 正如你所看到的,我甚至没有让base decorator实现接口来证明你绝对不需要后期绑定。
BTW: getComputer()
违反了C#约定。 它应该是C#中的属性,并以大写字母开头。 除了特定于语言的约定之外,该方法的名称也在于其意图。
我问题的真正解决方案是
public abstract override String getComputer()
在InBetween建议的ComponentDecorator
中; 因为我想装饰计算机,所以它应该保持不变,即使在我的情况下(我正在使用的真实应用程序)也无法在计算机类中进行任何更改。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.