顯示具有 Principle 標籤的文章。 顯示所有文章
顯示具有 Principle 標籤的文章。 顯示所有文章

2014年3月31日 星期一

設計模式的解析與活用 (一) 物件導向範型

從例子開始說明吧!!

講師與學生,老師的責任就是讓學生知道下堂課要去哪上課
所以老師應該會
  1. 獲取聽課名單
  2. 針對每個人
  3. 找到每個人要去聽的課
  4. 找到聽課的地點
  5. 找到前往路徑
  6. 告訴他們
但是實際上真的是這樣嗎?

實際上應該是老師貼一張課表給學生自己去看吧
其實這個行為就是責任轉移,讓學生去負責自己的行為

這兩種作法的差別在哪?

第一種方法講師必須關注許多細節,因為所有的事情都由講師負責
第二種方法講師只負責告知,接著學生負責前往所屬課堂

第二種方法的好處在於

假設今天增加了助教研究生,助教需要在下堂課前收集本堂課的學生對課程的評價
對講師來說依然只需要告訴學生下堂課的位置,學生各自會負責該做的責任,研究生會蒐集評價並前往下堂課,普通學生會前往下堂課,翹課的學生會自行翹課….etc

各司其職(負責自己的責任)

第二種方法有以下三個方面不同

  1. 人們對自己的行為負責
  2. 講師將不同類型的人(普通學生&研究生),一視同仁,把他們都視為學生,並且告知他們必須前往下一堂課
  3. 講師不需要知道學生如何前往下一間教室
用術語來說明的話就是

概念-軟體要負責甚麼
規約-怎麼使用軟體
實作-軟體怎麼履行責任

講師要負責甚麼 ClassRoom  getnextClassRoom()  取得每個學生的課堂
學生必須做甚麼 gotoNextClassRoom(ClassRoom room) 
研究生必須收集資料並且前往下堂課
普通學生必須前往下堂課

對講師來說他只需要負責告知的動作
普通學生與研究生會負責自己該負責的責任


封裝的概念

講師不知道哪些是一般學生,哪些是研究生,對講師隱藏了學生的類別(也就式封裝了學生)
雖然學生都是前往下堂課堂,但是行為卻不同

封裝的好處
  • 使用者不需在操心實作的部分,使用者只需要知道想要做甚麼,剩下的交由被呼叫者去處理
  • 可以在不考慮呼叫者的情況下實作(testable)
  • 其他物件對該物件內部是未知的,例如講師呼叫gotonextClassRoom,但是講師並不知道普通學生、研究生實際做了哪些事情

其實這個例子用到了LSP,以及DIP

普通的學生和研究生都是學生,都擁有學生的行為,例如前往下堂課,姓名,學號之類的
LSP-繼承自學生的物件必定擁有學生的行為

講師告訴學生必須前往下堂課,但是他並不知道是告訴研究生還是普通學生
DIP-細節依賴於抽象,講師只呼叫gotoNextClassRoom,學生會自己依照他們的類別,做他們該做的事情,物件只在概念上耦合,在實踐並不耦合

最後,要稍為推薦一下設計模式的解析與活用這本書,雖然目前也只看了一個章節,不過真覺得獲益良多阿!







2013年12月24日 星期二

OOAD原則 (九) 封裝

何謂封裝?

對資料所做的任何計算都受到保護,因為資料不會被存取。
也就是隱藏程式執行內部事物的部分。

舉個例子

Ted.gotoTaipeiStation()
Bob.gotoTaipeiStation()

有時候我們只在乎 Ted and Bob 都到了台北,我們不會去care
Ted 住在台北  所以他搭捷運到北車
Bob住在台南,所以他必須搭高鐵到北車

封裝隱藏了實際實作的部分。



封裝在某些情況也避免了使用者錯誤的操作

例如

networkParser.getUserData()
dbParser.getUserData()

networkParser的動作可能包含了
1.連到網路 2.取得正確網址 3.取得資料 4. 擷取UserData

dbParser得動作可能包含了
1.連線到db 2.query db table資料並取得資料

但是使用者不需要知道詳細的情況,使用者只需要知道現在要拿userdata就好了
封裝讓使用者輕易的取得UserData,而不需要實際去操作,這避免了使用者錯誤的使用


該如何使用封裝?
可以看看以下的範例


