顯示具有 Design Pattern 標籤的文章。 顯示所有文章
顯示具有 Design Pattern 標籤的文章。 顯示所有文章

2014年10月2日 星期四

[RefactoringToPatterns] Replace Constructors with Creation Methods

最近開始看 RefactoringToPatterns 最近應該會開始分享這本書的一些心得,
 今天就先來分享 Replace Constructors with Creation Methods吧
 作者在一開始就列出了兩個範例



哪個看起來比較能清楚表達我們要創建什麼物件?

我相信大家都會選擇下方的選項,
Replace Constructors with Creation Methods的意思就是

        以目的清楚, 返回物件實體的Creation Method 取代建構式

然而使用這個方法雖然能夠讓我們清楚了解要創建什麼物件,相對的他也帶來了部分的缺點

優點:
  • 比建構式更能表達要創建哪種物件
  • 突破建構式的限制,不能同時擁有兩個  引數個數和引數型別君相同的建構式
  • 更容易找出未使用的創建式
缺點:

  • 創建的方式變得不標準,以上面loan的例子來說:我們可以用new Loan(...)
    也可以用  Loan.createTermLoad(...)


至於該如何實做,這邊附上一個很小的範例


public class Loan {

    public Loan(int a, int b) {

    }

}

原本的建構式是長這個樣子,經過refactoring 之後


public class Loan {
    
    private Loan(int a,int b){
        
    }
    
    public static Loan createTermLoan(int a,int b){
        return new Loan(a,b);
    }
}

可以很清楚地看到,我們使用了static method來創建我們的Loan
這樣之後要創建Loan時我們只需要call Loan.createTermLoan即可

最後,作者也有提到,當我們的物件裡含有太多的creation method,
而這些method開始混淆這個class原本該做的事情時,根據單一責任原則,
我們可以將Creation method 再度重構為Factory.
如下圖所示


2014年4月23日 星期三

設計模式(十二) Bridge 模式

Bridge 模式 :  將抽象與實作解耦合,使他們都可以獨立的變化。

解耦: 讓事物獨立地行事,或者至少明確的宣告之間的關係
抽象:不同事物之間概念上的關聯方式


Bridge模式遵循兩個原則:

  1.  找出變化並封裝之
  2. 優先使用物件聚合而不是類別繼承

舉個例子:
假設我有兩個版本的繪圖程式DP1和DP2然後我希望用這兩個繪圖程式來繪製圖形
方形和圓形,範例如下圖



依照需求來看,我們可能會畫出類似上面的UML圖,
接著開始思考該如何將圖組合在一起,

首先我們從Client使用物件的順序來思考,假設Client需要畫一個V1Rectangle,
依序會呼叫 Client -> rectangle.draw -> dp1.drawline *4 
我們發現雖然類別圖的物件非常的多,但是實際上我們只須處理三個物件
  1. 使用矩形的Client物件
  2. V1Rectangle物件
  3. DP1物件
接著我們可能會畫出如下的UML圖


利用繼承來解決這個需求似乎是很自然的解法,但是再仔細思考一下,如果現在新增了一種形狀和一種繪圖程式,物件的數量會變成9個,如果將繪圖程式持續增加,將會造成物件爆炸,
這讓我們必須重頭思考這樣的設計是否是有問題的。

過度使用繼承
過度使用繼承透過特殊化處理變化,從已有的類別衍生新類別,會造成物件越來越難以維護
用設計模式進行思考,用物件的職責來思考而不是其結構,良好的設計應該為每種變化使用不同的特殊化(繼承)並將變化轉移到使用或擁有這種變化的物件中(組合)

那我們應該怎麼做呢?

我們應該
  1. 找出變化並封裝之
  2. 優先使用物件聚合,而不是類別繼承
例如 Client應該只知道他在使用一個Shap物件而不應該知道是Rectangle或Circle,
Shap應該只知道目前是使用某一個版本的Drawing繪圖程式而不必知道是V1或是V2

再看一次下面的圖


Client只知道Shap 所以我們可以思考出左邊的類別圖,Shap應該只知道Drawing
所以我們會得到右邊的圖,接著兩張圖該怎麼聯結再一起呢?

這是一個抽象與實作分離的例子,形狀的抽象與繪圖實作分離,這其實就是Bridge模式。

讓我們來看一下Bridge模式的特徵

意圖:將一組實作與另一組使用他們的物件分離

問題:一個抽象類別的衍生類別必須使用多個實作,但不能出現類別數量爆炸性增長。

參與者與協作者:Abstraction為要實作的物件定義介面,Implementor為具體的實作類別定義介面
Abstraction的衍生類別使用Implementor的衍生類別,卻無須知道自己具體使用哪一個ConcreteImplementor

解決方案:為所有實作定義一個介面,供抽象類別的所有衍生類別使用。

效果:實作與使用實作的物件解耦,提供了可擴展性,客戶物件無須操心實作問題。

實作:
  • 將實作封裝在一個抽象類別。
  • 在要實作的抽象基礎類別中包含一個實作(也可利用介面代替抽象類別)。


小結:

Bridge模式把握幾個原則

物件對自己負責: Shap可以繪製自己,Drawing負責繪圖元素

抽象類別:Shap代表形狀,Drawing代表繪圖程式

