状态机设计模式_设计模式-代理/状态机模式
代理模式
是使率非常高的模式: 為其它對象提供一種代理以控制這個對象的訪問。該模式也稱為委托模式,在使用的時候代理模式可以為我們提供非常好的訪問控制。
如下代碼
interface IGamePlayer {void login(String user, String password);void killBoss();void upgrade(); }class GamePalyer implements IGamePlayer {private String name = "";public GamePalyer(String name) {this.name = name;}@Overridepublic void login(String user, String password) {System.out.println("登錄名為 " + user + "的用戶" + this.name + "登錄成功");}@Overridepublic void killBoss() {System.out.println(this.name + "在打怪");}@Overridepublic void upgrade() {System.out.println(this.name + "又升級了");} }class GamePlayerProxy implements IGamePlayer {private IGamePlayer gamePlayer = null;public GamePlayerProxy(IGamePlayer gamePlayer) {this.gamePlayer = gamePlayer;}@Overridepublic void login(String user, String password) {this.gamePlayer.login(user,password);}@Overridepublic void killBoss() {this.gamePlayer.killBoss();}@Overridepublic void upgrade() {this.gamePlayer.killBoss();} } public class ProxyModel {public static void main(String[] args) {// 玩家直接打游戲GamePalyer palyer = new GamePalyer("張三");palyer.login("zhangsan","password");palyer.killBoss();palyer.upgrade();// 找個游戲代練幫 palyer 打升級GamePlayerProxy gamePlayerProxy = new GamePlayerProxy(palyer);gamePlayerProxy.login("zhangsan","password");gamePlayerProxy.killBoss();gamePlayerProxy.upgrade();} }代理模式也稱為委托模式,在使用的時候代理模式可以為我們提供非常好的訪問控制,上面例子代碼就是使用 GamePlayerProxy 代理類去代理真正的玩游戲的人,調用的代理類的方法 login killBoss upgrade 等實際上都是真正的用戶的方法。一個代理類可以代理多個被委托者或者代理者,一個代理類具體代理哪個真實主體角色,是由場景來決定的。
通常情況下一個接口只需要一個代理類就可以了。
代理模式的特點
- 職責清晰
真實的角色就是實現實際的業務邏輯(實現接口中的方法),不用關系其它非本職責的事務。
- 高擴展性
具體的主體角色是隨時都會發生變化的,只要它實現了接口,不管如何變化,都需要復合接口中方法定義的輸入輸出邏輯,而代理類則不需要做任何修改可以直接使用。
普通代理
普通代理就是我們要知道代理的的存在,它的要求就是客戶端只能訪問代理角色,而不能訪問方法的真實角色,這是比較簡單的,如在上面的例子中main 函數是可以訪問到被代理對象。
對上面的例子做一個擴展,使得main 方法只能訪問代理對象,而訪問不到被代理對象。
interface IGamePlayer {void login(String user, String password);void killBoss();void upgrade(); }class GamePalyer implements IGamePlayer {private String name = "";// 對能創建的對象進行限制public GamePalyer(IGamePlayer _gamePlayer ,String name) throws Exception {if (_gamePlayer == null) {throw new Exception("不能創建角色");}else {this.name = name;}}@Overridepublic void login(String user, String password) {System.out.println("登錄名為 " + user + "的用戶" + this.name + "登錄成功");}@Overridepublic void killBoss() {System.out.println(this.name + "在打怪");}@Overridepublic void upgrade() {System.out.println(this.name + "又升級了");} }class GamePlayerProxy implements IGamePlayer {private IGamePlayer gamePlayer = null;// 通過構造函數要對誰進行代練public GamePlayerProxy(String gamePlayerName) {try {gamePlayer = new GamePalyer(this, gamePlayerName);} catch (Exception e) {e.printStackTrace();}}@Overridepublic void login(String user, String password) {this.gamePlayer.login(user,password);}@Overridepublic void killBoss() {this.gamePlayer.killBoss();}@Overridepublic void upgrade() {this.gamePlayer.killBoss();} } public class ProxyModel {public static void main(String[] args) {// 找個游戲代練幫 palyer 打升級,給被代理對象直接傳遞一個對象。GamePlayerProxy gamePlayerProxy = new GamePlayerProxy("張三");gamePlayerProxy.login("zhangsan","password");gamePlayerProxy.killBoss();gamePlayerProxy.upgrade();} }一個類可以實現多個接口,完成不同任務的整合,代理類不僅僅可以實現主體接口,也可以實現其它接口完成不了的任務,而且代理的目地是在目標對象的方法基礎之上做增強,這種增強的本質通常就是對目標對象的方法進行攔截和過濾。如還可以給代理類實現其它的接口并實現,從而增強代理類
interface IProxy {void count(); }class GamePalyer implements IGamePlayer, IProxy{....}動態代理
所謂動態代理就是在實現階段不用關系代理誰,而是在運行階段才指定代理哪一個對象,相對來說,自己寫代理類的方式就是靜態代理,而動態代理在橫切面編程也就是AOP使用的畢竟流行。
class GamePlayIH implements InvocationHandler {// 被代理者Class cls = null;// 被代理的實例Object obj = null;public GamePlayIH(Object obj) {this.obj = obj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return method.invoke(this.obj, args);} }完成對真實方法的調用,動態代理是根據被代理的接口生成所有的方法,也就是說給定一個接口,動態代理會宣稱,我已經實現該接口下的所有方法了那么動態代理是如何知道被代理接口中的方法呢,默認情況下所有的方法的返回值都是空的,是的,代理已經實現它了,但是沒有任何的邏輯含義,所有的方法都是由該Handler 來進行處理,即所有被代理的方法都是由 Handler 來進行處理 即所有被代理的方法都是由 InvocationHandler 接管實際的處理任務。
調用
GamePalyer palyer = new GamePalyer("張三");GamePlayIH gamePlayIH = new GamePlayIH(palyer);ClassLoader cl = palyer.getClass().getClassLoader();IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(cl, GamePalyer.class.getInterfaces(), gamePlayIH); proxy.login("zhangsan","password"); proxy.killBoss(); proxy.upgrade();這里我們既沒有創建代理類,也沒有實現 IGamePlayer 接口,這就是動態代理。
還可以使用動態代理做很多增強的事情
class GamePlayIH implements InvocationHandler {// 被代理者Class cls = null;// 被代理的實例Object obj = null;public GamePlayIH(Object obj) {this.obj = obj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object invoke = method.invoke(this.obj, args);if (method.getName().equalsIgnoreCase("login")) {System.out.println("有人在用我的賬號登錄");}return invoke;} }使用動態代理寫一個簡單的AOP的功能
大概的設計如上圖,動態代理實現代理的職責,業務邏輯Subject 實現相關的邏輯功能,兩者直接沒有必然的相互耦合的關系,通知Advice 從另一個切面切入,最終在高層模塊Client 進行藕盒,完成邏輯的封裝任務。
interface Subject {void doSomething(String str); }class RealSubject implements Subject {@Overridepublic void doSomething(String str) {System.out.println("----" + str);} }class MyInvocationHandler implements InvocationHandler {// 被代理的對象private Object target = null;// 通過構造函數傳遞一個對象public MyInvocationHandler(Object target) {this.target = target;}// 代理方法@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 執行代理方法return method.invoke(this.target, args);} }class DynamicProxy<T> {public static <T> T newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) {// 尋找JoinPoint 連接點,AOP 框架使用元數據定義if (true) {// 執行一個前置通知(new BeforeAdvice()).exec();}// 執行目標,并返回結果return (T) Proxy.newProxyInstance(loader, interfaces, h);} }class SubjectDynamicProxy extends DynamicProxy {public static <T> T newProxyInstance(Subject subject) {// 獲得classLoaderClassLoader classLoader = subject.getClass().getClassLoader();// 獲得接管數組Class<?>[] interfaces = subject.getClass().getInterfaces();//獲得handlerMyInvocationHandler myInvocationHandler = new MyInvocationHandler(subject);return newProxyInstance(classLoader, interfaces, myInvocationHandler);} }interface IAdvice {void exec(); }class BeforeAdvice implements IAdvice {@Overridepublic void exec() {System.out.println("我是前置通知,我被通知了");} } public class AOPExample {public static void main(String[] args) {Subject realSubject = new RealSubject(); // MyInvocationHandler myInvocationHandler = new MyInvocationHandler(realSubject); // // Subject proxy = DynamicProxy.newProxyInstance(realSubject.getClass().getClassLoader(), // realSubject.getClass().getInterfaces(), myInvocationHandler);Subject proxy = SubjectDynamicProxy.newProxyInstance(realSubject);proxy.doSomething("finish");} }在這里實現的動態代理的首要條件是,代理類必須是一個接口,當然現在還有很多技術如CGBLIB 可以實現不需要接口也可以實現動態代理
狀態機模式
下面以例子來一步一步的說明狀態的設計模式
//電梯接口 interface ILift {void open();void close();void run();void top(); }class Lift implements ILift {@Overridepublic void open() {System.out.println("電梯門打開");}@Overridepublic void close() {System.out.println("電梯門關閉");}@Overridepublic void run() {System.out.println("電梯運行起來");}@Overridepublic void top() {System.out.println("電梯停了");} }public class StateModel {public static void main(String[] args) {Lift lift = new Lift();lift.open();lift.close();} }上面的代碼是一個人人都會寫的電梯運行程序,但是這個程序也會有問題的:
- 電梯門不是隨時都能打開的
2. 閉門狀態
電梯門關閉了這個狀態下可能運行的動作是開門(下電梯),停止(忘記按路層了),運行3.運行狀態
電梯正在跑,在這個狀態下電梯只能做的是停止4. 停止狀態
電梯停止不動,這個狀態下電梯有繼續運行和開門的操作電梯的這4個狀態,正確的應該是每一次動作的發生都應該對狀態進行判斷,判斷是否可以執行動作,而在上面的實現中并沒有做任何前置條件,所以這4個方法是不能為外部類調用的(外部類可能會調任何一個)
解決這個問題使用switch case 是一個辦法
//電梯接口 interface ILift {// 在interface里面的變量都是 public static final 的int OPENNING_STATE = 1;int CLOSING_STATE = 2;int RUNNING_STATE = 3;int STOPPING_STATE = 4;void setState(int state);void open();void close();void run();void stop(); }class Lift implements ILift {private int state;@Overridepublic void setState(int state) {this.state = state;}@Overridepublic void open() {switch (this.state) {// 以下的狀態下開門的操作case OPENNING_STATE:break;case CLOSING_STATE:this.openWithoutLogic();this.setState(OPENNING_STATE);break;case RUNNING_STATE:break;case STOPPING_STATE:this.openWithoutLogic();this.setState(OPENNING_STATE);break;}}@Overridepublic void close() {switch (this.state) {// 以下的狀態下關門的操作case OPENNING_STATE:this.closeWithoutLogic();this.setState(CLOSING_STATE);break;case CLOSING_STATE:break;case RUNNING_STATE:break;case STOPPING_STATE:break;}}@Overridepublic void run() {switch (this.state) {// 以下的狀態下電梯運行的操作case OPENNING_STATE:break;case CLOSING_STATE:this.runWithoutLogic();this.setState(RUNNING_STATE);break;case RUNNING_STATE:break;case STOPPING_STATE:this.runWithoutLogic();this.setState(RUNNING_STATE);break;}}@Overridepublic void stop() {switch (this.state) {// 以下的狀態下電梯停止的操作case OPENNING_STATE:break;case CLOSING_STATE:this.stopWithoutLogic();this.setState(STOPPING_STATE);break;case RUNNING_STATE:this.stopWithoutLogic();this.setState(STOPPING_STATE);break;case STOPPING_STATE:break;}}private void stopWithoutLogic() {System.out.println("電梯停止了");}private void runWithoutLogic() {System.out.println("電梯開始運行");}private void openWithoutLogic() {System.out.println("電梯門打開");}private void closeWithoutLogic() { System.out.println("電梯門關閉"); } }public class StateModel {public static void main(String[] args) {Lift lift = new Lift();lift.setState(ILift.RUNNING_STATE);lift.stop();} }但是這里新的實現方式也是有問題的
以上是新的設計,Context 是一個環境角色,它的作用是串聯各個狀態的過度,在LiftState 抽象類中我們定義并把這個環境角色聚合起來,并傳遞到子類,也就是4個具體的實現類中歐過自己根據環境來決定如何進行狀態的過度。
通過實現這個代碼,通過各個子類來實現,并且每個子類代碼都比較短,也沒有在使用switch case 語句。這也復合開閉原則,如果我們要增加狀態,那么修改原來的類,但是這里的修改是增加,而不是在原來的基礎上修改。最后現在各個狀態是單獨的類,只有與這個狀態有關的因素修改了,這個類才修改,復合迪米特法則。
這就是狀態模式
當一個對象內在狀態改變的時候允許其改變行為,這個對象看起來像改變了其類。狀態模式的核心就是封裝,狀態的變更引起了行為的變更,從外部看起來就好像這個對象對應的類發生了改變一樣。狀態模式中的3個主要角色
- State 抽象狀態角色
接口或者抽象類,負責對對象狀態的定義,并且封裝環境角色以實現狀態切換
- ConcretState 具體狀態角色
每一個具體狀態必須完成兩個職責,本狀態的行為管理以及趨向狀態處理,通俗低說,就是本狀態下要做的事情,以及本狀態如何過度到其它狀態。
- Context 環境角色
定義客戶端需要的接口,并且負責具體狀態的切換,
狀態模式相對來說畢竟復雜,它提供了一種對物質運行的另一個觀察視角,通過狀態變化促使行為的變化,就類似水的狀態一樣,一碗水的初始狀態是液態,通過加熱變為氣態,狀態的改變同時也引起了體積的擴大,然后產生一種新的行為,鳴笛或者頂起壺蓋。
class Context1 {public static final State STATE1 = new ConcretState();public static final State STATE2 = new ConcretState2();private State currentState;public void setCurrenttState(State state) {this.currentState = state;this.currentState.setContext(this);}// 行為委托public void handle1(){this.currentState.handle1();}public void handle2(){this.currentState.handle2();}}抽象環境角色 abstract class State {環境變量,提供子類訪問protected Context1 context;設置環境角色public void setContext(Context1 context) {this.context = context;}行為1abstract void handle1();行為2abstract void handle2(); }class ConcretState extends State {@Overridevoid handle1() {// 本狀態下必須處理的邏輯}@Overridevoid handle2() {//設置當前狀態為 state2super.context.setCurrenttState(Context1.STATE2);// 過度到 state2 狀態,由Context 實現super.context.handle2();} }class ConcretState2 extends State {@Overridevoid handle1() {//設置當前狀態為 state1super.context.setCurrenttState(Context1.STATE1);// 過度到 state2 狀態,由Context 實現super.context.handle1();}@Overridevoid handle2() {// 本狀態下必須處理的邏輯 } }public class StateModel {public static void main(String[] args) {Context context = new Context();context.setLiftState(new ConcretState1());context.handle1();context.handle2();} }具體環境角色有兩個職責,處理本狀態必須完成的任務,決定是否可以過度到其它狀態,在環境變量中有2個不成文的約束
- 將狀態變量對象申明為靜態常量,有幾個狀態對象就幾個狀態變量
- 環境角色具有狀態抽象定義的多有行為,具體執行使用委托方式
而在使用的過程中,已經隱藏了狀態的變化過程,它的切換引起了行為的變化,對外來說,只看到行為發生變化,而不用知道是狀態變化引起的。
- 狀態模式有點:結構清晰,封裝性非常好,遵循設計原則
- 當然也會有缺點:子類太多,也就是會子類膨脹
使用場景
- 行為隨狀態改變而改變的場景
- 條件,分支判斷語句的替代者
總結
以上是生活随笔為你收集整理的状态机设计模式_设计模式-代理/状态机模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 记录每次更新到仓库 —— Git 学习笔
- 下一篇: 查看提交历史 —— Git 学习笔记 1