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

Android 開發 (七) DB 的使用介紹

在Android中要使用DataBase的功能,可以實作 SQLiteOpenHelper 或 SQLiteDatabase 接著只需在裡面使用相關的sql command即可 在這邊就稍微介紹一下該如何下指令



interface DAO_DB
{
 public void createTable();
 public void insertData(Object obj);
 public void deleteData(Object obj);
 public void updateData(Object obj);
 public void FindAllData();
 public void FindLastID();
 public void close();
}
public class DBHelper extends SQLiteOpenHelper implements DAO_DB{
 private SQLiteDatabase dbDatabase;
 private String TABLE_NAME = "MyTab";
 
 public DBHelper(Context context) {
  super(context, "TED_DE", null, 1);
  // TODO Auto-generated constructor stub
 }


 @Override
 public void onCreate(SQLiteDatabase db) {
  // TODO Auto-generated method stub
  dbDatabase = db;
  createTable();
 }

 @Override
 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void createTable() {
  // TODO Auto-generated method stub
  String cmd = "CREATE TABLE " + TABLE_NAME + " ("
    + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
    DBColumns.NAME + " TEXT, " +
    DBColumns.TEL + " TEXT, " +
    DBColumns.EMAIL + " TEXT);"; 
  dbDatabase.execSQL(cmd);
 }

 @Override
 public void insertData(Object obj) {
  HashMap<String, String> map = (HashMap<String, String>)obj;
     String sql = "INSERT INTO "+ TABLE_NAME + 
       " values("
       +"\"" + map.get(DBColumns._ID) +"\","
       +"\"" + map.get(DBColumns.NAME) +"\","
       +"\"" + map.get(DBColumns.TEL) +"\","
       +"\"" + map.get(DBColumns.EMAIL) +"\""
       + ");";

     getWritableDatabase().execSQL(sql);
 }

 @Override
 public void deleteData(Object obj) {
  // TODO Auto-generated method stub
  HashMap<String, String> map = (HashMap<String, String>)obj;
  String sql = String.format("DELETE FROM "+ TABLE_NAME+" WHERE _id = '%s' ", map.get(DBColumns._ID));
  getWritableDatabase().execSQL(sql);
 }


 @Override
 public void updateData(Object obj) {
  HashMap<String, String> map = (HashMap<String, String>)obj;
     ContentValues values = new ContentValues();
     
     String sql = "UPDATE "+ TABLE_NAME + 
       " SET "+DBColumns.NAME+" = ?, "
       +DBColumns.TEL+" = ?, "
       +DBColumns.EMAIL+" = ? "+
       "WHERE _ID = ? ";
     Object[] bindArgs = new Object[]{map.get(DBColumns.NAME),map.get(DBColumns.TEL),map.get(DBColumns.EMAIL),map.get(DBColumns._ID)};
     getWritableDatabase().execSQL(sql,bindArgs);
 }


 @Override
 public void FindAllData() { 
   String query = "SELECT * FROM "+TABLE_NAME;
   Cursor cursor = getWritableDatabase().rawQuery(query, null);
    
    
    StringBuffer sf = new StringBuffer();
    cursor.moveToFirst();
    while (!cursor.isAfterLast()) {
     sf.append(cursor.getInt(0)).append(" : ")
     .append(cursor.getString(1)).append(" : ")
     .append(cursor.getString(2)).append(" : ")
     .append(cursor.getString(3)).append("\n");
     cursor.moveToNext();
     }
    Log.d("Ted", sf.toString());
 }


 @Override
 public void FindLastID() {
  Cursor cur = getWritableDatabase().rawQuery("select LAST_INSERT_ROWID() ",null);
        cur.moveToFirst();
        Log.d("Ted","LAST ID"+ String.valueOf(cur.getLong(0)));
 }

}

上面實作了簡單的 insert ,update, del ,FindLastId , FindAllData的方法
當然其實也可以簡單的使用getWritableDatabase().insert getWritableDatabase().update getWritableDatabase().del  只是作者想順便練練execSQL的寫法而已 XD


若有興趣看看sampleCode 可以使用下面的網址

Sample Code

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年12月2日 星期一

Eclipse Theme

這是一個可以改變Eclipse主題的套件,如下圖所示


使用步驟如下


首先
進入Help -> Install New Software 安裝該軟體






在name可以隨便Key
在網址處key 入 http://eclipse-color-theme.github.io/update/
接著next 按到底就完成了


接著
到 http://eclipsecolorthemes.org/ 選擇喜歡的主題 點擊進去之後





接著點選Download底下的 (EPF) Eclipse Preference
下載下來之後將檔案import進去  點選Preferences




將剛剛下載的檔案Import進來





接著新的主題就出現了