透過抽象類別進行封裝:Client只看的到Shap , Shap只知道Drawing 

一條規則,實作一次:  抽象類別中經常有些方法實際使用實作物件,抽象類別的子類別會忽叫這些方法,如此再需要修改時,只需要修改一個地方。

可測試性:改進的方案,讓我們可以很輕易的做測試,可分別對shap以及drawing做測試。

2014年4月13日 星期日

設計模式 (十一) 開拓視野 part2

開拓視野 part1 提出了物件,封裝,繼承的一些不同看法,在part2會針對聚合,共通性和可變性分析與抽象類別,以及敏捷程式設計的品質做相關的介紹。

在part1我們可以瞭解到特殊化繼承帶來的便利性,以及之後所造成的麻煩,為了避免這個問題,我們可以使用聚合的方式將需要的功能帶入,卻又可以保持內聚性以及封裝性,

舉個例子
現在有個需求

  • 每種動物都有數量不同的腿
  • 動物物件必須能夠記住並獲取這一資訊
  • 每種動物的移動方式都不同
  • 對於指定的地形類型,動物物件必須能夠計算出往返的時間
接著該怎麼實作呢?
我們發現每種動物的移動方式是不同的,有些動物是會飛,有些動物會跑,有些動物會游泳,我們總不能為了每種動物都使用一個method吧,但是我們發現他們的都有移動的行為,我們發現了變化(移動方式),同時我們也發現了相同的概念(移動的行為),所以我們可以將變化的部分封裝起來,在Design pattern中有提到

考慮設計中哪些地方可能有變化,如何在不重新設計的情況下增加需求因應變化,關鍵在於封裝變化的概念。

經過封裝變化之後我們的uml 會如下所示

這麼做的好處在於,當有新的behavior出現,只需要增加一個class即可,並不會重新設計。

注意到上面在分析如何封裝時其實有一個分析的方式叫做共通性和可變性分析和抽象類別,
共通性分析就是尋找一些共同的要素,例如移動的行為,可變性分析則說明了物件之間不同的行為,例如飛,走,游。

在設計模式的解析與活用中提到

共通性分析尋找的是不可能隨時間而改變的結構,而可變性分析則要找到可能變化的結構,
從架構的視角來看,共通性分析為架構提供長效的要素,而可變性分析則促進適應實際使用所需。



從上面這張圖我們可以看出這樣的概念

  • 抽象類別(共通性):需要用甚麼介面來處理這個類別的所有責任
  • 衍生類別(可變性):對於這個指定的實作(這個變化),應該怎樣根據指定的規約來實作他

下面有關於使用抽象類別進行特殊化的好處,也就是繼承抽象的好處



我們發現這種設計模式,總是預先設計大概的雛形,這是否跟敏捷開發中不預先設想的想法衝突?

關於這個問題我們再進一步仔細思考,我認為在某些情況下我們的確不該預先設想,過於關注細節,我們應該觀察物件發生變化的可能,並且使用封裝的方式將變化封裝,然而我們必須注意的是,敏捷開發以及設計模式所關注的共同要點
  1. 無冗餘
  2. 可讀性
  3. 可測試性
無冗餘性

Kent Back Once and Only Once rule,他說明了功能只在一個地方實作,也就是同樣的程式碼不出現在第二個地方,避免出現重複的程式碼,消除重覆的好處在於,當功能需要更改時我們只需要更改一個地方,而且在更改時我們不需再去考慮是否有遺漏。
這種概念就跟依介面設計的概念不謀而合,找出變化,利用介面使程式碼高度內聚。

可讀性

也就是反應意圖,這如同依介面設計的概念,當我們使用介面時,我們就是利用了介面來傳遞我們的概念,例如  上面動物行為的例子中  behavior.move(),代表著動物執行了移動的動作
但是我們並不在乎動物是如何移動的,它反映了動物的意圖。

可測試性

TDD與依介面設計的概念也是一樣的,如果封裝的好,就會產生高內聚,鬆耦合的程式碼,
也會讓我們在寫測試的時候更加的方便。

其實敏捷開發與設計模式要求的都是

內聚: 程式碼更容易測試,因為程式碼只負責一個責任(SRP)
鬆耦合:由於需要關注的東西較少,所以在測試的時候不需要去在乎太多其他類別的東西
冗餘程式碼:冗餘程式碼過多會造成測試涵蓋率下降
可讀性好:可以更明確的表達意圖
封裝性好:鬆耦合,減少對其他物件的關注

小結

其實不管是設計模式或是敏捷開發,其實大家在乎的要點都是,如何開發出高品質的程式碼,
簡單的說,想辦法寫出高內聚,鬆耦合,封裝變化的程式碼就對了

2014年4月10日 星期四

設計模式 (十) 開拓視野 part1

關於物件導向設計有三個基本概念,物件,封裝和抽象類別,設計人員對這些概念的看法是很重要的,傳統的看法有很大的侷限性

傳統的概念

  1. 將物件視為資料和方法的簡單集合
  2. 封裝視為資料隱藏
  3. 繼承 = 特殊化再利用
新的看法

  1. 將物件視為具有責任的東西
  2. 封裝視為隱藏一切的一種能力
  3. 繼承 = 物件分類的一種方法
  4. 共通性與可變性分析
  5. 概念視角,規約視角,實作視角抽象類別與衍生類別的關係
  6. 設計模式與敏捷程式設計方法的差別(冗餘性,可讀性,測試性)