interface GotoTaipeiBehavior
{
 void gotoTaipeiStation();
}
class Ted implements GotoTaipeiBehavior
{

 @Override
 public void gotoTaipeiStation() {
  // TODO Auto-generated method stub
  System.out.println("Tkae SubWay");
 }
 
}

class Bob implements GotoTaipeiBehavior
{

 @Override
 public void gotoTaipeiStation() {
  // TODO Auto-generated method stub
  System.out.println("Tkae HightSpeedRail");
 }
 
}
public class person {

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  GotoTaipeiBehavior behavior = new Ted();
  behavior.gotoTaipeiStation();
  behavior = new Bob();
  behavior.gotoTaipeiStation();
 }

}

這個範例就說明了 Ted 和 Bob去台北車站的例子,
main裡面就是client使用時呼叫 ted 和 bob 要去台北車站,
可以注意到這邊我們只在乎他們的行為(GotoTaipeiBehavior )不在乎他們是誰,
在我們將Ted 或Bob 創建出來之後,我們將他們一視同仁(都是behavior),然後要去台北車站,
這樣寫的好處是,不管是誰要去台北車站都可以使用gotoTaipeiStation這段程式,
程式的可用性提高了,而且使用者不需要知道裡面實際做了甚麼事情,只需要知道人到了台北,這同時也提升了程式的可讀性。


結論

封裝會隱藏物件的實做,以及保護資料避免被user操作,同時他也可以將某些user不需要知道資料隱藏起來(例如某些控制開關的flag)。
封裝可以降低程式的耦合度,同時若加上良好的命名封裝同時也可以提升程式的可讀性,進一步提升了程式的維護性。

2013年12月17日 星期二

OOAD原則 (八) Interface Seggregation Principle

ISP (接口隔離原則)主要強調的是,interface必須是內聚的,也就是說不應該出現 "胖" interface,
如果出現了 "胖" interface 代表他應該是可以分解成多組。

底下是一個 "胖" interface 的範例




首先一開始客戶要求我們做一台車,很簡單的我們直接使用了 interface Vehicle ,
之後不管是摩托車,跑車,房車,中古車  都可以使用他

interface Vehicle
{
 void Run();
}
class Porsche911 implements Vehicle
{

 @Override
 public void Run() {
  // TODO Auto-generated method stub
  System.out.println("Porsche911 Running");
 }
 
}
public class Car implements Vehicle{

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  Vehicle carVehicle = new Car();
  carVehicle.Run();
  carVehicle = new Porsche911();
  carVehicle.Run();
 }

 @Override
 public void Run() {
  // TODO Auto-generated method stub
  System.out.println("Car Running");
 }

}


但是好景不常,某一天客戶提出了新的需求,
他們公司生產了飛天車,這也代表著我們的程式也必須提供飛天車的資料
下面的作法是比較不好的作法

interface Vehicle
{
 void Run();
 void Fly();
}
class Porsche911 implements Vehicle
{

 @Override
 public void Run() {
  // TODO Auto-generated method stub
  System.out.println("Porsche911 Running");
 }

 @Override
 public void Fly() {
  // TODO Auto-generated method stub
  System.out.println("Porsche911 not Flying");
 }
 
}
class FlyCar implements Vehicle
{

 @Override
 public void Run() {
  // TODO Auto-generated method stub
  System.out.println("FlyCar Running");
 }

 @Override
 public void Fly() {
  // TODO Auto-generated method stub
  System.out.println("FlyCar flying");
 }
 
}
public class Car implements Vehicle{

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  Vehicle carVehicle = new Car();
  carVehicle.Run();
  carVehicle = new Porsche911();
  carVehicle.Run();
 }

 @Override
 public void Run() {
  // TODO Auto-generated method stub
  System.out.println("Car Running");
 }

 @Override
 public void Fly() {
  // TODO Auto-generated method stub
  System.out.println("Car not Flying");
 }

}

可以很清楚的發現,在interface裡新增了 fly這個功能,但是這其實是災難的開始,
這使得原本跑的很正常的 car and Porsh911 這兩個物件也新增了fly的功能,
但是他們並不會飛阿,而且他們也不想要會飛,可是他們現在逼不得以也必須implement Fly這個功能,這樣寫法非常不好的原因在於,

  1. implement的功能並不是我們所需要的
  2. 現在只有兩個class需要做修正(Car and Porsh911),當我們有一千個class要修改的時候我相信修改的人一定會......!@#$%
