設計模式--Decorator

本文轉自一下博客:裝飾模式html

1、 裝飾(Decorator)模式

裝飾(Decorator)模式又名包裝(Wrapper)模式[GOF95]。裝飾模式以對客戶端透明的方式擴展對象的功能,是繼承關係的一個替代方案。編程

引言app

孫悟空有七十二般變化,他的每一種變化都給他帶來一種附加的本領。他變成魚兒時,就能夠到水裏游泳;他變成雀兒時,就能夠在天上飛行。而無論悟空怎麼變化,在二郎神眼裏,他永遠是那隻猢猻。ide

裝飾模式以對客戶透明的方式動態地給一個對象附加上更多的責任。換言之,客戶端並不會以爲對象在裝飾前和裝飾後有什麼不一樣。裝飾模式能夠在不使用創造更多子類的狀況下,將對象的功能加以擴展。函數


2、 裝飾模式的結構

裝飾模式使用原來被裝飾的類的一個子類的實例,把客戶端的調用委派到被裝飾類。裝飾模式的關鍵在於這種擴展是徹底透明的。性能

在孫猴子的例子裏,老孫變成的魚兒至關於老孫的子類,這條魚兒與外界的互動要經過"委派",交給老孫的本尊,由老孫本尊採起行動。this

裝飾模式的類圖以下圖所示:spa

在裝飾模式中的各個角色有:.net

  • 抽象構件(Component)角色:給出一個抽象接口,以規範準備接收附加責任的對象。
  • 具體構件(Concrete Component)角色:定義一個將要接收附加責任的類。
  • 裝飾(Decorator)角色:持有一個構件(Component)對象的實例,並定義一個與抽象構件接口一致的接口。
  • 具體裝飾(Concrete Decorator)角色:負責給構件對象"貼上"附加的責任。

3、 裝飾模式示例性代碼

如下示例性代碼實現了裝飾模式:設計

// Decorator pattern -- Structural example  
using System;

// "Component"
abstract class Component
{
  // Methods
  abstract public void Operation();
}

// "ConcreteComponent"
class ConcreteComponent : Component
{
  // Methods
  override public void Operation()
  {
    Console.WriteLine("ConcreteComponent.Operation()");
  }
}

// "Decorator"
abstract class Decorator : Component
{
  // Fields
  protected Component component;

  // Methods
  public void SetComponent( Component component )
  {
    this.component = component;
  }

  override public void Operation()
  {
    if( component != null )
      component.Operation();
  }
}

// "ConcreteDecoratorA"
class ConcreteDecoratorA : Decorator
{
  // Fields
  private string addedState;

  // Methods
  override public void Operation()
  {
    base.Operation();
    addedState = "new state";
    Console.WriteLine("ConcreteDecoratorA.Operation()");
  }
}

// "ConcreteDecoratorB"
class ConcreteDecoratorB : Decorator
{
  // Methods
  override public void Operation()
  {
    base.Operation();
    AddedBehavior();
    Console.WriteLine("ConcreteDecoratorB.Operation()");
  }

  void AddedBehavior()
  {
  }
}

/// <summary>
/// Client test
/// </summary>
public class Client
{
  public static void Main( string[] args )
  {
    // Create ConcreteComponent and two Decorators
    ConcreteComponent c = new ConcreteComponent();
    ConcreteDecoratorA d1 = new ConcreteDecoratorA();
    ConcreteDecoratorB d2 = new ConcreteDecoratorB();

    // Link decorators
    d1.SetComponent( c );
    d2.SetComponent( d1 );

    d2.Operation();
  }
}

  上面的代碼在執行裝飾時是經過SetComponent方法實現的,在實際應用中,也有經過構造函數實現的,一個典型的建立過程可能以下:

new Decorator1(
   new Decorator2(
      new Decorator3(
         new ConcreteComponent()
         )
      )
   )

  裝飾模式經常被稱爲包裹模式,就是由於每個具體裝飾類都將下一個具體裝飾類或者具體構件類包裹起來。

