Sunday, December 30, 2012

Design Patterns-Object Oriented Design

Oject Orient Design:提到SOLID(WIKI)

包含了6個原則,原則通常會隨著專案可能會違背這些原則,但這些原則可以幫助你在設計時所要考慮因素,為何要違背某一原則?是為了其他類別?在這專案下違背了影響層面很小?

Single Responsibility Principle:

There should never be more than one reason for a class to change.

單一職責原則,僅有一個原因引起類別的變更。

Tem
Example:

   1: public interface IPhone
   2: {
   3:     public void dail(String phoneNumber);
   4:     public void chat(Object o);
   5:     public void hangup()
   6: }

這看起來很正常,一支手機會需要接撥號、接聽、聊天,但如果以SRP的概念這介面負責了接聽(hangup、chat)和資料重送(chat)兩種功能,因此在SRP概念裡要分離


可以用Extract Class、Extract Interface方法來提煉。


Tem


職責非常分明、架構清晰但一般在設計時很難馬上使用這個方式來設計,設計時會對未來來做設想,但應盡量表持簡單(Keep it sample)。


 


因此通常會在設計成:


Tem


職責也確實分開,也比上圖還要簡潔。


SRP提供的好處:


類別的複雜性降低。實現甚麼職責都也清晰明確的定義。


可讀性上升。複雜性降低就會比較好閱讀。


可維護性提升。


變更引起的風險降低。不得已要變更介面,但如果使用SRP修改的介面也不會影響到其他介面。


PS.職責因各自專案而異。


 


很難確實把SRP執行到每個專案的部分,但SRP可以落實到 介面、類別 、方法。



   1: private void RotationAndMove(){}

可以把方法提煉成兩個



   1: private void Rotation(){}
   2: private void Move(){}

在Code Refactoring也提到在Extract Method、Extract Class、Extract Interface,提煉出來的東西必須是單一且明確的。


Open-Close Principle:


Software entities like classes,modules and functions should be open for extension but closed for modifications.


軟體實體應該透過擴展來實作變化,而不是透過修改已有的代碼來實作變化。


Tem



   1: public interface IBook
   2: {
   3:     public String getName();
   4:     public int getPrice();
   5:     public String getAuthor();
   6: }


   1: public class NovelBook : IBook
   2: {
   3:     private String name;
   4:     private int price;
   5:     private String author;
   6:     public NovelBook(String _name,int _price,String _author)
   7:     {
   8:         this.name = _name;
   9:         this.price = _price;
  10:         this.author = _author;
  11:     }
  12:     
  13:     public String GetAuthor()
  14:     {
  15:         return this.author;
  16:     }
  17:     public int GetPrice()
  18:     {
  19:         return this.price;
  20:     }
  21:     public string GetName()
  22:     {
  23:         return this.name;
  24:     }
  25: }


   1: public class BookStore
   2: {
   3:     private static ArrayList<IBook> bookList = new ArrayList<IBook>();
   4:     static{
   5:         bookList.add(new NovelBook("A",320,"A book"));
   6:         bookList.add(new NovelBook("B",520,"B book"));
   7:         bookList.add(new NovelBook("C",420,"C book"));
   8:     }
   9:     public static void main(String[] args)
  10:     {
  11:         for(IBook book:bookList)
  12:             //print book
  13:             //print detail
  14:     }
  15:     
  16: }

假設書店開始打折拍賣,那程式碼的變更可能有三種:


1.修改介面:


      在IBook上增加getOffPrice(),處理打折,一旦修改了IBook介面,實作他的NovelBook要修改,BookSstore也要改,假設類別很多種不只Novel那修改起來就不可靠,也失去介面的意義。所以這不行。


2.修改實作類別:


      直接在NovelBook類別中,在getPrice()實作打折處理,處理上很簡單很值觀,但或許有更好的做法。


3.擴展實作變化:


      增加一個子類別 OffNovelBook,去 Override getPrice方法,BookStore透過實作OffNovelBook類別產生新的物件,修改少、且沒有動到舊有的物件(風險小)。


Tem



   1: public class OffNovelBook : NovelBook
   2: {
   3:     public OffNoveBook(string _name,int _price,string _author)
   4:         :base(_name,_price,_author){}
   5:     public override int getPrice()
   6:     {
   7:         int selfPrice = base.getPrice();
   8:         int offPrice = 0;
   9:         selfPrice > 400 ? offPrice = selfPrice * 90/100:offPrice = selfPrice * 80 / 100;
  10:         return offPrice;
  11:     }
  12: }


   1: public class BookStore
   2: {
   3:     private static ArrayList<IBook> bookList = new ArrayList<IBook>();
   4:     static
   5:     {
   6:         bookList.add(new OffNovelBook("A",320,"A Book"));
   7:         bookList.add(new OffNovelBook("B",420,"B Book"));
   8:         bookList.add(new OffNovelBook("C",320,"C Book"));
   9:     }
  10:     public static main(string[] args)
  11:     {
  12:         //print detail
  13:     }
  14: }

