程序中有重复代码?骨架实现(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. 抽象接口或骨架实现
要完成骨架实现:
建立接口。
建立抽象类来实现该接口,并实现公共方法。
在子类中建立一个私有内部类,继承抽象类。如今把外部调用委托给抽象类,该类能够在使用通用方法同时继承和实现任何接口。
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 骨架实现的好处
子类可继承其余类,好比 `DrinkVending`。
经过将调用委托给抽象类消除重复代码。
子类可根据须要实现其余的接口。
4. 总结
当接口有公用方法时能够建立抽象类,使用子类做为委派器,建议使用骨架实现。