物件

以傳統的方式來看,物件只不過是個資料處理的方法,然而新的看法則是物件是一個具有責任的實體,這兩個差別在於,將物件視為一個責任的實體會讓我們更專注於物件的意圖(責任),而不是如何實作他,我們可以利用這個概念
  1. 做出初步的設計,這個物件的責任為何,(interface abstract)各種抽象的概念
  2. 當我們將一切的職責分配完成再來關心實作部分
舉個例子來說

有個需求希望在畫面上顯示/移除形狀,依照這個需求我們可以想像出一個物件需要負責這些事情
  1. 選擇一種形狀
  2. 顯示
  3. 移除
  4. 取得位置
再進一步仔細想想,是否有發現,所有的形狀物件都一定擁有2,3,4三個必要功能(責任)
所以最後的UML圖會長像是下面的樣子
在此時我們尚未關注細節,我並不曉得方形該怎麼顯示,更不知道三角形該如何取得各點的位置,我不需要瞭解到細節,事實上,這種概念將物件的實作與使用他的物件解耦了。


封裝

傳統的看法為資料的隱藏,然後這種看法侷限性太大了,新的看法則是將封裝視為任何型式的隱藏
  1. 實作細節
  2. 衍生類別
  3. 設計細節
  4. 實體化設計

讓我們看看上面的UML圖,這是一個adapter pattern的UML圖

Shape shape = new Rectangle(); 
  • 這個動作為類的封裝

shape.Display();  
  • 實作的封裝

利用adapter pattern 將 third-party Circle封裝進 Circle  
  • 其他物件的封裝
Rectangle / Triangle/Circle ,詳細的資料只存在自己的class內部,例如一些flag ,各自的location....etc
  • 資料的封裝

用這種更寬廣方式來看待封裝,優點就是能夠帶來一種更容易分解程式的方法,而封裝層就成為了設計需要遵循的介面,透過封裝Shape的衍生類別,當我有新的形狀要加入時只需要新增一個class就好了,並不會對其他的程式造成影響。

早期的提倡者,曾將類別得再利用作為巨大的優勢,通常都是建立一個基礎類別之後,然後再從基礎類別衍生出新的特殊化的類別,如下圖


可以想成是普通的圓形以及以虛線畫成的特殊圓形,
利用繼承的好處是我可以快速的利用Circle已經實作的程式碼,但是...........
壞處呢?

  • 繼承造成了弱內聚,我再修改Circle的程式碼時,我還必須關注SpecialCircle是否會出現SideEffect。
  • 假設我今天希望畫一個虛線版本的三角形呢? 畫虛線的程式碼是否無法再利用,我必須重新建立一個新的物件叫做SpecialTriangle,然後裡頭再重新實作虛線的功能,這種作法減少了程式碼的再利用性
  • 同上面描述的,這也減少了物件的延展性,假設今天有更多更多的類別出現,粗線圓形,紅色圓形,漸層圓形,你會發現程式越來越難改,if else判斷越加越多,bug越來越多?!
而另一種方式則是依相同行為分類,使用聚合,關於這部分會在part2的時候做更多的說明~

2014年4月8日 星期二

設計模式 (九) adapter pattern

adapter pattern又稱轉換器,介面轉換。

adapter pattern 就是將一個類別的介面轉換成客戶希望的另一個介面,也就是讓原本不相容的介面一起工作。

舉例來說,假設有以下的需求

  1. 我希望每個shape類別都有顯示的行為
  2. 使用者不需要知道物件為甚麼形狀
接著大概會出現類似下面的程式碼

public abstract class Shape {

	public abstract void Show();
}

class Rectangle extends Shape
{

	@Override
	public void Show() {
		System.out.println("Rectangle");
	}
	
}

class Triangle extends Shape
{

	@Override
	public void Show() {
		System.out.println("Triangle");
	}
}

UML圖會長這樣

可以很看出,Rectangle和Triangle繼承並實作了Shape

接著在此時又出現了一個需求,需要再多實作一個五芒星的圖型
可是我並不知道如何實作這個功能,所以請教了google 大神之後,我得到了一個第三方的sdk
他的功能很完整,不過實作方法有點不同,如下

public class FiveStar
{
	public void FiveStarDisplay()
	{
		System.out.println("FiveStar");
	}
}

可以發現到,我現在必須使用if else 來判斷該怎麼顯示圖型

	public static void showShap(Object shape)
	{
		if(shape instanceof Shape){
			((Shape)shape).Show();
		}
		else if(shape instanceof FiveStar){
			((FiveStar)shape).FiveStarDisplay();
		}
	}
這是個悲劇的開始,客戶必須區分這是甚麼形狀,只要用到shape就必須多加if else 判斷,
並且或許在未來某天,還可能誤將 shape 轉型轉錯造成app crash。

那麼我們可以做甚麼來避免這場悲劇的發生呢?

先思考一個問題,怎麼做對我們最好?
如果我們有辦法將FiveStar視為Shape那是不是所有的問題都迎刃而解了呢?
可是該怎麼做呢?

看看下面的方式

class FiveStarAdapter extends Shape
{
	private FiveStar star;
	@Override
	public void Show() {
		// TODO Auto-generated method stub
		star.FiveStarDisplay();
	}
	
}

