我尝试在C#中运行以下示例程序我得到输出"你正在使用计算机"而不是"你得到的是一台电脑和一个磁盘,一台显示器和一个KeyBoard"。
为什么这种情况只发生在C#中而不是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();
}
}
}
答案 0 :(得分:10)
使用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());
}
}
用户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。
虽然它实际上并没有实现装饰器,但如果Computer.getComputer()
为virtual
,您的代码将会正常工作。在代码的Main
中,computer
的类型为Computer
。由于getComputer()
不是virtual
,因此调用Computer.getComputer()
而不是预期的KeyBoard.getComputer()
。因为在Java中,每个方法总是virtual
,所以这个问题不会发生。
您的C#编译器应该给您一个警告:来自子类的getComputer()
隐藏了原始实现。警告表示您正在进行的操作将编译,但可能无法按预期执行,这就是这种情况。
答案 1 :(得分:3)
computer.getComputer()
位于以下行
Console.WriteLine(" You are getting a " + computer.getComputer());
调用Computer的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 "";
}
}
答案 2 :(得分:0)
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
。)
答案 3 :(得分:0)
为了在示例中使用装饰器模式,您需要Computer.GetComputer()
为虚拟。在Java中,我认为默认情况下所有方法都是虚拟的。在C#中并非如此,您明确需要通过virtual
关键字将方法定义为虚拟方法。这就是为什么代码在java中工作但不在C#中工作。
这仍然不是代码中唯一的问题。即使您使Computer.GetComputer()
为虚拟,输出也保持不变。另一个问题是你有效地隐藏了Computer.GetComputer()
中的基类ComponentDecorator
方法(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
不是虚拟的,那么您将不得不使用不同的模式。
答案 4 :(得分:0)
如果我要实现装饰模式,我会选择这样的东西 -
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
答案 5 :(得分:0)
这里的代码示例都没有实际实现装饰器模式(好吧,当我写这个时它们没有...)。如果具体的类和装饰器是同一继承树的一部分,它就会失败。在这种情况下,实际存储对具体对象的引用是没有意义的,因为您只需调用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();
}
}
我提取了一个界面。但是,您可以使用完全相同的抽象类。抽象装饰器只是重复使用存储组件引用,但仅此而已。
关键是装饰者不应该从具体类继承,因为这会破坏装饰者模式的点。正如你所看到的,我甚至没有让基础装饰器实现接口来证明你绝对不需要后期绑定。
BTW:getComputer()
违反了C#惯例。它应该是C#中的属性,并以大写字母开头。除了特定于语言的惯例之外,该方法的名称也与其意图有关。
答案 6 :(得分:0)
我的问题的真正解决方案是
public abstract override String getComputer()
InBetween建议的ComponentDecorator
中的;因为我想要装饰计算机,所以它应该保持不变,即使在我的情况下(我正在使用的实际应用程序)也无法在计算机类中进行任何更改。