4、 裝飾模式應當在什麼狀況下使用

在如下狀況下應當使用裝飾模式:

  1. 須要擴展一個類的功能,或給一個類增長附加責任。
  2. 須要動態地給一個對象增長功能,這些功能能夠再動態地撤銷。
  3. 須要增長由一些基本功能的排列組合而產生的很是大量的功能,從而使繼承關係變得不現實。

5、 裝飾模式實際應用的例子

該例子演示了經過裝飾模式爲圖書館的圖書與錄像帶添加"可借閱"裝飾。

// Decorator pattern -- Real World example  
using System;
using System.Collections;

// "Component"
abstract class LibraryItem
{
  // Fields
  private int numCopies;

  // Properties
  public int NumCopies
  {
    get{ return numCopies; }
    set{ numCopies = value; }
  }

  // Methods
  public abstract void Display();
}

// "ConcreteComponent"
class Book : LibraryItem
{
  // Fields
  private string author;
  private string title;

  // Constructors
  public Book(string author,string title,int numCopies)
  {
    this.author = author;
    this.title = title;
    this.NumCopies = numCopies;
  }

  // Methods
  public override void Display()
  {
    Console.WriteLine( " Book ------ " );
    Console.WriteLine( " Author: {0}", author );
    Console.WriteLine( " Title: {0}", title );
    Console.WriteLine( " # Copies: {0}", NumCopies );
  }
}

// "ConcreteComponent"
class Video : LibraryItem
{
  // Fields
  private string director;
  private string title;
  private int playTime;

  // Constructor
  public Video( string director, string title,
    int numCopies, int playTime )
  {
    this.director = director;
    this.title = title;
    this.NumCopies = numCopies;
    this.playTime = playTime;
  }

  // Methods
  public override void Display()
  {
    Console.WriteLine( " Video ----- " );
    Console.WriteLine( " Director: {0}", director );
    Console.WriteLine( " Title: {0}", title );
    Console.WriteLine( " # Copies: {0}", NumCopies );
    Console.WriteLine( " Playtime: {0}", playTime );
  }
}

// "Decorator"
abstract class Decorator : LibraryItem
{
  // Fields
  protected LibraryItem libraryItem;

  // Constructors
  public Decorator ( LibraryItem libraryItem )
  { this.libraryItem = libraryItem; }

  // Methods
  public override void Display()
  { libraryItem.Display(); }
}

// "ConcreteDecorator"
class Borrowable : Decorator
{
  // Fields
  protected ArrayList borrowers = new ArrayList();

  // Constructors
  public Borrowable( LibraryItem libraryItem )
    : base( libraryItem ) {}

  // Methods
  public void BorrowItem( string name )
  {
    borrowers.Add( name );
    libraryItem.NumCopies--;
  }

  public void ReturnItem( string name )
  {
    borrowers.Remove( name );
    libraryItem.NumCopies++;
  }

  public override void Display()
  {
    base.Display();
    foreach( string borrower in borrowers )
      Console.WriteLine( " borrower: {0}", borrower );
  }
}
 
/// <summary>
///  DecoratorApp test
/// </summary>
public class DecoratorApp
{
  public static void Main( string[] args )
  {
    // Create book and video and display
    Book book = new Book( "Schnell", "My Home", 10 );
    Video video = new Video( "Spielberg",
      "Schindler's list", 23, 60 );
    book.Display();
    video.Display();

    // Make video borrowable, then borrow and display
    Console.WriteLine( " Video made borrowable:" );
    Borrowable borrowvideo = new Borrowable( video );
    borrowvideo.BorrowItem( "Cindy Lopez" );
    borrowvideo.BorrowItem( "Samuel King" );

    borrowvideo.Display();
  }
}

  

6、 使用裝飾模式的優勢和缺點