我們將 FiveStar封裝在 FiveStarAdapter裡這樣既達到了實作五芒星的功能,又可以維持客戶不需要區分是甚麼形狀就可以使用,接著就可以把showShap改成這樣


	public static void showShap(Shape shape)
	{
			shape.Show();
	}


程式變得更加簡潔,而且更低耦合度了


結論
Adapter的實作方式就是建立一個具備所需介面的新類別(FiveStarAdapter),然後包裝原有的類別(FiveStar),如此就可以將原有的類別轉型成Shape

2014年4月6日 星期日

設計模式 (八) facade pattern

Facade Pattern

定義一個更高層的介面,使子系統更加容易使用,提供更簡單的方法與系統交流

  • 意圖:希望簡化原有系統的使用方式
  • 問題:只需要使用某個複雜系統的子集,或者需要以一種特殊的方式與系統交流
  • 解決方案:façade為原有系統的客戶提供了一個新的介面
  • 參與者與協作者:為客戶提供一個簡化介面,更容易使用
  • 效果:façade模式簡化了對所需子系統的使用過程,由於façade並不提供完整的功能,客戶可能無法使用某些功能
  • 實作:定義一個或多個具備所需介面的新類別;讓新的類別使用原有的系統

下面有四個class 分別是家庭Facade控制器,風扇控制器,燈光控制器,電視控制器

public class HomeDeviceFacade {
 private FanController FC = new FanController();
 private LightController LC = new LightController();
 private TVController TC = new TVController();
 
 public void DeviceOn()
 {
  FC.TurnOn();
  LC.TurnOn();
  TC.TurnOn();
 }
 
 public void DeviceOff()
 {
  FC.TurnOff();
  LC.TurnOff();
  TC.TurnOff();
 }

 public static void main(String args[]) {
    
 System.out.println("====Facade====");
     HomeDeviceFacade facade = new HomeDeviceFacade();

        facade.DeviceOn();
        System.out.println();
        facade.DeviceOff();          
    }
}

class FanController
{
 private int speed;
 public void ChangeFanSpeed(int _speed)
 {
  speed = _speed;
 }
 public void TurnOn()
 {
  System.out.println("Fan on");
 }
 
 public void TurnOff()
 {
  System.out.println("Fan off");
 }
}

class LightController
{ 
 public void TurnOn()
 {
  System.out.println("Light on");
 }
 
 public void TurnOff()
 {
  System.out.println("Light off");
 }
}

class TVController
{
 private int vol;
 public void changeVolume(int _vol)
 {
  vol = _vol;
 }
 public void TurnOn()
 {
  System.out.println("TV on");
 }
 
 public void TurnOff()
 {
  System.out.println("TV off");
 }
}


可以看出Facade讓我們簡化了其他的動作,原本需要一個一個將電視風扇燈光打開,現在只需要一個步驟就可以將所有的裝置做開關,這簡化了系統

結論
Facade模式提出了一種通用方法,建立了新介面供客戶使用, 客戶並不需要原有系統的所有功能

Facade還有其他功用
  • 追蹤系統的使用情況
由於所有的裝置都會經過HomeDeviceFacade 所以我們可以利用這個class 追蹤裝置的使用狀況 
  • 改換系統
由於裝置都經由HomeDeviceFacade 做處理,當有新版本的 TVController2 出現時,我們的控制程式(Main)並不需要做更改,只需要修改HomeDeviceFacade  裡的 TVController即可。

最後依照慣例附上 Sample Code

2013年10月17日 星期四

CleanCode 讀書筆記 (四) 邊界

邊界主要是描述使用第三方API時如何想辦法
將API與我們內部的程式分隔開來   減低耦合
裡面有提到可以使用 Adapter  (轉接器)
Adapter是系統 與 API 之間的橋梁
使得API有所更動時  不會影響到系統  只需要在 Adapter上做修改即可

以下兩個範例  廠商在開發初期出版了  FactoryAPI_0
以及過了一陣子出版了新版  FactoryAPI_1
在兩版之間  裡面有一個method做了修正
如果沒有使用Adapter  會使得主程式必須做修正  修正後也可能會衍生出bug
但是使用Adapter的話  由於修改都在一個class內   相對來說比較不會衍生出新問題

廠商舊版FactoryAPI_0


    public class FactoryAPI_0
    {
        public void Make()
        {
            System.Diagnostics.Debug.Write("Make");
        }
    }

    public class FactoryAdapter
    {
        private FactoryAPI_0 ApI;
        public FactoryAdapter(FactoryAPI_0 API)
        {
            this.ApI = API;
        }
        public void Process()
        {
            ApI.Make();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            FactoryAPI_0 api = new FactoryAPI_0();
            FactoryAdapter adapter = new FactoryAdapter(api);
            adapter.Process();
            Console.Read();

        }
    }



廠商新版 FactoryAPI_1


    public class FactoryAPI_1
    {
        public void Execute()
        {
            System.Diagnostics.Debug.Write("Make");
        }
    }
    public class FactoryAdapter
    {
        private FactoryAPI_1 ApI;
        public FactoryAdapter(FactoryAPI_1 API)
        {
            this.ApI = API;
        }
        public void Process()
        {
            ApI.Execute();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            FactoryAPI_0 api = new FactoryAPI_0();
            FactoryAdapter adapter = new FactoryAdapter(api);
            adapter.Process();
            Console.Read();

        }
    }
