簡體   English   中英

裝飾者模式和C#

[英]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();

        }
    }
}

C#中的裝飾者

使用裝飾器模式時 ,想法是讓幾個類實現相同的接口。 其中一個是接口的正常具體實現,在您的情況下是Computer 其他人為Computer的行為添加了一些東西。 我們可以擺脫ComponentDecorator 您可以創建一個實現IComputer接口的抽象裝飾器類,但您不必這樣做。

入門

我們首先創建界面並使您的具體Computer實現它:

public interface IComputer
{
    string getComputer();
}

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

這里的Computersealed 它不一定是,但在這種情況下完成,以顯示裝飾器存在您的具體類旁邊 ,而不是從它派生

沒有用於裝飾器的抽象基類

裝飾器實現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());
    }
}

但結果是一樣的,如這里所見。

為什么你的代碼在Java中工作,而在C#中不工作?

雖然它實際上並沒有實現裝飾器,但如果Computer.getComputer()virtual ,那么您的代碼將起作用。 在您的代碼中,在Maincomputer屬於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.getComputeroverrideComponentDecorator.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM