天天看點

設計模式(15)--結構型模式_裝飾模式

作者:chencalf

0、主要參考

(1)講的較好:https://blog.csdn.net/mzl87/article/details/127878302

(2)https://www.cnblogs.com/for-easy-fast/archive/2023/06/05/17458083.html

(3)也還行:https://www2014.aspxhtml.com/post-20244/

1、概念

1.1問題來源

(1)子類複子類,子類何其多。

設計模式(15)--結構型模式_裝飾模式

圖檔來源https://www.bilibili.com/video/BV1Jz4y1d7TX?p=10

1.2裝飾器模式的作用

裝飾器模式旨在動态地向現有對象添加附加功能。裝飾器為擴充功能提供了子類化的替代方法。雖然可以通過對象類的子類化來為整個對象類添加功能,但裝飾器模式旨在僅向單個對象添加功能,而使該類的其他對象保持不變。

  • (1)有時有人評論說,通過在不修改原始類型的情況下增強現有類型,該模式遵循開閉原則。
  • (2)非常有趣的是,該模式通過違背OO建議的錯誤方式來實作其目标。

1.3組合與繼承的選擇?

面向對象設計的典型問題:

(1)對于給定的類/對象A,在建立要重用類/對象A的新類/對象B時,如何以及基于什麼标準在組合和繼承之間進行選擇?通常給出的方案是:如果您打算重用現有類A的Public接口,請使用繼承。如果您打算僅重用現有類A的功能,請選擇組合。

然而裝飾器模式正好相反:

它計劃重用現有類的Public接口,但使用組合。這個決定對Public接口擴充的問題有一些影響,我們稍後會看到。

2 裝飾器使用的動機和意圖

2.1動機

子類複子類,子類何其多,功能擴充越來越複雜。上述描述的問題根源在于我們“過度地使用了繼承來擴充對象的功能”,由于繼承為類型引入的靜态特質,使得這種擴充方式缺乏靈活性;并且随着子類的增多(擴充功能的增多),各種子類的組合(擴充功能的組合)會導緻更多子類的膨脹(多繼承)。

如何使“對象功能的擴充”能夠根據需要來動态地實作?同時避免“擴充功能的增多”帶來的子類膨脹問題?進而使得任何“功能擴充變化”所導緻的影響降為最低?

2.2意圖

動态地給一個對象增加一些額外的職責。就增加功能而言,Decorator模式比生成子類更靈活。(《設計模式》GoF)

2.3 用處

  • 在軟體開發過程中,經常會遇到需要給一個對象添加一些額外的行為的情況。如果直接修改這個對象的代碼,可能會引起其他代碼的不穩定性,而且會導緻代碼的可維護性變差。此時,裝飾器模式就可以很好地解決這個問題。
  • 使用裝飾器模式,你可以在運作時動态地給一個對象添加一些額外的職責,而不需要修改這個對象的代碼。這樣,你就可以保持原有代碼的穩定性和可維護性,同時還能夠滿足新的需求。

3 裝飾器的實作

3.1裝飾器模式的組成

裝飾器模式的實作需要以下幾個步驟:

  • (1)定義一個接口(或者抽象元件),它包含了被裝飾對象和裝飾器對象需要實作的方法。
  • (2)實作一個具體的類,它實作了這個接口,并且包含了一些基本的行為。
  • (3)實作一個裝飾器類(這是一個抽象類),它也實作了這個接口,并且包含了一個指向被裝飾對象的引用。
  • (4)在裝飾器類中,實作需要添加的額外行為,并且在調用被裝飾對象的方法時,也調用相應的方法。
  • (5)在用戶端代碼中,建立一個具體的對象,并且用裝飾器類動态地給它添加一些額外的行為。

3.2裝飾器的結構圖

(1)

設計模式(15)--結構型模式_裝飾模式

(2)

設計模式(15)--結構型模式_裝飾模式

3.3測試例子

參考了https://www.cnblogs.com/for-easy-fast/archive/2023/06/05/17458083.html

(1)定義一個接口

比如定義個形狀的類

// Component 接口
public interface IShape
{
    void Draw();
}           

(2)定義一個具體的類,實作了上面的接口

比如定義一個矩形的類的實作

// ConcreteComponent 類
public class Rectangle : IShape
{
    public void Draw()
    {
        Console.WriteLine("Drawing a rectangle.");
    }
}           

(3)定義裝飾類,實作了上面的接口

注意:

  • 繼承了接口,
  • 類裡面有protected屬性的接口字段
  • 使用虛函數virtual實作接口中的方法。(如果接口使用抽象類定義,則可以重寫,并使用相同的方法實作)
// Decorator 類
public abstract class ShapeDecorator : IShape
{
    protected IShape decoratedShape;

    public ShapeDecorator(IShape decoratedShape)
    {
        this.decoratedShape = decoratedShape;
    }
    public virtual void Draw()
    {
        decoratedShape.Draw();
    }
}           

(4)定義2個具體的類,進行各自的功能擴充

//(4) ConcreteDecorator 類1
public class RedShapeDecorator : ShapeDecorator
{
    public RedShapeDecorator(IShape decoratedShape) : base(decoratedShape)
    {
    }

    public override void Draw()
    {
        decoratedShape.Draw();
       //具體功能擴充測試代碼如下
        Console.WriteLine("Border Color: Red_1");
    }
}
//(5) ConcreteDecorator 類2
public class BlueShapeDecorator : ShapeDecorator
{
    public BlueShapeDecorator(IShape decoratedShape) : base(decoratedShape)
    {
    }

    public override void Draw()
    {
        decoratedShape.Draw();
      //具體功能擴充測試代碼如下
        Console.WriteLine("Border Color: blue2_1");
    }
}
           

(5)用戶端的測試代碼

// 用戶端代碼
public class Client
{
    public void run()
    {
        // (1)建立一個具體的對象
        IShape rectangle = new Rectangle();

        // (2)用裝飾器類動态地給它添加一些額外的行為
        IShape redRectangle = new RedShapeDecorator(rectangle);

        // (3)原始具體類的輸出
        rectangle.Draw(); 
        Console.WriteLine("-----------------------------");
        // (4)用具體類裝飾一下輸出
        redRectangle.Draw();
        Console.WriteLine("-----------------------------");

        // (5)注意啦,對上面的裝飾器結果接着進行裝飾!!!
        IShape blueShapeDecorator = new BlueShapeDecorator(redRectangle);
        blueShapeDecorator.Draw();
    }
}

public class Demo
{
    static void Main(string[] args)
    {
        Client client = new Client();
        client.run();
    }

}           

(6)測試結果

設計模式(15)--結構型模式_裝飾模式

4、相關過程和注意實作

(1)接口(抽象類)定義了規範

(2)具體類A實作了基本功能

(3)裝飾器(抽象類)對接口進行了實作,并定義了protected的接口字段

(4)裝飾器的具體類對接口功能進行了拓展

(5)用戶端通過建立具體類A;在裝飾器具體類A中,并在構造函數傳入具體類A;進而拓展了具體類A的擴充功能

(6)用戶端還可以建立裝飾器具體類B,并在構造函數傳入裝飾器具體類A,接着擴充了裝飾器A,進而實作了2個擴充功能

(7)裝飾器可以一路裝飾下去。

繼續閱讀