由上面兩個範例可以發現    主程式並沒有被更動到
所有的更動都在 class 內部
造成的影響也都在class內   主程式成功的與API 分開

簡潔的程式邊界
當改變來臨時
好的設計能適應變化
不好的設計必須重新改寫或投入大量時間做修正

API 可以用封裝的方式  或者使用adapter
使API 被修正時  我們只需要做及少的修正

2013年10月5日 星期六

設計模式 (七) Observable Pattern

觀察者模式
 當某個狀態改變時就通知所有訂閱者
 目的是要讓互動的物件做鬆綁 兩個物件可以作互動 
但彼此之間並不太清楚彼此細節
下面的範例是 當有個人Post一篇文章
 所有他的定閱者都會收到通知 然後都可以做他們相對應的動作

        public interface Notifier
        {
             void Notify();            
        }
        public class Paul : Notifier
        {
            public void Notify()
            {
                Console.WriteLine("Paul 立刻去看文章");
            }
        }
        public class Ted : Notifier
        {
            public void Notify()
            {
                Console.WriteLine("Ted 把文章加入書籤");
            }
        }
        public class Tina : Notifier
        {
            public void Notify()
            {
                Console.WriteLine("Tina 不理會");
            }
        }
        public class ArticlePoster
        {
            private List<Notifier> PersonList = new List<Notifier>();
            public void Add(Notifier person)
            {
                PersonList.Add(person);
            }
            public void PostArticle()
            {
                foreach (Notifier person in PersonList)
                {
                    person.Notify();
                }
            }
        }
        static void Main(string[] args)
        {
            ArticlePoster poster = new ArticlePoster();
            poster.Add(new Paul());
            poster.Add(new Tina());
            poster.Add(new Ted());
            poster.PostArticle();
            Console.ReadKey();
        }

2013年10月4日 星期五

設計模式 (六) Iterator Pattern

Iterator Pattern 為反覆器讓我們能夠取得collection內的元素(string)而不需要取得collection (List<string>,string[]) 這樣的好處讓我們可以將這些元素  一視同仁(printDevice)
讓我們方便處理

下面的sample  我們希用使用printDevice 去印出Ted 和 Paul身上的配件
但是他們兩個是使用不同的collection  Ted是使用List<string>  而 Paul是使用string[]
通常都會使用以下的寫法
 
      

        class Ted_Devices

        {

            private List<string> items = new List<string>();

            public Ted_Devices()

            {

                items.Add("Phone");

                items.Add("Bag");

                items.Add("Pad");

            }

            public List<string> GetItems()

            {

                return items;

            }

        }

        class Paul_Devices

        {

            private string[] items = new string[3];

            public Paul_Devices()

            {

                items[0]="Pencil";

                items[1]="Watch";

                items[2]="book";

            }

            public string[] GetItems()

            {

                return items;

            }

        }

        static void Main(string[] args)

        {

            Ted_Devices TD = new Ted_Devices();

            List<string> TD_list = TD.GetItems();

            Paul_Devices PD = new Paul_Devices();

            string[] PD_list = PD.GetItems();

            Console.WriteLine("===Ted list====");

            printDevice(TD_list);

            Console.WriteLine("====Paul list====");

            printDevice(PD_list);

            Console.ReadLine();

        }



        private static void printDevice(object obj)

        {

            if (obj is List<string>)

            {

                foreach (string item in obj as List<string>)

                {

                    Console.WriteLine(item);

                }

            }

            else

            {

                foreach (string item in obj as string[])

                {

                    Console.WriteLine(item);

                }

            }

        }
Result:
===Ted list====
Phone
Bag
Pad
====Paul list====
Pencil
Watch
book
這樣的缺點是
當需要印更多人的裝置而每個人使用的collection的格式都不同時
例如出現了 int[] , List<CustomData>, ....等等的資料時
printDevice 就會長的越來越可怕  判斷式越來越多
如果這時出現新的需求不是只需要印device
還需要針對每個人身上的配件去做其他項目的處理時....
這樣的code就會變的非常難以maintain以及新增功能

下面的寫法是使用Iterator的方式

public interface Iterator
    {
        bool hasNext();
        Object next();
    }
    class TedIterator : Iterator
    {
        private List<string> items;
        int position = 0;
        public TedIterator(List<string> items)
        {
            this.items = items;
        }
        public bool hasNext()
        {
            if (position &gt;= items.Count)
                return false;
            else
                return true;
        }

        public object next()
        {
            string CurrentItem = items[position];
            position++;
            return CurrentItem;
        }
    }
    class Ted_Devices
    {
        private List<string> items = new List<string>();
        public Ted_Devices()
        {
            items.Add("Phone");
            items.Add("Bag");
            items.Add("Pad");
        }
        public Iterator CreateIterator()
        {
            return new TedIterator(items);
        }
    }
    class PaulIterator : Iterator
    {
        private string[] items;
        int position = 0;
        public PaulIterator(string[] items)
        {
            this.items = items;
        }
        public bool hasNext()
        {
            if (position &gt;= items.Length || items[position]==null)
                return false;
            else
                return true;
        }

        public object next()
        {
            string CurrentItem = items[position];
            position++;
            return CurrentItem;
        }
    }
    class Paul_Devices
    {
        private string[] items = new string[3];
        public Paul_Devices()
        {
            items[0] = "Pencil";
            items[1] = "Watch";
            items[2] = "book";
        }
        public Iterator CreateIterator()
        {
            return new PaulIterator(items);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Ted_Devices TD = new Ted_Devices();
            Iterator TD_list = TD.CreateIterator();
            Paul_Devices PD = new Paul_Devices();
            Iterator PD_list = PD.CreateIterator();
            Console.WriteLine("===Ted list====");
            printDevice(TD_list);
            Console.WriteLine("====Paul list====");
            printDevice(PD_list);
            Console.ReadLine();
        }

        private static void printDevice(Iterator obj)
        {
            while (obj.hasNext())
            {
                Console.WriteLine(obj.next());
            }
        }
    }