由以上的例子,告訴了我們這是一個不好的設計,然後ISP也提到這就是一個 "胖" interface ,
所以根據上面的例子我們可以做以下的修改

interface Vehicle
{
 void Run();
}
interface FlyVehicle extends Vehicle
{
 void Fly();
}
class Porsche911 implements Vehicle
{

 @Override
 public void Run() {
  // TODO Auto-generated method stub
  System.out.println("Porsche911 Running");
 }

}
class FlyCar implements FlyVehicle
{

 @Override
 public void Run() {
  // TODO Auto-generated method stub
  System.out.println("FlyCar Running");
 }

 @Override
 public void Fly() {
  // TODO Auto-generated method stub
  System.out.println("FlyCar flyning");
 }
 
}
public class Car implements Vehicle{

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  Vehicle carVehicle = new Car();
  carVehicle.Run();
  carVehicle = new Porsche911();
  carVehicle.Run();
  carVehicle = new FlyCar();
  ((FlyVehicle)carVehicle).Fly();
 }

 @Override
 public void Run() {
  // TODO Auto-generated method stub
  System.out.println("Car Running");
 }

}


我們使用interface FlyVehicle繼承 Vehicle 並新增 fly的功能,
這邊其實跟SingleResponsibilityPrinciple的感覺很像,每個interface都有單一的職責(注意不是單一method)這使得我們只需要修改FlyVehicle就可以了,而且新增功能對於其他的class並不會造成額外的影響。

當然這種修改方式僅限於開發階段,

  • 在小怪物還沒有長成千年老妖之前處理是很容易的...


如果是處在維護階段的話,上面的作法可能會牽一髮而動全身,如果能動就動吧,如果不能動就....,試試下面的方法吧


interface Vehicle
{
 void Run();
 void Fly();
}
abstract class VehicleAdapter implements Vehicle
{
 abstract public void Run();
 public void Fly()
 {
  System.out.println("Do Nothing");
 }
 
}
class Porsche911 extends VehicleAdapter
{

 public void Run() {
  // TODO Auto-generated method stub
  System.out.println("Porsche911 Running");
 }
}
class FlyCar extends VehicleAdapter
{

 @Override
 public void Run() {
  // TODO Auto-generated method stub
  System.out.println("FlyCar Running");
 }

 @Override
 public void Fly() {
  // TODO Auto-generated method stub
  System.out.println("FlyCar flyning");
 }
 
}
public class Car extends VehicleAdapter{

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  Vehicle carVehicle = new Car();
  carVehicle.Run();
  carVehicle = new Porsche911();
  carVehicle.Run();
  carVehicle = new FlyCar();
  carVehicle.Fly();
 }

 @Override
 public void Run() {
  // TODO Auto-generated method stub
  System.out.println("Car Running");
 }


}

上面使用了一個adapter轉接器將Fly這個功能在adapter中做掉,使得繼承vehicleAdapter的物件不再需要實做fly這個功能,然後Flycar可以override掉原本的功能,改成fly的功能。用這樣的方式就可以使之後想使用vehicle interface但不想fly的人,可以使用新的選擇而不再需要去實做Fly這個功能。

ISP原則感覺就是將SRP原則套在interface身上,希望每個interface都是單一原則(不是單一method),而不是一個interface擁有多個責任,太胖的interface會造成許多問題,
善用這個方法,可以降低耦合,使程式碼更加彈性。

2013年12月15日 星期日

OOAD原則 (七) Dependency Injection (DIP依賴倒置原則 - 續2)

相依性注入 (DI) 是實踐DIP的方法
  • Constructor Injection
  • Property Injection
  • Interface Injection
以下要直接介紹這三個方法



Constructor Injection

class Behavior
{
 public void Run()
 {
  System.out.println("Run");
 }
}
public class ConstructorInjection {

 /**
  * @param args
  */
 
 private Behavior behavior;
 public ConstructorInjection(Behavior _be)
 {
  behavior = _be;
 }
 public void Execute()
 {
  behavior.Run();
 }
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  ConstructorInjection injection = new ConstructorInjection(new Behavior());
  injection.Execute();
 }
}

