之前在infoq上看到一篇文章:
swift版本原文參考:
http://www.infoq.com/cn/articles/design-patterns-in-swift
于是想著把這篇文章修改為Java8的版本,本文是對原文的Java8版本的修改,所以大部分文章和示例都是采用原文的敘述;再次表達對原文作者的感謝;
設計模式##
設計模式(Design Pattern)是對軟件設計中普遍存在的各種問題,所提出的解決方案。這個術語是由埃里?!べが?shù)热耍‥rich Gamma,Richard Helm,Ralph Johnson和John Vlissides這四人提出的。也被稱為:Gang of Four,GOF,四人幫)在1990年代從建筑設計領域引入到計算機科學的。 設計模式并不能直接用于完成代碼的編寫,而是描述在各種不同情況下,要怎么解決問題的一種方案。
以上描述摘自維基百科。
隨著我們所使用的編程語言的演化,我們遇到的問題也確實一直在改變。GOF提出的23種設計模式也許部分過時了。但我們遇到的問題并不會消失,設計模式的概念將一直存在:提煉普遍存在的問題,提出解決方案。這其實是一個抽象過程?,F(xiàn)在已有的編程語言都存在表達的局限,即對某類問題抽象層次過低。所以,我們在使用任何編程語言時,都還是會遇到一些普遍存在的卻沒有被語言本身很好解決的問題。這時,我們就會使用到“設計模式”,即人們總結出來的解決方案:遇到問題A,用方案A;遇到問題B,用方案B。只不過問題會一直變化?,F(xiàn)在我們不可以再使用那23個模式來解決問題了,但是我們仍然需要總結出其它模式來解決新的問題。這種情況一直會持續(xù)到我們擁有“完美語言”的那一天。但現(xiàn)在看起來,這一天還沒有到來的跡象。
Java8中的設計模式##
Java8中提出了函數(shù)式編程等新的概念,使得Java8在面對傳統(tǒng)的GOF23模式的時候,解決問題的方式已經得到了部分的改善,之前的一些設計模式在Java8面前就已經不再是問題了。但是Java8提出了函數(shù)式編程的同時,因為涉及到向后兼容的問題,同樣在Java8中可能面對新的代碼設計問題,比如接口中的默認方法帶來的諸多設計細節(jié),同樣也是會引人深思的;
下面主要討論的是,在Java8中,傳統(tǒng)的哪些開發(fā)模式被消除了或者以一種更簡單的方式簡化了。
一,命令模式##
命令模式使用對象封裝一系列操作(命令),使得操作可以重復使用,也易于在對象間傳遞。首先來一個傳統(tǒng)Java方式實現(xiàn)的命令模式代碼。
public class Light {
public void on() {
System.out.println("light turn on");
}
public void off() {
System.out.println("light turn off");
}
}
interface Command {
void execute();
}
class FilpUpCommand implements Command {
private Light light;
public FilpUpCommand(Light light) {
this.light = light;
}
public void execute() {
light.on();
}
}
class FilpDownCommand implements Command {
private Light light;
public FilpDownCommand(Light light) {
this.light = light;
}
public void execute() {
light.off();
}
}
以上代碼中,燈(Light)是命令(Command)的操作對象(Receiver)。我們定義了命令的協(xié)議,同時我們實現(xiàn)兩個具體的命令操作:FlipUpCommand和FlipDownCommand。它們分別使燈亮,和使燈滅。
class LightSwitch{
private List<Command> queue=new ArrayList<>();
public void addCommand(Command cmd){
queue.add(cmd);
}
public void execute(){
for(Command cmd:queue){
cmd.execute();
}
}
}
class Client{
public static void pressSwitch(){
Light lamp=new Light();
Command flipUpCommand=new FilpUpCommand(lamp);
Command flipDowomnCmand=new FilpDownCommand(lamp);
LightSwitch lightSwitch = new LightSwitch();
lightSwitch.addCommand(flipUpCommand);
lightSwitch.addCommand(flipDowomnCmand);
lightSwitch.addCommand(flipUpCommand);
lightSwitch.addCommand(flipDowomnCmand);
lightSwitch.execute();
}
}
上面的代碼首先創(chuàng)建了一個命令執(zhí)行者LightSwitch,并創(chuàng)建一個客戶對象來使用命令;
在函數(shù)式編程中,由于存在高階函數(shù)。我們可以直接將一個函數(shù)作為參數(shù)傳給另外一個函數(shù)。所以,使用類包裹函數(shù)在對象間傳遞這件事情就顯得多余了。以下代碼顯示如何使用高階函數(shù)達到命令模式相同的效果:
class LightSwitchFP {
private List<Consumer<Light>> queue = new ArrayList<>();
public void addCommand(Consumer<Light> cmd) {
queue.add(cmd);
}
public void execute(Light light) {
for (Consumer<Light> cunsumer : queue) {
cunsumer.accept(light);
}
}
}
class Client {
public static void pressSwitch() {
Light lamp = new Light();
Consumer<Light> flipUp = light -> {light.on();};
Consumer<Light> flipDown = light -> {light.off();};
LightSwitchFP lightSwitch = new LightSwitchFP();
lightSwitch.addCommand(flipUp);
lightSwitch.addCommand(flipDown);
lightSwitch.addCommand(flipUp);
lightSwitch.addCommand(flipDown);
lightSwitch.execute(lamp);
}
}
在Java8中,首先我們直接使用Java8提供的Consumer函數(shù)接口作為我們的命令接口,因為有了lambda表達式,我們根本無需在單獨為具體命令對象創(chuàng)建類型,而通過傳入labmda表達式來完成具體命令對象的創(chuàng)建;
二,策略模式##
策略模式定義了一系列算法,將每個算法封裝起來,并且使它們之間可以互相替換。此模式讓算法的變化獨立于使用算法的客戶。
下面簡單演示一個傳統(tǒng)的策略模式實現(xiàn)方案:
interface Strategy {
public Integer compute(Integer op1, Integer op2);
}
class Add implements Strategy {
public Integer compute(Integer op1, Integer op2) {
return op1 + op2;
}
}
class Multiply implements Strategy {
public Integer compute(Integer op1, Integer op2) {
return op1 * op2;
}
}
class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void use(Integer first, Integer second) {
System.out.println(this.strategy.compute(first, second));
}
}
類似于命令模式,策略模式中的策略對象主要用于封裝操作(函數(shù)),不同的是策略模式中的策略對象封裝的是不同的算法。這些算法實現(xiàn)了相同的接口,在這個例子中,接口是用Strategy協(xié)議表示的。我們使用兩個實現(xiàn)了Strategy協(xié)議的具體類:Add和Multiply分別封裝兩個簡單的算法。Context對象,用于對算法進行配置選擇,它有一個Strategy類型的實例變量:strategy。通過配置Context的strategy具體類型,可以使用不同的算法。
然后我們再看看怎么簡化策略模式:
public static final BinaryOperator<Integer> add = (op1, op2) -> op1 + op2;
public static final BinaryOperator<Integer> multiply = (op1, op2) -> op1 * op2;
class ContextFP {
private BinaryOperator<Integer> strategy;
public ContextFP(BinaryOperator<Integer> strategy) {
this.strategy = strategy;
}
public void use(Integer first, Integer second) {
System.out.println(strategy.apply(first, second));
}
}
public static void main(String[] args) {
StraFP fp = new StraFP();
ContextFP ctx = fp.new ContextFP(StraFP.add);
ctx.use(1, 2);
}
在Java8中,我們很自然的使用內建的function interface作為封裝算法的載體,這樣更為直接自然。例子中,ContextFP的構造器的傳參就是函數(shù)類型。給予構造器代表不同算法的函數(shù),就配置了不同的算法。
函數(shù)也可以作為類的實例變量。這樣在類中,直接維護代表算法的函數(shù)也成為可能。從類型聲明可以看出,ContextFP中的實例變量strategy就是一個函數(shù)。
一等函數(shù)的概念使得函數(shù)獲得了更高的地位,使得函數(shù)的靈活性大大增加。在很多場景下直接使用函數(shù)會是更直接自然的選擇。面向對象編程范式,賦予了對象更高的地位。但是,如果給予函數(shù)“正?!币恍┑牡匚?,可以簡化不少問題。設計模式中的不少模式存在都是由于函數(shù)的使用限制,需要使用在使用類包裹函數(shù)。類似的例子還有模版方法模式(Template method)。
上面代碼示例不能對策略做很好的封裝,下面提供了一個枚舉的版本:
public enum StrategyEnum {
ADD(() -> (x, y) -> x + y),
MULTIPLY(() -> (x, y) -> x * y);
private Supplier<BinaryOperator<Integer>> operation;
private StrategyEnum(Supplier<BinaryOperator<Integer>> operation) {
this.operation = operation;
}
public BinaryOperator<Integer> get() {
return operation.get();
}
}
class ContextFP {
private StrategyEnum strategy;
public ContextFP(StrategyEnum strategy) {
this.strategy = strategy;
}
public void use(Integer first, Integer second) {
System.out.println(strategy.get().apply(first, second));
}
}