Result:
===Ted list====
Phone
Bag
Pad
====Paul list====
Pencil
Watch
book
從Main function就可以很清楚的看到  兩個人的collection  List<string> 和 string[]都被轉為iterator
若有需要對data做進一步的處理  他們可以使用相同的寫法(printDevice) 去印出他們身上的device
若有進一步的處理  也可以使用同樣一套寫法 (不需要用if else去區分是誰的device)
這樣子在maintain以及開發上就會相對輕鬆許多

2013年10月3日 星期四

設計模式 (五) Visitor Pattern

參觀者模式  其實概念就是將演算法跟結構做分離
下面的範例使用將學生的成績利用兩個演算法做排序 ( BigToSmall , SmallToBig)
使用訪問者模式的好處是
1.針對結構新增演算法時只需要新增class即可
2.參觀者所做的操作都集中在class 裡 trace 較方便
缺點是

1.當需要針對該結構做更改時  會影響到所有演算法 (更改會較困難)



    abstract class Visitor

    {

        abstract public void Visit(Element element);

    }



    class BigToSmallVisitor : Visitor

    {



        public override void Visit(Element element)

        {

            ScoreSheet sheet = (ScoreSheet)element;



            sheet.ScoreList.Sort((x, y) => { return -x.Score.CompareTo(y.Score); });



            foreach (ScoreData item in sheet.ScoreList)

            {

                Console.WriteLine(item.Name + " / " + item.Score);

            }

        }

    }



    class SmallToBigVisitor : Visitor

    {



        public override void Visit(Element element)

        {

            ScoreSheet sheet = (ScoreSheet)element;



            sheet.ScoreList.Sort((x, y) => { return x.Score.CompareTo(y.Score); });



            foreach (ScoreData item in sheet.ScoreList)

            {

                Console.WriteLine(item.Name + " / " + item.Score);

            }

        }

    }



    class ScoreData

    {

        private int score;

        private string name;

        public int Score

        {

            get { return score; }

            set { score = value; }

        }

        public string Name

        {

            get { return name; }

            set { name = value; }

        }



        public ScoreData(string name, int score)

        {

            this.Name = name;

            this.Score = score;

        }

    }



    abstract class Element

    {

        abstract public void Accept(Visitor visitor);

    }



    class ScoreSheet : Element

    {

        public List<ScoreData> ScoreList = new List<ScoreData>();

        public void Add(ScoreData data)

        {

            ScoreList.Add(data);

        }

        public override void Accept(Visitor visitor)

        {

            visitor.Visit(this);

        }

    }

    public class VisitorApp

    {

        public static void Main(string[] args)

        {

            ScoreSheet sheet = new ScoreSheet();

            sheet.Add(new ScoreData("John", 100));

            sheet.Add(new ScoreData("Marry", 90));

            sheet.Add(new ScoreData("Ted", 60));

            sheet.Add(new ScoreData("Bob", 70));

            BigToSmallVisitor bigtosmall = new BigToSmallVisitor();

            SmallToBigVisitor smalltobig = new SmallToBigVisitor();

            Console.WriteLine("=================");

            sheet.Accept(bigtosmall);

            Console.WriteLine("=================");

            sheet.Accept(smalltobig);

            Console.ReadKey();

        }

    }
Result:
 =================
 John / 100 
 Marry / 90 
 Bob / 70 
 Ted / 60 
 ================= 
 Ted / 60 
 Bob / 70 
 Marry / 90 
 John / 100

2013年9月25日 星期三

設計模式 (四) Composite Pattern