上面的方法是將物件在class 初始化的時候將所需要的Behavior直接注入,來避免高偶合(繼承)的問



Property Injection

class TomRun
{
 public void Run()
 {
  System.out.println("Run");
 }
}
public class PropertyInjection {

 /**
  * @param args
  */
 private TomRun behavior;
 public TomRun GetTomRun()
 {
  return behavior;
 }
 public void setTomRun(TomRun _be)
 {
  behavior = _be;
 }
 public void Execute()
 {
  behavior.Run();
 }
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  PropertyInjection injection = new PropertyInjection();
  injection.setTomRun(new TomRun());
  injection.Execute();
 }

}

上面的方法是利用getter and setter 讓我們將所需要的 TomRun注入  來避免高耦合(繼承)的情形


Interface Injection

interface RunBehavior
{
 void Run();
}
public class interfaceInjection implements RunBehavior {

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  RunBehavior behavior = new interfaceInjection();
  behavior.Run();
 }

 @Override
 public void Run() {
  // TODO Auto-generated method stub
  System.out.println("Run");
 }

}

上面的方法是利用 interface 讓class implements 該interface 並實作之
利用interface 注入來避免高耦合(繼承)的問題


利用這些方法,我們可以避免使用繼承,使用相依注入,降低耦合,
當然,第一個例子和第二個例子更好的寫法是使用interface 然後注入使用的參數是interface而不是class  這樣又可以呼應到DIP的要點



依賴抽象類別,不要依賴具象類別。



利用抽象來當作class之間溝通的方式,這樣寫出來的程式耦合度會更低,會更好maintain

2013年12月11日 星期三

OOAD原則 (六) Ioc Inversion of Control (DIP依賴倒置原則 - 續)

DIP 是個理論,而如何達到這個目標就必須使用IoC這個技巧,

IoC容器

  • 會實踐Dependency injection
  • 提供設定dependency的方法
講白話點就是
  • 創造dependency /object 
  • 注射或傳遞dependency



接著我們來看一下範例

根據  DIP 依賴倒置原則 這篇文章,我們可以將該範例的UML畫出來如下圖



class EatRiceBehavior 
{
 void eat()
 {
  System.out.println("Person_EATRice");
 }
}
public class Person
{
 EatRiceBehavior behavior=new EatRiceBehavior();
 public void eat() {
  // TODO Auto-generated method stub
   behavior.eat();
 }
 public static void main(String[] args) 
 {
  Person person = new Person();
  person.eat();
 }
 
}

人吃飯,人擁有吃飯的物件來實做吃飯該做的事情,但是依賴於具象是不好的,會造成程式的高耦合性,應該依賴於抽象,所以我們更改架構變成下圖


interface Behavior
{
 void eat();
}
class EatRiceBehavior implements Behavior
{

 @Override
 public void eat() {
  // TODO Auto-generated method stub
  System.out.println("eat rice");
 }

}
class EatNoodleBehavior implements Behavior
{

 @Override
 public void eat() {
  // TODO Auto-generated method stub
  System.out.println("eat  EatNoodle");
 }
}
class EatDumplingBehavior implements Behavior
{

 @Override
 public void eat() {
  // TODO Auto-generated method stub
  System.out.println("eat  Dumpling");
 }
}

public class Person
{
 Behavior behavior;
 public void eat(Behavior behavior) {
  behavior.eat();
 }
 public static void main(String[] args) 
 {
  Person person = new Person();
  person.eat(new EatRiceBehavior ());

 }
 
}


我就沒有畫出noodle 還有 dumpling的class了 ,但是他的意義其實是,



用程式的意義來說明

原本Person 依賴於 EatRiceBehavior ,當人想要eat的時候就會將指令傳達給EatRiceBehavior
當要吃麵的時候就會將指令傳達給EatNoodleBehavior 這樣的缺點是當想吃其他的東西時參數並無法共用,會產生出許多參數造成管理不便,
但是經過我們修改之後我們將控制權交給了interface Behavior
使得 EatNoodleBehavior、 EatDumplingBehavior、 EatRiceBehavior 皆依賴於Behavior
,只要有人擁有Behavior就擁有他們的控制權,然而Person恰巧擁有Behavior,這使得Person能夠輕易的控制那三個Behavior。



用注射傳遞的方式來說明

