2016年11月13日 星期日

設計模式九:建造者模式(Builder)

定義:將一個複雜物件的構建與該物件的表示(representations)分離,使得同樣的構建過程可以建立不同的表示。(P.S. 這裏的表示應該是指物件各個不同的參數。)

Abstract factory and factory patterns are designed for polymorphism. builder pattern is not. The builder pattern uses another object, a builder, that receives each initialization parameter step by step and then returns the resulting constructed object at once.

就像去餐廳點餐一樣,客人只要向服務生這個介面下要求就好,不必參與後面餐點的複雜「建造」過程。


設計模式八:外觀模式(Facade)

定義:為子系統中的一「組」介面提供一「個」一致的介面,此模式定義了一個高層介面,這個介面使得這一子系統更加容易使用。

這似乎在一般的refactor中都會自然發生的結果,因為要減少程式碼,將重覆使用子類別的程式集合起來,自然就形成了一個新的介面。要不斷利用這些子類別時,透過這個新的介面,就可以減少程式碼、使用和維護起來也更容易。

那麼,和原型模式不同的地方是?一個著眼在物件的產生,另一個是在提供介面。這個理解是正確的嗎?

2016年4月26日 星期二

物件導向(4)迪米特法則

Low of Demeter: 就是最少知識原則。如果兩個類別不該直接通信,那麼這兩個類別就不應當發生直接的相互作用。如果其中一個類別需要調用另一個類別的某一個方法的話,可以透過第三者轉發這個調用。

這其實也包含在MVC架構裏。view由controller控制,而controller也取得model的資料。但model和view彼此無關。這樣view可以reuse, model和view都可以分開維護和發展。

換句話說,軟體有更好的可維護性與適應性。因為某甲較少依賴某乙的內部結構,則可以改變容器(container)而不用改變調用者(caller)。

所以這仍然是基於物件導向的精神產生的原則。

設計模式七:範本方法(Template method)

範本方法(template method pattern)模式:定義一個操作中的演算法的骨架,而將一些步驟延遲到子類別中。範本方法使得子類別可以不改變一個演算法的結構即可以重定義該演算法的某些特定步驟。

舉例來說:

動物喝水三步驟:走到水邊,「讓水和嘴接近」,張嘴喝水。
狗狗喝水:走到水邊,「低頭就水」,張嘴喝水。
人類喝水:走到水邊,「拿碗乘水、以碗就口」,張嘴喝水。

這樣算是範本模式?


2016年4月14日 星期四

設計模式六:原型模式(Prototype)

原型模式:用原型實例指定建立物件的種類。
而且必須注意的是,如果物件內含其他物件,就要了解何時用淺層複製,何時用深層複製。

如果物件有一個clone的程序可以複製自我,這樣有什麼好處?和工廠模式總是用new來產生物件相比,其目的是?

狀況一:如果new很慢時、很費資源時。例如遠端網站的圖片。如果又有一個request需要這個物件,那麼是否可以用原型模式複製一個就好?這算不算一個例子?

狀況二:如果一個物件的資料必須透過許多搜尋、計算才能得到,那麼要再一個的時候,就別去用工廠模式了,用原型模式複製即可。

總之,就是copy物件。但是,如何讓程式可以適當地應用工廠和原型在設計中,而又符合物件導向的各原則呢?我認為這十分值得深思、更需要深入的體驗。


2016年4月13日 星期三

設計模式五:工廠模式(factory)

工廠模式和簡單工廠模式間最大的不同在於:

  • 簡單工廠模式的物件生產,是透過一個統一的工廠,可以完成許多重覆的初始化動作。另一方面,當物件種類增加時,工廠就要修改。
  • 工廠模式,也可以稱為工廠方法模式。就是把建立物件的方法,改為工廠,而去除對某一「物件種類」實體化的相依程度。
例如:

GetPizzaForCustomer(orderType) {
  CreatePizza(orderType)
  Bake()
  Cut()
  Pack()
}

如果我們將CreatePizza變成抽象的工廠方法,那麼實作這一個工廠方法就可以重覆利用GetPizzaForCustomer這個成熟且已經測試無誤的程序,只要將想要的Pizza種類實作在CreatePizza這個方法即可。如此即完成物件之間相依性的鬆綁。

