浅谈设计模式(一):状态模式|外观模式|代理模式
前言
计划开一个设计模式的系列,介绍常见的几种设计模式,本文涉及的设计模式包含以下3种
-
状态模式:state pattern
-
外观模式:facade pattern
-
代理模式:proxy pattern
备注:下文适合看过《海贼王》的人阅读,没看过海贼王的观众请在父母陪同下阅读
状态模式:state pattern
在状态模式的设计方案里,一个主类(称为context类),可以在内部状态变化的时候一次性改变它的「所有行为」,而这个「所有行为」会被我们聚合到不同的类(state1,state2,state3)里面去。
这个内部状态我们可以理解为一个可以手动设置的state变量,设置它可以让context内部的state1切换为state2,或者是从state2切换为state3。
这么做,相比起传统的代码逻辑会发生什么变化呢?在传统的代码里,我们可能会在每个方法下,都写一大段if-else的状态判断逻辑里,然后对不同状态分别做处理,这个时候代码非常松散,不利于阅读和扩展,所以我们选择以「状态」为依据, 把这些if-else的每一部分都「聚合」到不同的状态(不同的state类)里面去,然后通过一个主类(context),去统一维护和管理。这样,逻辑上就清晰了很多,也大大降低了维护和扩展的难度。
Example
草帽路飞,是热血漫《海贼王》的主角,像其他许多同类型的作品一样,主角有自己不同层次战斗的状态,进化过程如下所示
-
二档:加速血液的流动,大幅提高速度和身体强度,代表大招是「橡胶Jet火箭炮」(拳)和 「橡胶Jet」(踢)
-
三档:向橡胶的身体吹入空气,使身体变成巨人,攻击力大增。代表大招是「橡胶巨人火箭炮」(拳)和「橡胶巨人战斧」(踢)
-
四档:将武装色霸气和橡胶果实融合,攻击和速度再次强化,代表大招是「橡胶狮子火箭炮」(拳)和「橡胶犀牛榴弹炮」(踢)
下图描述的是主角路飞初次进化为「二档」的历史性时刻
我们发现,主角路飞拥有不同的战斗状态:二档,三档,四档,并且大招的使用是类似的,无非就是用拳头还是用脚踢的问题,但是攻击力和招式上都不同,我们可以根据这个状态的统一性抽象一个state接口出来:
public interface State { // 拳打 public void punch (); // 脚踢 public void kick (); }
然后创建二档,三档,四档类,并且实现state接口
// 二档 public class SecGearState implements State { public void punch () { System.out.println("二档:橡胶Jet火箭炮"); }; public void kick () { System.out.println("二档:橡胶Jet鞭"); }; } // 三档 public class ThirdGearState implements State { public void punch () { System.out.println("三档:橡胶巨人火箭炮"); }; public void kick () { System.out.println("三档:橡胶巨人战斧"); }; } // 四档 public class FourGearState implements State{ public void punch () { System.out.println("四档:橡胶狮子火箭炮"); }; public void kick () { System.out.println("四档:橡胶犀牛榴弹炮"); }; }
最后,路飞可能会在战斗中随时切换状态,比如从二档切换到三档,或者从三档切换到四档,所以我们要设置一个Context类去管理,在这个类里面,它有两个功能
-
随时切换状态
-
代理调用状态类的方法
public class Context { State state; // 随时切换状态 public void setState(State state){ this.state = state; } // 代理调用状态类的方法 public void punch () { state.punch(); } public void kick () { state.kick(); } }
测试
public class Test { public static void main(String args []) { State secGearState = new SecGearState(); State thirdGearState = new ThirdGearState(); State fourGearState = new FourGearState(); Context context = new Context(); // 路飞进化成二档 context.setState(secGearState); context.punch(); context.kick(); System.out.println("----------------"); // 路飞进化成三档 context.setState(thirdGearState); context.punch(); context.kick(); System.out.println("----------------"); // 路飞进化成四档 context.setState(fourGearState); context.punch(); context.kick(); } }
输出
外观模式:facade pattern
外观模式很简单且容易理解,但理解之后却非常有用。
说白了就是:把不同类的不同接口,统一代理到一个类里面对外输出,使代码具有良好的封装性
Example
咱们还是拿海贼王的一个情境举个例子
比如说,在海贼王367里,草帽海贼团 VS 巨人僵尸奥兹 的时候,索隆,山治,佛兰奇,乌索普和乔巴使用了一招非常精(you)彩(zhi)的技能:合体-大皇帝。
也就是说,合体后的草帽海贼团,在能够使用每个人的绝招的同时,是作为“大皇帝”这个整体对外暴露的
我们使用外观模式去实现的话,代码逻辑如下所示
首先每个成员我们用一个类去表示
// 索隆 public class Zoro { public void useSword () { System.out.println("三刀流斩击"); } } // 山治 public class Sanj { public void kick () { System.out.println("恶魔风脚"); } } // 弗兰奇 public class Franky { public void openFire () { System.out.println("风来炮"); } } // 爱吃棉花糖的乔巴 public class QiaoBa { public void cure () { System.out.println("回血治疗"); } }
然后我们用一个整体的类,去代理上面的每个成员类的逻辑
// 合体后的大皇帝 public class BigKing { Franky franky; QiaoBa qiaoba; Sanj sanj; Zoro zoro; public BigKing () { franky = new Franky(); qiaoba = new QiaoBa(); sanj = new Sanj(); zoro = new Zoro(); } // 索隆类的功能 public void useSord () { zoro.useSword(); } // 山治类的功能 public void kick () { sanj.kick(); } // 佛兰奇类的功能 public void openFire () { franky.openFire(); } // 乔巴类的功能 public void cure () { qiaoba.cure(); } }
测试
public class Test { public static void main(String args []) { BigKing bigking = new BigKing(); bigking.useSord(); bigking.kick(); bigking.cure(); bigking.openFire(); } }
输出
附带一张图,hhhh
代理模式:proxy pattern
使用一个类接管另一个类所有的方法调用,同时能在原来类的方法调用前,加入一些自己的“中间逻辑”。这种方式被称为代理模式。
假设类B 是类A的代理类,那么在调用类B的方法的时候,实际还是通过类B去调用类A的接口,但是现在所有的「控制权」都已经牢牢掌握在类B手里了,代理类B能够很自由的加入一些中间逻辑。
显然,类B和类A起到的功能是相同的,我们可以抽象一个接口,去让原类(A )和代理类(B)去实现
Example
不好意思,这里还是用我熟悉的海贼王打个比方,在七武海-多佛朗明哥刚刚出场的时候,他就用线线果实提供的能力,操控两名海军自相残杀。
如果我们把海军抽象为一个类的话,那么多佛朗明哥就是「海军类」的代理类了,实际上我们发现
-
实质出手伤人的并不是海军,而是多佛朗明哥,也就是代理类掌握了真正的控制权
-
直接出手伤人的仍然是海军,也就是代理类仍然调用的是原类的接口
代码如下
1.我们抽象一个海军战士的接口出来
// 海军战士接口 public interface NavyFighter { // 使用刀剑 public void useSword (); // 徒手格斗 public void fight(); // 使用枪炮 public void useGuns(); }
2.让海军军官实现这个接口
public class NavyCaptain implements NavyFighter { String name = "海军上尉"; // 海军装备预算不够,不能购买二十一大快刀 public void useSword() { System.out.println(name +"发动了一次普通的斩击"); } // 没有果实能力,只能徒手格斗了 public void fight() { System.out.println(name + "发动了一次普通的拳击"); } public void useGuns() { System.out.println(name +"打出了一发普通的海楼石子弹"); } }
3.让多佛朗明哥也实现这个接口
public class Doflamingo implements NavyFighter { NavyFighter navyFighter; public Doflamingo (NavyFighter navyFighter) { this.navyFighter = navyFighter; } public void useSword() { System.out.print("在多佛朗明哥操控下,"); this.navyFighter.useSword(); } public void fight() { System.out.print("在多佛朗明哥操控下,"); this.navyFighter.fight(); } public void useGuns() { System.out.print("在多佛朗明哥操控下,"); this.navyFighter.useGuns(); } }
测试
public class Test { public static void main(String args []) { NavyFighter navyCaptain = new NavyCaptain(); navyCaptain.useSword(); navyCaptain.fight(); navyCaptain.useGuns(); System.out.println("-----------------------------"); NavyFighter doflamingo = new Doflamingo(navyCaptain); doflamingo.useSword(); doflamingo.fight(); doflamingo.useSword(); } }
输出
End