使用裝飾模式主要有如下的優勢:

  1. 裝飾模式與繼承關係的目的都是要擴展對象的功能,可是裝飾模式能夠提供比繼承更多的靈活性。
  2. 經過使用不一樣的具體裝飾類以及這些裝飾類的排列組合,設計師能夠創造出不少不一樣行爲的組合。
  3. 這種比繼承更加靈活機動的特性,也同時意味着裝飾模式比繼承更加易於出錯。

使用裝飾模式主要有如下的缺點:

因爲使用裝飾模式,能夠比使用繼承關係須要較少數目的類。使用較少的類,固然使設計比較易於進行。可是,在另外一方面,使用裝飾模式會產生比使用繼承關係更多的對象。更多的對象會使得查錯變得困難,特別是這些對象看上去都很相像。


7、 模式實現的討論

大多數狀況下,裝飾模式的實現都比上面定義中給出的示意性實現要簡單。對模式進行簡化時須要注意如下的狀況:

(1)一個裝飾類的接口必須與被裝飾類的接口相容。

(2)儘可能保持Component做爲一個"輕"類,不要把太多的邏輯和狀態放在Component類裏。

(3)若是隻有一個ConcreteComponent類而沒有抽象的Component類(接口),那麼Decorator類常常能夠是ConcreteComponent的一個子類。以下圖所示:

(4)若是隻有一個ConcreteDecorator類,那麼就沒有必要創建一個單獨的Decorator類,而能夠把Decorator和ConcreteDecorator的責任合併成一個類。

 

8、 透明性的要求

透明的裝飾模式

裝飾模式一般要求針對抽象編程。裝飾模式對客戶端的透明性要求程序不要聲明一個ConcreteDecorator類型的變量,而應當聲明一個Component類型的變量。換言之,下面的作法是對的:

Component c = new ConcreteComponent();
Component c1 = new ConcreteDecorator1(c);
Component c2 = new ConcreteDecorator(c1);

  而下面的作法是不對的:

ConcreteComponent c = new ConcreteDecorator();

  

這就是前面所說的,裝飾模式對客戶端是徹底透明的含義。

用孫悟空的例子來講,必須永遠把孫悟空的全部變化都當成孫悟空來對待,而若是把老孫變成的雀兒當成雀兒,而不是老孫,那就被老孫騙了,而這是不該當發生的。

下面的作法是不對的:

大聖本尊 c = new 大聖本尊();
雀兒 bird = new 雀兒 (c);

  

半透明的裝飾模式

然而,純粹的裝飾模式很難找到。裝飾模式的用意是在不改變接口的前提下,加強所考慮的類的性能。在加強性能的時候,每每須要創建新的公開的方法。即使是在孫大聖的系統裏,也須要新的方法。好比齊天大聖類並無飛行的能力,而雀兒有。這就意味着雀兒應當有一個新的fly()方法。

這就致使了大多數的裝飾模式的實現都是"半透明"(semi-transparent)的,而不是徹底"透明"的。換言之,容許裝飾模式改變接口,增長新的方法。即聲明ConcreteDecorator類型的變量,從而能夠調用ConcreteDecorator類中才有的方法:

齊天大聖 c = new 大聖本尊();
雀兒 bird = new 雀兒(c);
bird.fly();

齊天大聖接口根本沒有fly()這個方法,而雀兒接口裏有這個方法。

9、 裝飾模式在.NET中的應用

.net中存在以下類模型:

下面的代碼段用來將XmlDocument的內容格式輸出。咱們能夠體會Decorator模式在這裏所起的做用。

// 生成ConcreteComponent(內存流ms)
MemoryStream ms = new MemoryStream();

// 用XmlTextWriter對內存流 ms 進行裝飾
// 此處使用了半透明的裝飾模式
XmlTextWriter xtw = new XmlTextWriter(ms, Encoding.UTF8);
xtw.Formatting = Formatting.Indented;

// 對裝飾xtw的操做會轉而操做本體-內存流ms
xmlDoc.Save(xtw);

byte[] buf = ms.ToArray();
txtResult.Text = Encoding.UTF8.GetString(buf,0,buf.Length);
xtw.Close();
相關文章
相關標籤/搜索