文章出處

索引

意圖

允許在運行時動態靈活的創建新的 "類",而這些類的實例代表著一種不同的對象類型。

Allow the flexible creation of new “classes” by creating a single class, each instance of which represents a different type of object.

結構

Type Object 模式包含兩個具體類。一個用于描述對象,另一個用于描述類型。每個對象都包含一個指向其類型的指針。

參與者

TypeClass

  • 是 TypeObject 的種類。
  • 每個種類都會有一個單獨的類。

TypeObject

  • 是 TypeClass 的實例。
  • 代表著一種對象。定義一種對象所包含的屬性和行為。

適用性

當以下情況成立時可以使用 Type Object 模式:

  • 類的實例需要根據它們的通用屬性或者行為進行分組。
  • 類需要為每個分組定義一個子類來實現該分組的通用屬性和行為。
  • 類需要大量的子類或者多種變化的子類甚至無法預期子類的變化。
  • 你需要有能力在運行時創建一些無法在設計階段預測的新的分組。
  • 你需要有能力在類已經被實例化的條件下更改一個對象的子類。

效果

  • 運行時創建新的類型對象。
  • 避免子類膨脹。
  • 客戶程序無需了解實例與類型的分離。
  • 可以動態的更改類型。

相關模式

  • Type Object 模式有些類似于 Strategy State 模式。這三種模式都是通過將對象內部的一些行為代理到外部的對象中。Stategy 和 State 通常是純行為的代理,而 Type Object 則包含更多個共享數據狀態。State 可以被頻繁的更改,Type Object 則很少被改變。Strategy 通常僅包含一個職責,Type Object 則通常包含多個職責。
  • Type Object 的實現與 Bridge 模式中的 Abstraction 和 Implementor 的關系很像。區別在于,客戶程序可以與 Type Object 直接協作,而不會直接與 Implementor 進行交互。
  • Type Object 有點像 Flyweight 一樣處理它的對象。兩個對象使用相同的 Type Object 可能看起來是使用的各自的實例,但實際是共享的對象。
  • Type Object 可以解決多個對象共享數據和行為的問題。類似的問題也可以用 Prototype 模式來解決。

實現

實現方式(一):Type Object 的經典介紹。

  • TypeClass - Movie
  • TypeObject - Star Wars, The Terminator, Independence Day
  • Class - Videotape
  • Object - John's Star Wars, Sue's Star Wars
 1 namespace TypeObjectPattern.Implementation1
 2 {
 3   public class Movie
 4   {
 5     public string Title { get; set; }
 6     public float RentalPrice { get; set; }
 7   }
 8 
 9   public class Videotape
10   {
11     public Videotape(Movie movie)
12     {
13       this.Movie = movie;
14     }
15 
16     public Movie Movie { get; private set; }
17 
18     public Customer Renter { get; private set; }
19     public bool IsRented { get; private set; }
20 
21     public void RentTo(Customer customer)
22     {
23       IsRented = true;
24       Renter = customer;
25       Renter.ChargeForRental(this.Movie.RentalPrice);
26     }
27   }
28 
29   public class Customer
30   {
31     public string Name { get; set; }
32 
33     public void ChargeForRental(float rental)
34     {
35       // pay money
36     }
37   }
38 
39   public class Client
40   {
41     public void TestCase1()
42     {
43       Customer john = new Customer() { Name = "John" };
44       Customer sue = new Customer() { Name = "Sue" };
45 
46       Movie starWars = new Movie()
47       {
48         Title = "Star Wars",
49         RentalPrice = 100,
50       };
51       Movie terminator = new Movie()
52       {
53         Title = "The Terminator",
54         RentalPrice = 200,
55       };
56 
57       Videotape starWarsVideotapeForJohn = new Videotape(starWars);
58       starWarsVideotapeForJohn.RentTo(john);
59 
60       Videotape starWarsVideotapeForSue = new Videotape(starWars);
61       starWarsVideotapeForSue.RentTo(john);
62 
63       Videotape terminatorVideotapeForJohn = new Videotape(terminator);
64       terminatorVideotapeForJohn.RentTo(john);
65     }
66   }
67 }

實現方式(二):Type Object 在游戲設計中的使用。

