在 Java 中應用骨架實現

程序中有重複代碼?骨架實現(Skeletal Implementation)經過接口與抽象類配合,讓你擺脫重複,留下程序中有用的代碼。java


骨架實現是一種設計,咱們能夠同時享受接口和抽象類的好處。shell


Java Collection API 已經採用了這種設計:AbstractSet、 AbstractMap 等都是骨架實現案例。Joshua Bloch 的"Effective Java"書中也提到了骨架接口。app


本文咱們將探討如何高效設計系統,使其可以同時利用接口和抽象類的特性。ide


讓咱們試着經過一個實際問題來理解。工具


假設咱們想建立不一樣類型的自動售貨機。從自動售貨機購買產品,須要啓動售貨機、選擇產品、付款、而後取貨。spa


取貨完成以後,自動售貨機應該中止操做。設計


1. 方法一code


咱們能夠爲不一樣的產品類型建立一個自動售貨機接口。爲了讓接口工做,咱們還要爲自動售貨機提供具體實現。orm


1.1 代碼繼承


Ivending.java

```java
package com.example.skeletal;
public interface Ivending {
   void start();
   void chooseProduct();
   void stop();
   void process();
}
```


CandyVending.java

```java
package com.example.skeletal;
public class CandyVending implements Ivending {
   @Override
   public void start()
{
       System.out.println("Start Vending machine");
   }

   @Override
   public void chooseProduct()
{
       System.out.println("Produce different candies");
       System.out.println("Choose a type of candy");
       System.out.println("Pay for candy");
       System.out.println("Collect candy");
   }
   
   @Override
   public void stop()
{
       System.out.println("Stop Vending machine");
   }

   @Override
   public void process()
{
       start();
       chooseProduct();
       stop();
   }
}
```


DrinkVending.java

```java
package com.example.skeletal;
public class DrinkVending implements Ivending {
   @Override
   public void start()
{
       System.out.println("Start Vending machine");
   }

   @Override
   public void chooseProduct()
{
       System.out.println("Produce diiferent soft drinks");
       System.out.println("Choose a type of soft drinks");
       System.out.println("pay for drinks");
       System.out.println("collect drinks");
   }

   @Override
   public void stop()
{
       System.out.println("stop Vending machine");
   }

   @Override
   public void process()
{
       start();
       chooseProduct();
       stop();
   }
}
```


VendingManager.java

```java
package com.example.skeletal;
public class VendingManager {
   public static void main(String[] args) {
       Ivending candy = new CandyVending();
       Ivending drink = new DrinkVending();
       candy.process();
       drink.process();
   }
}
```


輸出結果:


```shell
Start Vending machine
Produce different candies
Choose a type of candy
Pay for candy
Collect candy
Stop Vending machine
*********************
Start Vending machine
Produce diiferent soft drinks
Choose a type of soft drinks
Pay for drinks
Collect drinks
Stop Vending machine
```


簡單起見,我沒有將每一個步驟定義一個單獨方法,在 `chooseProduct()` 中合併了這些步驟。


雖然看起來很好,可是上面的代碼」有一些問題「。若是咱們仔細檢查一下,就會發現其中有不少重複代碼。 `start()`、 `stop()` 和 `process()` 方法在每一個實現類中作了相同的事情。


當新增具體實現時,系統的代碼會複製三次。


這時咱們能夠新建工具類,將公共代碼放到工具類裏。然而這麼作會破壞」單一責任原則「,產生 Shotgun surgery 問題代碼。


譯註:[Shotgun surgery][1] 是軟件開發中的一種反模式,它發生在開發人員嚮應用程序代碼庫添加特性的地方,這些代碼庫會在一次更改中跨越多個實現。


[1]:https://en.wikipedia.org/wiki/Shotgun_surgery


1.2 接口的缺點


因爲接口是一種約定且不包含方法體,所以每一個實現都必須按照約定實現接口中的全部方法。在具體的實現中一些方法可能會重複。


2. 方法二


經過抽象類彌補接口的不足。


2.1 代碼


AbstractVending.java

```java
package com.example.skeletal;
public abstract class AbstractVending {
   public void start()
   
{
       System.out.println("Start Vending machine");
   }

   public abstract void chooseProduct();
   
   public void stop()
   
{
       System.out.println("Stop Vending machine");
   }
   
   public void process()
   
{
       start();
       chooseProduct();
       stop();
   }
}
```


CandyVending.java

```java
package com.example.skeletal;
public class CandyVending extends AbstractVending {
   @Override
   public void chooseProduct()
{
       System.out.println("Produce diiferent candies");
       System.out.println("Choose a type of candy");
       System.out.println("Pay for candy");
       System.out.println("Collect candy");
   }
}
```


DrinkVending.java

```java
package com.example.skeletal;
public class DrinkVending extends AbstractVending {
   @Override
   public void chooseProduct()
{
       System.out.println("Produce diiferent soft drinks");
       System.out.println("Choose a type of soft drinks");
       System.out.println("Pay for drinks");
       System.out.println("Collect drinks");
   }
}
```


VendingManager.java