會選擇第三個方法主要是不去修改原本NovelBook的類別,風險將會降低很多,因為是用擴張的方式,即使擴張後有問題,可以先暫時在把擴張的程式碼移除,還可以去保專案是可運行的。


在BookStore還是有修改的地方,但修改的幅度很小。


Open Close 優點:


1.可以提升重複利用性:


       所有的邏輯都是從原子邏輯組合而來,而不是在一個類別中獨立時做一個業務邏輯。只有這樣代碼才可以被重複利用,粒度越小可能性越大。


2.提升可維護性:


       擴展一個類別和修改一個類別,難度上一下是擴展較容易。


Liskov substitution Principle:


Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.


父類別能出現的地方,子類別就可以出現,而且替換為子類別也不會產生任何錯誤和異常。使用者根本不需要知道出現的是父類別還是子類別。


Tem



   1: public abstract class AbstractGun{public abstract void shoot()}


   1: public class HandGun : AbstractGun
   2: {
   3:     public override void shoot(){//shoot...} 
   4: }


   1: public class RifleGun : AbstractGun
   2: {
   3:     public override void shoot(){//Rifle shoot...} 
   4: }


   1: public class MachineGun : AbstractGun
   2: {
   3:     public override void shoot(){// Machine shoot...} 
   4: }


   1: public class Soldier
   2: {
   3:     private AbstractGun gun;
   4:     public void setGun(AbstractGun _gun)
   5:     {
   6:         this.gun = _gun;
   7:     }
   8:     public void killenemy()
   9:     {
  10:         //Solider Move
  11:         gun.shoot();
  12:     }
  13: }


   1: public class Client
   2: {
   3:     public static void main(String[] args)
   4:     {
   5:         Soldier SA =new Soldier();
   6:         SA.setGun(new Rifle());
   7:         SA.killEnemy();
   8:     }
   9: }

1.子類別必須完全實作父類別的方法:


在類別中呼叫其他類別時務必要使用父類別或介面,如果不能使用父類別或介面,則類別的設計已經違反LSP。


 


2.子類別可以有自己的個性:


子類別可以父類別的身分出現,不代表子類別出現的地方一定可以用父類別的身分出現,當然子類別可以有自己的屬性和方法。


 


3.覆寫或實作父類別的方法時輸入參數可以被放大:


子類別中方法的前置條件必須與父類別中被覆寫的方法的前置條件相同或更寬鬆。


 


4.複寫或實作父類別的方法時輸出結果可以被縮小:


符合LSP所以可以被縮小。


 


Law of Demeter:


Each unit should have only limited knowledge about other units: only units “closely”related to the current unit.


Each unit should only talk to its friends;dont talk to strangers.


only talk to your immediate.


一個物件應該對其他物件有最少的了解。知道得越少越好。


1.只和朋友交流:only talk to your immediate.


Tem


在Code Refactoring 提到 Hide Delegate:在Server端放置一個簡單的委託函式(delegating method),將委託關係隱藏起來,從而去除相依性。


這麼一來即便委託關係發生變化,變化將被限制在Server中,不波及客戶。


在這例子上Teacher計算Classmate數量,Teacher只須委託GroupLeader去數全班人數是否到齊,不需知道每個Classmate屬性,即Teacher和Classmate並不存在任何關係,當Classmate進行變化(功能、數量),修改的代碼只需處理GroupLeader和Classmate。


 


2.類別之間也是有距離的:


類別與類別間的方法不需要全都知道,假設A Class和 B Class。



   1: public class A
   2: {
   3:     B b = new B();
   4:     b.FirstStep();
   5:     b.SecondStep();
   6:     b.ThirdStep();
   7: }


   1: public class B
   2: {
   3:     public void FirstStep()
   4:     { // No.1}
   5:     public void SecondStep()
   6:     { // No.2}
   7:     public void ThirdStep()
   8:     { // No.3}
   9: }

A為了使用B,而知道了B裡面的3個Method,當改面B內的Method名稱或是Method呼叫順序(First-Third-Second),這些變動必須對A class和B Class都修改,風險較高,因此:



   1: public class A
   2: {
   3:     B b = new B();
   4:     b.PackageStep();
   5: }


   1: public class B
   2: {
   3:     public void PackageStep()
   4:     {
   5:         FirstStep();
   6:         SecondStep();
   7:         ThirdStep();
   8:     }
   9:     private void FirstStep(){//First}
  10:     private void SecondStep(){//Second}
  11:     private void ThirdStep(){//Third}
  12: }

當順序改變時,只須改B class而不用動到A class,風險相對低很多。這在Code Refactoring也有提到。


 


Interface Segregation Principle


Client should not be forced to depend upon interfaces that they dont use.


The dependency of one class to another one should depend on the smallest possibile interface.


客戶端不該依賴它不需要的介面。


類別間的依賴關係應該建立在最小的介面上。


Dependency Inversion Principle


High Level modules should not depend upon low level modules.Both should depend upon abstractions.


Abstractions should not depend upon details.


Details should depend upon abstractions.

No comments:

Post a Comment