通常會用到composite pattern都是在資料為樹狀圖時才會用到它
他的做法是將聚合元素(枝)與單一元素(葉) 視為同一個東西 (繼承同一個物件)
這樣的好處是當新增其他的葉時並不需要修改其他的class
先示範較不好的範例
他的缺點是當有新的葉class要做新增時例如希望新增 BCounty的時候對於bnation來說需要做大量的修改才能順利加BCounty加入
     
    class BRoot
    {
        private string name;
        private List<bnation> Nations = new List<bnation>();
        public BRoot(string name)
        {
            this.name = name;
        }
        public void Add(BNation b)
        {
            Nations.Add(b);
        }

        public void Display(int depth)
        {
            Console.WriteLine(new String('-', depth) + name);
            foreach (var Nation in Nations)
            {
                Nation.Display(depth + 3);
            }
        }

    }
    class BNation
    {
        private string name;
        private List<bcity> Cities = new List<bcity>();
        public BNation(string name)
        {
            this.name = name;
        }

        public void Add(BCity b)
        {
            Cities.Add(b);
        }

        public void Display(int depth)
        {
            Console.WriteLine(new String('-', depth) + name);
            foreach (var city in Cities)
            {
                city.Display(depth + 3);
            }
        }
    }
    class BCity
    {
        private string name;
        public BCity(string name)
        {
            this.name = name;
        }
        public void Display(int depth)
        {
            Console.WriteLine(new String('-', depth) + " city " + name);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            BRoot root = new BRoot("Root");
            BNation BChina = new BNation("China");
            BNation BTaiwan = new BNation("Taiwan");
            
            BChina.Add(new BCity("Ba Jin"));
            BChina.Add(new BCity("Su Jou"));                       
            BTaiwan.Add(new BCity("Taipei"));
            BTaiwan.Add(new BCity("Tainan"));

            root.Add(BChina);
            root.Add(BTaiwan);
            root.Display(0);

            Console.WriteLine();
        }
   }
Result:
Root
---China
------ city Ba Jin
------ city Su Jou
---Taiwan
------ city Taipei
------ city Tainan

正確的範例如下
當需要增加BCounty的時候  就如同增加BCity一樣 新增一個class就可以完成
新的修改並不會對舊有的code造成修改
    
    abstract class Component
    {
        protected string name;

        // Constructor
        public Component(string name)
        {
            this.name = name;
        }

        public abstract void Add(Component c);
        public abstract void Remove(Component c);
        public abstract void Display(int depth);
    }
    class Nation : Component
    {
        private List<component> Nations = new List<component>();
        public Nation(string name)
            : base(name)
        {
        }

        public override void Add(Component c)
        {
            Nations.Add(c);
        }

        public override void Remove(Component c)
        {
            throw new NotImplementedException();
        }

        public override void Display(int depth)
        {
            Console.WriteLine(new String('-', depth) + name);
            foreach (Component City in Nations)
            {
                City.Display(depth + 3);
            }
        }
    }
    class City : Component
    {
        public City(string name)
            : base(name)
        {
        }

        public override void Add(Component c)
        {
            throw new NotImplementedException();
        }

        public override void Remove(Component c)
        {
            throw new NotImplementedException();
        }

        public override void Display(int depth)
        {
            Console.WriteLine(new String('-', depth) + " city " + name);
            //throw new NotImplementedException();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Nation Root = new Nation("Root");
            Nation China = new Nation("China");
            Nation Taiwan = new Nation("Taiwan");
            
            China.Add(new City("Ba Jin"));
            China.Add(new City("Su Jou"));
            Taiwan.Add(new City("Taipei"));
            Taiwan.Add(new City("Tainan"));

            Root.Add(Taiwan);
            Root.Add(China);
            Root.Display(0);
        }
     }
Result:
Root
---Taiwan
------ city Taipei
------ city Tainan
---China
------ city Ba Jin
------ city Su Jou

2013年9月23日 星期一

設計模式 (三) Abstract Factory

觀察 main function 可得知
使用者不需要關心 他所創造的是哪個OEM工廠
他只需要知道他目前需要製造的東西是甚麼
好處是 如果使用者之後需要新增OEM工廠
只需要再新增一個class就好
而不需要把code寫在main裡面造成維護或讀code上面的困難
下面的範圍實做兩個OEM工廠
兩個OEM工廠都可以製作手機以及電話


  
    
    abstract class IPhoneBase
    {
        public abstract void Dial();
    }

    abstract class INoteBook
    {
        public abstract void PowerOn();
    }

    interface IFactory
    {
        IPhoneBase CreatePhone();
        INoteBook CreateNoteBook();
    }

    class SamsugPhone : IPhoneBase
    {
        public override void Dial()
        {
            Console.WriteLine("Samsug Phone Is Dialing");
        } 
    }

    class AsuPhone : IPhoneBase
    {
        public override void Dial()
        {
            Console.WriteLine("Asu Phone Is Dialing");
        }
    }

    class SamsugNotBook : INoteBook
    {
        public override void PowerOn()
        {
            Console.WriteLine("Samsug NoteBook Is PowerOn");
        }
    }

    class AsuNotBook : INoteBook
    {
        public override void PowerOn()
        {
            Console.WriteLine("Asu NoteBook Is PowerOn");
        }
    }

    class AsuFactory : IFactory
    {
        public IPhoneBase CreatePhone()
        {
            return new AsuPhone();
        }
        public INoteBook CreateNoteBook()
        {
            return new AsuNotBook();
        }
    }

    class SamsugFactory : IFactory
    {
        public IPhoneBase CreatePhone()
        {
            return new SamsugPhone();
        }
        public INoteBook CreateNoteBook()
        {
            return new SamsugNotBook();
        }
    }

    class OEMFactory
    {
        public IFactory GetFactory(string name)
        {
            if (name == "Samsug")
            {
                return new SamsugFactory();
            }
            else if (name == "Asu")
            {
                return new AsuFactory();
            }
            else
                return null;
        }
    }



    class Program
    {
        static void Main(string[] args)
        {
            OEMFactory Factory = new OEMFactory();
            IFactory AsuFactory = Factory.GetFactory("Asu");
            AsuFactory.CreatePhone().Dial();
            AsuFactory.CreateNoteBook().PowerOn();

            IFactory SamsungFactory = Factory.GetFactory("Samsug");
            SamsugFactory.CreatePhone().Dial();
            SamsugFactory.CreateNoteBook().PowerOn();
            Console.ReadKey();
        }
    }
