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會造成許多問題,
善用這個方法,可以降低耦合,使程式碼更加彈性。

沒有留言:

張貼留言