[英]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.