如果不使用工廠方法,那麼作法可能就是用new XXXPizza或new OOOPizza,如此一來GetPizzaForCustomer對XXXPizza或OOOPizza都產生了相依性。

工廠方法符合開(CreatePizza可擴充修改)閉(GetPizzaForCustomer不必修改)原則。而各個工廠實作之間的干擾也降低。
  • 所有工廠只負責生產物件。而判斷要生產何種物件的機制,就交給應用端去處理。

2016年4月12日 星期二

設計模式四:代理模式(proxy)

代理模式的使用時機:
當真正物件的實體化十分耗時或耗資源時。例如:


  • 遠端代理(Remote):當物件在不同的位址空間,可以為此物件提供局部代理。這算是最容易理解的case,因為proxy處理了遠端存取的細節,而且提供相同的介面。所以同樣的程式不需修改介面,也可以存取真實的物件。


  • 虛擬代理(Virtual):透過代理來存放實體化需要很長時間的真實物件。這是說,如果一開始所有的物件都必須實體化才能執行下去的case。此時用代理,則可讓資源分散,直到真正的需求來臨時,才要實體化很大的物件。什麼樣的case需要這種模式?例如有許多影像的網頁,當真正需要顯示影像時,代理才將影像讀進來,以減少網路資源的消耗。這表示網頁的loader在讀完文字檔、建立代理影像物件後,就可以結束工作。而真正讀取影像的事,就交給代理影像的物件了。如此一來,loader就不會牽涉太多讀取影像的功能,降低彼此的耦合,除錯及維護、發展更容易。


  • 安全代理(Protection):用來控制真實物件存取時的許可權。也就是說,客戶端想要存取真實物件,而兩者之間有一proxy物件。透過控制proxy物件,就可以控制客戶端和真實物件之間的資訊。而重點仍然是,程式不必重寫,只要proxy物件和真實物性的介面相同即可!
  • 智慧參考:調用真實物件時,代理物件會判斷已提供的資源數,用來決定是否允許大量資源的調用或重覆要求。其實作用和安全代理一樣,就是掌控真實物件和其客戶端之間的溝通。

2016年4月11日 星期一

設計模式三:裝飾模式(decorator)

「動態的給物件加入一些額外的職責,這樣比設計子類別更靈活。」

怎麼說?這表示類別並未改變,而是「動態的」讓新的物件和此類別結合。這就好像QML的Objects都可以嵌入新的Object而擁有新功能,而未改變原本QML類別的設計。

怎麼實作?
1. 在原始類別設計好介面(abstract class),
2. 新的裝飾的類別也都要繼承及實作此原始類別。
3. 重點是讓這些介面能夠(用原始類別的「知識」,例如指標)連結到彼此。

c++的範例

另外一點就是避免只用「繼承」來實作所有類別,有些類別用合成會更新效率。
例如:

咖啡
焦糖咖啡
牛奶咖啡
肉桂焦糖咖啡
肉桂牛奶咖啡


如果只用繼承,,那麼我們會有5個class,以及較低的靈活度。
如果用合成,我們只需要4個class,靈活度、class的數目及其實體大小在增加更多個佐料後會更明顯。

物件導向(3)依賴倒置原則(Dependency inversion principle)


  • 抽象介面不應該依賴於具體實作,而具體實作則應該依賴於抽象介面。(是說要針對介面程式設計?這裏的抽象,指的是c++裏的純虛擬函數、較底層的抽象類別、java的interface、swift的protocol等)
  • 高層模組不應依賴低層模組,兩者皆應依賴抽象。(意思是說,別針對某個特定類別去設計,而是將介面定好,大家都照都介面設計,不必管日後彼此溝通的是原始類別或是繼承自原始類別的新類別)
  • Liskov替換原則:子類型必須能夠替換它們的父類型。(只有當子類別可以替換父類別而軟體的單位功能不受到影響,父類別才「能」真正被複用。這是指才「算」是真正被複用?)當然子類別在父類別的基礎上增加新的行為則是一種好的擴充形式。
  • 當程式設計是針對介面設計時,是物件導向的特色。而針對細節設計時是傳統程式設計。為什麼?當針對介面設計,則不同的物件都可以符合功能,而且可以替代及擴展。如果是針對細節設計,那麼維護、擴展、再利用都不易,當然靈活度也就不夠好。

2016年4月7日 星期四

物件導向(2)單一職責原則