我們將Behavior 注射/傳遞至Person內  Person就是Ioc容器他會負責幫我們在class之間做溝通
讓我們可以在上層與下層之間做溝通而不依賴於彼此

OOAD原則 (五) DIP 依賴倒置原則

Dependency Inversion Principle 依賴倒置原則主要有個要點
  • 依賴抽象類別,不要依賴具象類別。



簡單的說就是class 之間用interface/abstract 來溝通
下面是個簡單的人吃飯的例子。

class EatRiceBehavior 
{
 void eat()
 {
  System.out.println("Person_EATRice");
 }
}
public class Person
{
 EatRiceBehavior behavior=new EatRiceBehavior();
 public void eat() {
  // TODO Auto-generated method stub
   behavior.eat();
 }
 public static void main(String[] args) 
 {
  Person person = new Person();
  person.eat();
 }
 
}


但是當我想新增人吃麵的行為,原本的程式就不能使用了,因為他依賴於具象類別(EatRiceBehavior ),所以我就必須在class 裡面增加 EatNoodle Nbehavior; 類似這樣的參數,接著當我新增了越來越多的物件時這段程式就會變成類似下面這樣


public class Person
{
 EatBehavior behavior;
        EatNoodle  Nbehavior;
              Eatdumpling Dbehavior;  
public void eat(string val) { // TODO Auto-generated method stub if(val.equals("rice")) behavior.eat();
                else if(val.equals("noodle")) Nbehavior.eat();
                else Dbehavior.eat();
 }
 public static void main(String[] args) 
 {
  Person person = new Person();
  person.eat("rice");
 }
 
}


這樣的程式是非常可怕的,難以維護且重複性極高,
這時候還好有Dependency Inversion Principle,他告訴我們依賴於抽象而不依賴於具體類別
所以我們就可以寫出下面的例子


interface Behavior
{
 void eat();
}
class EatRiceBehavior implements Behavior
{

 @Override
 public void eat() {
  // TODO Auto-generated method stub
  System.out.println("eat rice");
 }

}
class EatNoodleBehavior implements Behavior
{

 @Override
 public void eat() {
  // TODO Auto-generated method stub
  System.out.println("eat  EatNoodle");
 }
}
class EatDumplingBehavior implements Behavior
{

 @Override
 public void eat() {
  // TODO Auto-generated method stub
  System.out.println("eat  Dumpling");
 }
}

public class Person
{
 Behavior behavior;
 public void eat(Behavior behavior) {
  behavior.eat();
 }
 public static void main(String[] args) 
 {
  Person person = new Person();
  person.eat(new EatRiceBehavior());
 }
 
}


現在不管新增了甚麼行為進來,都不至於造成太大的影響。

2013年12月3日 星期二

OOAD原則 (四) 委派 vs 合成 vs 聚合

委派

將特定工作的責任委派給一個類別或方法
譬如下面的例子:
老闆說要蓋大樓,接著當然不會是老闆蓋大樓,
老闆會將這個工作委託給工人們,由工人們就去蓋大樓。



class Worker
{
 public void Work()
 {
  System.out.println("Do Work");
 }
}

class Boss
{
 private Worker labor = new Wroker();
 public void Build()
 {
  labor.Work();
 }
}
public class delegate {

 /**
  * @param args
  */

 public static void main(String[] args) {
  // TODO Auto-generated method stub
  Boss boss = new Boss(); 
  boss.Build();  
 }

}


合成

擁有一組其他類別的行為,
還有一個重點是當合成的物件消失他所擁有的其他類別的行為也會消失。
下面的例子:
ArmsDealer擁有Weapon,
當ArmsDealer 被殺掉的話使用者就無法取得購買Weapon的通路。


interface Weapon
{
 public void Attack();
}
class Sword implements Weapon
{ 
 public void Attack()
 {
  System.out.println("Sword Attack");
 }
}
class Bow implements Weapon
{
 public void Attack()
 {
  System.out.println("Bow Attack");
 }
}
class ArmsDealer
{
 ArrayList<Weapon> list = new ArrayList<Weapon> ();
 public ArmsDealer()
 {
  list.add(new Bow());
  list.add(new Sword());
 }
 public Weapon GetWeapon(int index)
 {
  return list.get(index);
 }
}
public class Game {

 
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  ArmsDealer dealer= new ChooseWeapon();
  dealer.GetWeapon(0).Attack();
 }

}