想象我們正在制作在一個虛擬角色扮演游戲。我們的任務是設計一些邪惡的怪獸(Monster)來試圖殺掉我們的英雄(Hero)。怪獸有著一些不同的屬性,例如生命值(Health)、攻擊力(Attacks)、圖像、聲音等,但以舉例為目的我們僅考慮前兩個屬性。

游戲中的每個怪獸都有自己的生命值。生命值從滿血開始,每次怪獸被創傷,生命值減少。怪獸會有一個用于描述攻擊的字符串,當怪獸攻擊英雄時,這個字符串會被顯示到用戶屏幕上。

游戲設計師告訴我們,怪獸會有不同的品種(Breed),例如:猛龍(Dragon)和巨魔(Troll)。每個怪獸品種都描述了一種怪獸,在一個場景下會有多個同一種的怪獸遍布在地牢(Dungeon)中。

怪獸的品種(Breed)決定的怪獸的起始生命值,比如猛龍(Dragon)的生命值會比巨魔(Troll)的高,以使猛龍更難被殺掉。同時,同一個品種的怪獸的攻擊字符串也是相同的。

通過典型的 OO 設計,我們能得到下面這段代碼:

 1 namespace TypeObjectPattern.Implementation2
 2 {
 3   public abstract class Monster
 4   {
 5     public Monster(int startingHealth)
 6     {
 7       Health = startingHealth;
 8     }
 9 
10     public int Health { get; private set; }
11     public abstract string AttackString { get; }
12   }
13 
14   public class Dragon : Monster
15   {
16     public Dragon()
17       : base(500)
18     {
19     }
20 
21     public override string AttackString
22     {
23       get { return "The dragon breathes fire!"; }
24     }
25   }
26 
27   public class Troll : Monster
28   {
29     public Troll()
30       : base(300)
31     {
32     }
33 
34     public override string AttackString
35     {
36       get { return "The troll clubs you!"; }
37     }
38   }
39 
40   public class Client
41   {
42     public void TestCase2()
43     {
44       Monster dragon = new Dragon();
45       Monster troll = new Troll();
46     }
47   }
48 }

這段代碼淺顯易懂,使用繼承的方式設計類的層級結構。一個 Dragon 是一個 Monster,滿足了 "is a" 的關系。每一個怪物的品種都會用一個子類來實現。

如果游戲中有成百上千的怪物種類,則類的繼承關系變得龐大。同時也意味著,增加新的怪物品種就需要增加新的子類代碼。

這是可以工作的,但并不是唯一的選擇。我們可以嘗試另外一種架構。

因為變化較多的部分是品種(Breed)的屬性配置,包括生命值和攻擊字符串。

所以我們可以將品種(Breed)抽取成單獨的類,每個怪物類(Monster)包含一個品種類(Breed)。

Breed 類用于定義 Monster 的 "type"。每一個 Breed 的實例描述著一種 Monster 對象的概念上的 "type"。

 1 namespace TypeObjectPattern.Implementation3
 2 {
 3   public class Breed
 4   {
 5     public int Health { get; set; }
 6     public string AttackString { get; set; }
 7   }
 8 
 9   public class Monster
10   {
11     private Breed _breed;
12 
13     public Monster(Breed breed)
14     {
15       _breed = breed;
16     }
17 
18     public int Health
19     {
20       get { return _breed.Health; }
21     }
22 
23     public string AttackString
24     {
25       get { return _breed.AttackString; }
26     }
27   }
28 
29   public class Client
30   {
31     public void TestCase3()
32     {
33       Breed dragonBreed = new Breed()
34       {
35         Health = 500,
36         AttackString = "The dragon breathes fire!",
37       };
38       Breed trollBreed = new Breed()
39       {
40         Health = 300,
41         AttackString = "The troll clubs you!",
42       };
43 
44       Monster dragon = new Monster(dragonBreed);
45       Monster breed = new Monster(trollBreed);
46     }
47   }
48 }

Type Object 在這里的優勢在于,我們可以定義新的類型的怪物,而不用修改代碼。并且可以在運行時動態生成新的對象和修改對象的屬性。

參考資料

設計模式之美》為 Dennis Gao 發布于博客園的系列文章,任何未經作者本人同意的人為或爬蟲轉載均為耍流氓。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()