```java
package com.example.skeletal;
public class VendingManager {
   public static void main(String[] args) {
       AbstractVending candy =  new CandyVending();
       AbstractVending drink =  new DrinkVending();
       candy.process();
       System.out.println("*********************");
       drink.process();
   }
}
```


這裏我爲抽象類提供了通用的代碼,`CandyVending` 和 `DrinkVending` 都繼承了 `AbstractVending`。這麼作雖然消除了重複代碼,但引入了一個新問題。


`CandyVending` 和 `DrinkVending` 繼承了 `AbstractVending`,因爲 Java 不支持多重集成所以不能繼承其餘類。


假如要添加一個 `VendingServicing` 類,負責清潔和檢查自動售貨機。在這種狀況下,因爲已經繼承了 `AbstractVending`,所以不能繼承 `VendingServicing`。這裏能夠新建組合(composition),可是必須把 `VendingMachine` 傳入該組合,這會讓 `VendingServicing` 和 `VendingMachine` 產生強耦合。


2.2 抽象類的缺點


因爲菱形繼承問題,Java 不支持多重繼承。假如咱們可以同時利用接口和抽象類的優勢就太好了。


仍是有辦法的。


譯註:菱形繼承問題。兩個子類繼承同一個父類,而又有子類又分別繼承這兩個子類,產生二義性問題。


3. 抽象接口或骨架實現


要完成骨架實現:


  1. 建立接口。

  2. 建立抽象類來實現該接口,並實現公共方法。

  3. 在子類中建立一個私有內部類,繼承抽象類。如今把外部調用委託給抽象類,該類能夠在使用通用方法同時繼承和實現任何接口。


3.1 代碼


Ivending.java

```java
package com.example.skeletal;
public interface Ivending {
   void start();
   void chooseProduct();
   void stop();
   void process();
}
```


VendingService.java

```java
package com.example.skeletal;
public class VendingService {
   public void service()
   
{
       System.out.println("Clean the vending machine");
   }
}
```


AbstractVending.java

```java
package com.example.skeletal;
public abstract class AbstractVending implements Ivending {
   public void start()
   
{
       System.out.println("Start Vending machine");
   }
   public void stop()
   
{
       System.out.println("Stop Vending machine");
   }
   public void process()
   
{
       start();
       chooseProduct();
       stop();
   }
}
```


CandyVending.java

```java
package com.example.skeletal;
public class CandyVending  implements Ivending {
   private class AbstractVendingDelegator extends AbstractVending
   {
       @Override
       public void chooseProduct()
{
           System.out.println("Produce diiferent candies");
           System.out.println("Choose a type of candy");
           System.out.println("Pay for candy");
           System.out.println("Collect candy");
       }
   }

   AbstractVendingDelegator delegator = new AbstractVendingDelegator();

   @Override
   public void start()
{
       delegator.start();
   }
   @Override
   public void chooseProduct()
{
       delegator.chooseProduct();
   }
   @Override
   public void stop()
{
       delegator.stop();
   }
   @Override
   public void process()
{
       delegator.process();
   }
}
```


DrinkVending.java

```java
package com.example.skeletal;
public class DrinkVending extends VendingService  implements Ivending {
   private class AbstractVendingDelegator extends AbstractVending
   {
       @Override
       public void chooseProduct()
{
           System.out.println("Produce diiferent soft drinks");
           System.out.println("Choose a type of soft drinks");
           System.out.println("pay for drinks");
           System.out.println("collect drinks");
       }
   }
   AbstractVendingDelegator delegator = new AbstractVendingDelegator();
   @Override
   public void start()
{
       delegator.start();
   }
   @Override
   public void chooseProduct()
{
       delegator.chooseProduct();
   }
   @Override
   public void stop()
{
       delegator.stop();
   }
   @Override
   public void process()
{
       delegator.process();
   }
}
```


VendingManager.java

```java
package com.example.skeletal;
public class VendingManager {
   public static void main(String[] args) {
       Ivending candy = new CandyVending();
       Ivending drink = new DrinkVending();
       candy.process();
       System.out.println("*********************");
       drink.process();
       if(drink instanceof VendingService)
       {
           VendingService vs = (VendingService)drink;
           vs.service();
       }
   }
}
```


```shell
Start Vending machine
Produce diiferent candies
Choose a type of candy
Pay for candy
Collect candy
Stop Vending machine
*********************
Start Vending machine
Produce diiferent soft drinks
Choose a type of soft drinks
Pay for drinks
Collect drinks
Stop Vending machine
Clean the vending machine
```


上面的設計中,首先建立了一個接口,而後建立了一個抽象類,在這個類中定義了全部通用的實現。而後,爲每一個子類實現一個 delegator 類。經過 delegator 將調用轉給 `AbstractVending`。


3.2 骨架實現的好處


  1. 子類可繼承其餘類,好比 `DrinkVending`。

  2. 經過將調用委託給抽象類消除重複代碼。

  3. 子類可根據須要實現其餘的接口。


4. 總結


當接口有公用方法時能夠建立抽象類,使用子類做爲委派器,建議使用骨架實現。

相關文章
相關標籤/搜索