清除Java中的重复代码-骨架实现
Reference:
在 Java 中应用骨架实现 Effective Java - ITEM 18 重组合,轻继承 Effective Java 3 相关博客
Java Skeletal Implementation/Abstract Interfaces(骨架实现/抽象接口)指通过接口和抽象类,集接口多继承的优势与抽象类可以减少重复代码的优势于一体。
Java Collection API
已经采用了这种设计:AbstractSet
、 AbstractMap
等都是骨架实现案例。
Joshua Bloch
的Effective Java
书中也提到了骨架接口。
接口和抽象类
场景 对于一个咖啡续命的人来说,每天泡咖啡是常态,假如将我们泡咖啡的行为抽象则主要为以下步骤:
方式一(接口实现) CoffeeDaily
1 2 3 4 5 6 7 8 9 public interface CoffeeDaily { void chooseCoffee () ; void makeCoffee () ; void drinkCoffee () ; void process () ; }
InstantCoffee
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class InstantCoffee implements CoffeeDaily { @Override public void chooseCoffee () { System.out.println("today is instant coffee." ); } @Override public void makeCoffee () { System.out.println("make instant coffee" ); } @Override public void drinkCoffee () { System.out.println("drink instant coffee" ); } @Override public void process () { chooseCoffee(); makeCoffee(); drinkCoffee(); } }
LatteCoffee
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class LatteCoffee implements CoffeeDaily { @Override public void chooseCoffee () { System.out.println("today is latte coffee." ); } @Override public void makeCoffee () { System.out.println("make latte coffee" ); } @Override public void drinkCoffee () { System.out.println("drink latte coffee" ); } @Override public void process () { chooseCoffee(); makeCoffee(); drinkCoffee(); } }
Daily
1 2 3 4 5 6 7 8 9 10 public class Daily { public static void main (String[] args) { InstantCoffee instantCoffee = new InstantCoffee(); LatteCoffee latteCoffee = new LatteCoffee(); instantCoffee.process(); System.out.println("-----------------" ); latteCoffee.process(); } }
output
1 2 3 4 5 6 7 today is instant coffee. make instant coffee drink instant coffee ----------------- today is latte coffee. make latte coffee drink latte coffee
由于实现接口的类都必须实现接口中定义的方法,所以可能会有重复的方法。
上述例子将所有步骤集中到process()
中处理,但是其中其实有很多重复代码,单我们继续添加具体实现时,重复代码也会继续增加。
如果我们将公共的重复代码提取放入一个工具类中,看上去解决了大段重复代码的问题
但是可能会造成Shotgun surgery 问题代码
,而且也违反了单一责任原则
Shotgun surgery 是软件开发中的一种反模式,它发生在开发人员向应用程序代码库添加特性的地方,这些代码库会在一次更改中跨越多个实现。
方式二(抽象类) AbstractCoffeeDaily
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public abstract class AbstractCoffeeDaily { private String coffeeName; public AbstractCoffeeDaily (String coffeeName) { this .coffeeName = coffeeName; } public void chooseCoffee () { System.out.println((String.format("today is %s coffee." ,coffeeName))); } public void makeCoffee () { System.out.println((String.format("make %s coffee" ,coffeeName))); } public void drinkCoffee () { System.out.println((String.format("drink %s coffee" ,coffeeName))); } protected final void process () { this .chooseCoffee(); this .makeCoffee(); this .drinkCoffee(); } }
InstantCoffee
1 2 3 4 5 6 7 8 public class InstantCoffee extends AbstractCoffeeDaily { private static final String COFFEE_NAME = "instant" ; public InstantCoffee () { super (COFFEE_NAME); } }
LatteCoffee
1 2 3 4 5 6 7 8 public class LatteCoffee extends AbstractCoffeeDaily { private static final String COFFEE_NAME = "latte" ; public LatteCoffee () { super (COFFEE_NAME); } }
Daily
1 2 3 4 5 6 7 8 9 10 public class Daily { public static void main (String[] args) { AbstractCoffeeDaily instantCoffee = new InstantCoffee(); AbstractCoffeeDaily latteCoffee = new LatteCoffee(); instantCoffee.process(); System.out.println("-----------------" ); latteCoffee.process(); } }
output
1 2 3 4 5 6 7 today is instant coffee. make instant coffee drink instant coffee ----------------- today is latte coffee. make latte coffee drink latte coffee
由于菱形继承问题,Java 不支持多重继承。
菱形继承问题: 两个子类继承同一个父类,而又有子类又分别继承这两个子类,产生二义性问题
InstantCoffee
和LatteCoffee
都继承AbstractCoffeeDaily
,抽取了公用代码,消除了重复的代码。
但是由于Java
不支持多重继承,如果我此时想在添加一个外部类引入的方法,如:自动清洗杯子机器类CupCleanMachine
。
此时的InstantCoffee
和LatteCoffee
不能再继续继承,如果采用组合(composition
)的方式,将CupCleanMachine
引入,则会造成强耦合。
方式三(骨架实现或抽象接口) 骨架实现的步骤:
创建接口
创建抽象类实现该接口,并实现公共方法
在具体实现子类中创建一个私有内部类,继承抽象类。将外部调用委托
给抽象接口,该类可以在使用通用方法的同时继承和实现其他接口。
接口:CoffeeDaily
1 2 3 4 5 6 7 8 9 public interface CoffeeDaily { void chooseCoffee () ; void makeCoffee () ; void drinkCoffee () ; void process () ; }
杯子清洗机器:CupCleanMachine
1 2 3 4 5 public class CupCleanMachine { public void clean () { System.out.println("clean the cup." ); } }
AbstractCoffeeDaily
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class AbstractCoffeeDaily implements CoffeeDaily { private String coffeeName; public AbstractCoffeeDaily (String coffeeName) { this .coffeeName = coffeeName; } @Override public void chooseCoffee () { System.out.println((String.format("today is %s coffee." ,coffeeName))); } @Override public void makeCoffee () { System.out.println((String.format("make %s coffee" ,coffeeName))); } @Override public void drinkCoffee () { System.out.println((String.format("drink %s coffee" ,coffeeName))); } @Override public final void process () { chooseCoffee(); makeCoffee(); drinkCoffee(); } }
InstantCoffee
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class InstantCoffee implements CoffeeDaily { private static final String COFFEE_NAME = "instant" ; InstantCoffeeDelegator instantCoffeeDelegator = new InstantCoffeeDelegator(COFFEE_NAME); @Override public void chooseCoffee () { instantCoffeeDelegator.chooseCoffee(); } @Override public void makeCoffee () { instantCoffeeDelegator.makeCoffee(); } @Override public void drinkCoffee () { instantCoffeeDelegator.drinkCoffee(); } @Override public void process () { instantCoffeeDelegator.process(); } private class InstantCoffeeDelegator extends AbstractCoffeeDaily { public InstantCoffeeDelegator (String coffeeName) { super (coffeeName); } } }
LatteCoffee
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class LatteCoffee extends CupCleanMachine implements CoffeeDaily { private static final String COFFEE_NAME = "latte" ; LatteCoffeeDelegator latteCoffeeDelegator = new LatteCoffeeDelegator(COFFEE_NAME); @Override public void chooseCoffee () { latteCoffeeDelegator.chooseCoffee(); } @Override public void makeCoffee () { latteCoffeeDelegator.makeCoffee(); } @Override public void drinkCoffee () { latteCoffeeDelegator.drinkCoffee(); } @Override public void process () { latteCoffeeDelegator.process(); clean(); } private class LatteCoffeeDelegator extends AbstractCoffeeDaily { public LatteCoffeeDelegator (String coffeeName) { super (coffeeName); } } }
Daily
1 2 3 4 5 6 7 8 9 10 public class Daily { public static void main (String[] args) { InstantCoffee instantCoffee = new InstantCoffee(); LatteCoffee latteCoffee = new LatteCoffee(); instantCoffee.process(); System.out.println("-----------------" ); latteCoffee.process(); } }
output
1 2 3 4 5 6 7 8 today is instant coffee. make instant coffee drink instant coffee ----------------- today is latte coffee. make latte coffee drink latte coffee clean the cup.
上述方法中先创建了一个接口,定义主要的行为,然后创建抽象类实现接口定义所有通用的方法实现。
在具体子类中,实现一个委托类(delegator
),通过delegator
调用通用的方法实现
这样子类即可以再继承其他类,又可以实现其他的接口,同时也消除了重复的通用方法。