優點
1.產品從代碼中被分離出來
2.容易改變產品的系列
3.可拿到建立OEM系列的產品族的接口
缺點
1.當要新增OEM工廠裡的產品時須對所有的 OEM class 做修改

2013年9月22日 星期日

設計模式 (二) Strategy Pattern

策略模式 讓使用者可以動態改變演算法
這樣的寫法是使用合成方式 而不是使用繼承來完成我們需要的功能
這樣的好處是彈性較大  假設之後需要針對我們現在的寫法做(需求)
變更  我們並不需要針對舊有的程式做更改  只需要新增一個class就可以了
這是多麼開心的事情  新增功能又不會影響到舊有的功能


接著講sample
假設我今天有一段程式在不同的情境需要套用不同的sort 演算法
就可以套用strategy pattern
代碼如下

C#

    abstract class Sorting
    {
        abstract public void Process();
    }
    class QuickSort : Sorting
    {
        public override void Process()
        {
            System.Diagnostics.Debug.Write("QickSort Algorithm");
        }
    }
    class MergeSort : Sorting
    {
        public override void Process()
        {
            System.Diagnostics.Debug.Write("MergeSort Algorithm");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Sorting sortalgorithm = new QuickSort();
            sortalgorithm.Process();
            sortalgorithm = new MergeSort();
            sortalgorithm.Process();
        }
    }

Java

abstract class Sorting
{
    abstract public void Process();
}
class QuickSort extends Sorting
{
    public void Process()
    {
     System.out.println("QuickSort");
        
    }
}
class MergeSort extends Sorting
{
    public void Process()
    {
     System.out.println("MergeSort");
    }
}

public class strategy {

 /**
  * @param args
  */


 public static void main(String[] args) {
  // TODO Auto-generated method stub
        Sorting sortalgorithm = new QuickSort();
        sortalgorithm.Process();
        sortalgorithm = new MergeSort();
        sortalgorithm.Process();
 }

}

總結  寫code前多想一下吧
多用合成  少用繼承  未來maintain的生活會變得更美好 XD

設計模式 (一) Singleton

Singleton Pattern 就是獨體模式,當需要建立獨一無二的物件時可以使用
以下是範例

C#

    class LoginObj
    {
        private static LoginObj loginobj;
        private LoginObj()
        { }
        public static LoginObj GetInstance()
        {
            if (loginobj == null)
            {
                loginobj = new LoginObj();
            }
            return loginobj;
        }
        public void SetLoginData()
        {
            System.Diagnostics.Debug.Write("Set data");
        }
        public  void GetLoginData()
        {
            System.Diagnostics.Debug.Write("Get data");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            LoginObj loginobj = LoginObj.GetInstance();
            loginobj.SetLoginData();
            loginobj.GetLoginData();
        }
    }

Java

 
public class loginObj {

 /**
  * @param args
  */
 private static loginObj _loginObj;
 private loginObj(){}
 public static loginObj GetInstance()
 {
  if(_loginObj==null)
  {
   _loginObj = new loginObj();  
  }
  return _loginObj;
 }

 public static void main(String[] args) {
  // TODO Auto-generated method stub
  loginObj loginobj = loginObj.GetInstance();
  
 }

}
上面使用singleton建立唯一的物件來存取以及讀取user login的資料 上面的用法是不考慮multi-thread時的寫法 當該class 會同時被多個thread呼叫時使用上面的寫法還是有可能會Create出兩個物件 所以必須改用以下的寫法

C#

       class LoginObj
    {
        private static LoginObj loginobj;
        private static object syncObj = new Object();
        private LoginObj()
        { }
        public static LoginObj GetInstance()
        {
            if (loginobj == null)
            {
                lock (syncObj)
                {
                    if (loginobj == null)
                    {
                        loginobj = new LoginObj();
                    }
                }
            }
            return loginobj;
        }
        public void SetLoginData()
        {
            System.Diagnostics.Debug.Write("Set data");
        }
        public  void GetLoginData()
        {
            System.Diagnostics.Debug.Write("Get data");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            LoginObj loginobj = LoginObj.GetInstance();
            loginobj.SetLoginData();
            loginobj.GetLoginData();
        }
    }

Java

 
public class loginObj {

 /**
  * @param args
  */
 private static loginObj _loginObj;
 private loginObj(){}
 public static loginObj GetInstance()
 {
  if(_loginObj==null)
  {        
      synchronized(loginObj.class){
         if(_loginObj == null) {                
            _loginObj = new loginObj();  
         }
      }
   
  }
  return _loginObj;
 }

 public static void main(String[] args) {
  // TODO Auto-generated method stub
  loginObj loginobj = loginObj.GetInstance();
  
 }

}
通常會用到singleton的時機為
1.想要一個global物件可以做資料存取
2.當使用了某些只能被init一次的API
3.當必須確保該物件為唯一
Reference : 深入淺出設計模式