聚合

跟合成最明顯的差異就是,當聚合物件被摧毀時,他所擁有的物件並不會跟著一起銷毀。
下面的例子:
Ted是個司機當車子報廢的時候Ted還是可以去開motoBike並不會因為車子報廢Ted就跟著一起消失...如果這樣也太可怕了XD

abstract class Driver
{
 abstract public void Drive();
}
class Ted extends Driver
{
 public void Drive()
 {
  System.out.println(" Driving");
 }
}

class Car
{
 private Driver driver;
 public Car(Driver ted)
 {
  this.driver= ted;
 }
 public void Move()
 {
  driver.Drive();
 }
}

class MotoBike
{
 private Driver driver;
 public MotoBike(Driver ted)
 {
  this.driver= ted;
 }
 public void Move()
 {
  driver.Drive();
 }
}
public class Aggregation {

 /**
  * @param args
  */
 public static void main(String[] args) {
  Ted ted = new Ted();
  Car car = new Car(ted);
  car.Move();

 }

}


結論

委派是將某個行為委託給別人去做
ex:老闆叫員工去寫code

合成是擁有其他類別的資訊且當該類別被destroy的時候他所擁有的資訊也會一併消失
ex:冰淇淋被吃掉,上面的配料也都一併進到肚子裡去(消失)。

聚合也是擁有其他類別的資訊但是當該類別被destroy的時候他所擁有的資訊並不會一併消失
ex:司機開車,假設車子報廢,司機還是可以開新車。

2013年10月21日 星期一

OOAD原則 (三) dependency injection 相依性注入

相依性注入(DI)是一個能夠幫助我們解耦合的方法
舉例來說 Strategy Pattern 就有用到這個idea
    abstract class Sorting
    {
        abstract public void Process();
    }
    class SortAlgorithm
    {
        private Sorting algorithm;
        public SortAlgorithm(Sorting algorithm)
        {
            this.algorithm = algorithm;
        }
        public void Sort()
        {
            algorithm.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 quicksort = new QuickSort();
            SortAlgorithm sortalgorithm = new SortAlgorithm(new QuickSort());
            sortalgorithm.Sort();
            sortalgorithm = new SortAlgorithm(new MergeSort());
            sortalgorithm.Sort();
        }
    }


從例子來說   Sorting 真正的實體是由  QuickSort 或 MergeSort來決定的
這部分就是 DI的精神  這樣子寫就可以讓 SortAlgorithm與 QuickSort 以及 MergeSort 解耦合
如果之後有需要做SortAlgorithm的測試時  可以 丟入一個  寫死的演算法 TestSort  回傳固定的排序  這樣就可以測試 SortAlgorithm是否有問題

OOAD原則 (二) Single Responsibility Principle 單一責任原則