一個物件、類別,是否只擁有一個功能才是最好?哪些好處?


  • 不同的功能用不同的類別加以組合,如此一來靈活性自然高。
  • 再利用時不會有多餘的程式碼佔資源。
  • 就測試而言,單一功能是否意味著測試上「比較」容易,品質也「比較」好?
  • 維護容易是明顯的。
缺點呢?
  • 不同功能之間的協調會增加許多成本。但這成本的增加對於系統的穩定度、可維護、可擴充而言,幾乎是可以忽略的事。
  • 《大話設計模式》一書中提到「抑制完成其他職責的可能」,令人費解。完成其他職責,大可用其他物件完成,與當前物件的內部藕合無關才對。
  • 當變化發生時,修改類別內部的功能如果會導致脆弱的結構,那也是內部設計未考量物件導向的原則。但若考量物件導向原則,是不是意味著最好拆開呢?
  • 複合數種類別而成的新類別,再利用的可能性本來就低。除非不在乎多餘的資源。
討論到此應該差不多了!

設計模式二:策略模式(Strategy)

如果說多型是為了讓未來的物件可以使用現有的演算法,那麼策略模式就是讓物件固定而演算法可以更換。

例如檔案物件的排序,可以依據檔名、時間、種類、大小等等。排序演算法雖不同,但皆是針對檔案系統設計的。而使用者可以依需求選擇演算法、增減演算法而達到系統的靈活性。

這種設計對測試、維護、擴展程式都有顯而易見的好處。

《head first design pattern》介紹本模式時,也順便強調了「多用合成、少用繼承」的觀念。以上面的例子來說:一個檔案清單可以擁有(即合成)一個sorting的介面,而實作這個介面的可以是sortingByDate, sortingByFileSize, sortingByName等等。

而策略模式就是依據需求產生實際的物件再合成到檔案清單中。另一種用法就是繼承檔案清單後,根據需求override一個function以產生實作sorting介面的物件。如此一來,就只需要實作不同的sorting行為,就可以有不同能力的檔案清單。

"The Strategy design pattern - Problem, Solution, and Applicability". w3sDesign.com的定義是:the strategy pattern is a behavioral software design pattern that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.

2016年4月6日 星期三

設計模式一:簡單工廠模式(Simple Factory)

簡單工廠(Simple Factory)也稱為靜態工廠,有人認為這屬於programming idiom,即程式設計慣例或風格。做法是設計一個class來負責創建其他多種class的instance,被創建的instance應繼承自相同的class,如此傳回值才會一致、也便於多型演算法的再利用。
這個設計方法有幾個角色:
  1. 工廠(Factory):即某一class,其功能在創建各種class的instance。
  2. 抽象產品(Product):即各種不同的class、產品的輸出藍圖,在java裏就是interface,在c++裏是abstract class。
  3. 具體產品(Concrete product):工廠所創建的實例,以product的藍圖實現出的物件。
這個好處是什麼?重覆的程式碼很少、相關物件的創建及客製化都在這個class完成,易於維護,也方便演算法使用新class而不必更改程式。

物件導向(1)優點概說

物件導向的優點,可以用幾個詞概述:

  • 易維護(maintainable):簡單來說,就是設計改變時,可以很容易的修改以符合新的目的,此處應指小範圍的修改、或者是易於更正錯誤而不影響其他功能,系統不必大規模重測。這才是所謂「維護」。
  • 易擴展(extensible):當然,如果都是物件,那麼「新增」的功能只要用物件就可以組合起來。而舊的功能不變、不必重測。
  • 再利用(reusable):物件如果不能再利用,那就無法稱作物件,這顯而易見。但是,再利用是指再利用「率」高,這樣降低了測試、修改等等的成本。使物件得以再利用的方法,就是降低物件及程式碼之間、物件及物件之間等等的「藕合」程度,此時「封裝」可以派上用場。還有利用「繼承」提高利用率和物件的「去特殊化」,利用多形提高演算法對物件的相依度,演算法可以向「未來」相容、以物件層級來說就是「向下」相容。
  • 高靈活(flexible):和易維護不同。高靈活應指組合成新的功能、新的目的很容易,所需的測試較少。

而設計模式,基本上就是一些程試設計的手法,讓程式可以擁有上述的優點。也就是指出當一些需求出現是,套用這些手法,物性導向的好處就出現了。