SingleResponsibilityPrinciple主張 一個類別只有一個被修改的理由  一個類別只有一個職責
當一個類別可以做很多事情時  我們會稱他為God class  
通常這種類別的重覆使用性很低  因為他會跟很多其他的類別耦合  很難重覆使用

    class GodClass
    {
        private bool IsTurnOnTV = false;
        private bool IsTurnOnRadio = false;
        private bool IsTurnOnLight = false;
        public void TurnOnTV()
        {
            IsTurnOnTV = true;
            Console.WriteLine("TurnOnTV");
        }
        public void TurnOffTV()
        {
            IsTurnOnTV = false;
            Console.WriteLine("TurnOffTV");
        }
        public void TurnOnRadio()
        {
            IsTurnOnRadio = true;
            Console.WriteLine("TurnOnRadio");
        }
        public void TurnOffRadio()
        {
            IsTurnOnRadio = false;
            Console.WriteLine("TurnOffRadio");
        }

        public void TurnOnLight()
        {
            IsTurnOnLight = true; ;
            Console.WriteLine("TurnOnLight");
        }
        public void TurnOffLight()
        {
            IsTurnOnLight = false;
            Console.WriteLine("TurnOffLight");
        }

        public void Execute(string command)
        {
            switch (command)
            {
                case "TV":
                    if (IsTurnOnTV)
                        TurnOffTV();
                    else
                        TurnOnTV();
                    break;
                case "Light":
                    if(IsTurnOnLight)
                        TurnOffLight();
                    else
                        TurnOnLight();
                    break;
                case "Radio":
                    if (IsTurnOnRadio)
                        TurnOffRadio();
                    else
                        TurnOnRadio();
                    break;
                default:
                    throw new Exception();
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            GodClass godclass = new GodClass();
            godclass.Execute("Light");
            godclass.Execute("Light");
            Console.ReadKey();
        }
    }

上面是一個 god 類別的例子  他可以做 TV的開關  Light的開關 以及  Radio的開關
簡單的說就是甚麼都可以做  這樣寫好像不錯  所有控制的項目都整理在一個類別裡
但是  當有新的功能要要加入時  就得對execute的程式做修改  這樣就違反了OpenClosePrinciple
然後當新的需求不斷進來  最後  這個類別就會變成一個龐大而且充滿flag的類別
這樣的程式是非常難以維護的
但是  我們可以用下面的方式來做修改


  abstract class Command
    {
        public abstract void Execute();
    }
    class LightCommand : Command
    {
        private bool IsTurnOnLight = false;
        public void TurnOnLight()
        {
            IsTurnOnLight = true; ;
            Console.WriteLine("TurnOnLight");
        }
        public void TurnOffLight()
        {
            IsTurnOnLight = false;
            Console.WriteLine("TurnOffLight");
        }
        public override void Execute()
        {
            if (IsTurnOnLight)
                TurnOffLight();
            else
                TurnOnLight();
        }
    }

    class TVCommand : Command
    {
        private bool IsTurnOnTV = false;
        public void TurnOnTV()
        {
            IsTurnOnTV = true;
            Console.WriteLine("TurnOnTV");
        }
        public void TurnOffTV()
        {
            IsTurnOnTV = false;
            Console.WriteLine("TurnOffTV");
        }
        public override void Execute()
        {
            if (IsTurnOnTV)
                TurnOffTV();
            else
                TurnOnTV();
        }
    }

    class RadioCommand : Command
    {
        private bool IsTurnOnRadio = false;
        public void TurnOnRadio()
        {
            IsTurnOnRadio = true;
            Console.WriteLine("TurnOnRadio");
        }
        public void TurnOffRadio()
        {
            IsTurnOnRadio = false;
            Console.WriteLine("TurnOffRadio");
        }
        public override void Execute()
        {
            if (IsTurnOnRadio)
                TurnOffRadio();
            else
                TurnOnRadio();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Command LightCommand = new LightCommand();
            LightCommand.Execute();
            LightCommand.Execute();
            Console.ReadKey();
        }
    }
基本上這兩段程式碼做的事情是一樣的
但是上面這一段程式碼
當新的需求進來時   例如我想新增電腦開關
不論我在新的類別裡面修改或新增了多少東西
並不會影響到其他已經寫好的類別
對維護者來說  這樣的類別也是相對好維護的  flag少而且執行的功能明確
這就是把責任切開的好處

2013年9月23日 星期一

OOAD原則 (一) Open/Close Principle 開閉原則

簡稱OCP 意思是

一個軟體應該要夠開放使得他可以被擴充,但是也要夠封閉以避免不必要的修改 

這句話其實很像是繞口令而且看起來很矛盾


讓我們舉個例子
當我們有 功能/Bug 需要去修改程式時 
最好的方法是甚麼?  在程式裡面加上一個flag? 
這個是我覺得最可怕的解法...

每當遇到一個 新功能/Bug 就用flag去判斷
在一開始可以得到不錯的效果
但是當過了一個禮拜....兩個禮拜....兩個月之後
當某天必須回來看這份code  你會發現Long time ago......
(一個好的故事都是這麼開始的....可惜這是一個可怕的code....)
那麼多個flag到底是要幹嘛的
而且一大堆flag 你能很確定的說  這個flag的運作方式就跟當初想的時候一樣嗎?

我記憶力一向不太好  就算有註解   過了那麼久我也還是得把code重新看一次(崩潰...)

所以為了能修改程式又不要動到其他看似沒有問題的程式最好的辦法就是用class去分開他
在不同的情況下使用不同的class去處理事情
Strategy Pattern 就是用到了這個方法 
好處是user只需要知道做了sort這個動作而不需要知道細節
有新功能被增加時並不會影響到main function  (因為加在class裡)