7

浅谈设计模式(一):状态模式|外观模式|代理模式

 3 years ago
source link: https://zhuanlan.zhihu.com/p/95989704
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

浅谈设计模式(一):状态模式|外观模式|代理模式

广发证券 技术工程师

计划开一个设计模式的系列,介绍常见的几种设计模式,本文涉及的设计模式包含以下6种

  • 状态模式:state pattern
  • 外观模式:facade pattern
  • 代理模式:proxy pattern

项目github

https://github.com/penghuwan/Design-Pattern​github.com

备注:下文适合看过《海贼王》的人阅读,没看过海贼王的观众请在父母陪同下阅读

状态模式:state pattern

在状态模式的设计方案里,一个主类(称为context类),可以在内部状态变化的时候一次性改变它的「所有行为」,而这个「所有行为」会被我们聚合到不同的类(state1,state2,state3)里面去。

这个内部状态我们可以理解为一个可以手动设置的state变量,设置它可以让context内部的state1切换为state2,或者是从state2切换为state3。

这么做,相比起传统的代码逻辑会发生什么变化呢?在传统的代码里,我们可能会在每个方法下,都写一大段if-else的状态判断逻辑里,然后对不同状态分别做处理,这个时候代码非常松散,不利于阅读和扩展,所以我们选择以「状态」为依据, 把这些if-else的每一部分都「聚合」到不同的状态(不同的state类)里面去,然后通过一个主类(context),去统一维护和管理。这样,逻辑上就清晰了很多,也大大降低了维护和扩展的难度。

举个栗子

草帽路飞,是热血漫《海贼王》的主角,像其他许多同类型的作品一样,主角有自己不同层次战斗的状态,进化过程如下所示

  1. 二档:加速血液的流动,大幅提高速度和身体强度,代表大招是「橡胶Jet火箭炮」(拳)和 「橡胶Jet」(踢)
  2. 三档:向橡胶的身体吹入空气,使身体变成巨人,攻击力大增。代表大招是「橡胶巨人火箭炮」(拳)和「橡胶巨人战斧」(踢)
  3. 四档:将武装色霸气和橡胶果实融合,攻击和速度再次强化,代表大招是「橡胶狮子火箭炮」(拳)和「橡胶犀牛榴弹炮」(踢)

下图描述的是主角路飞初次进化为「二档」的历史性时刻

我们发现,主角路飞拥有不同的战斗状态:二档,三档,四档,并且大招的使用是类似的,无非就是用拳头还是用脚踢的问题,但是攻击力和招式上都不同,我们可以根据这个状态的统一性抽象一个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类去管理,在这个类里面,它有两个功能

  1. 随时切换状态
  2. 代理调用状态类的方法
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

外观模式很简单且容易理解,但理解之后却非常有用。

说白了就是:把不同类的不同接口,统一代理到一个类里面对外输出,使代码具有良好的封装性。

举个栗子

咱们还是拿海贼王的一个情境举个例子

比如说,在海贼王367里,草帽海贼团 VS 巨人僵尸奥兹 的时候,索隆,山治,佛兰奇,乌索普和乔巴使用了一招非常精(you)彩(zhi)的技能:合体-大皇帝。

也就是说,合体后的草帽海贼团,在能够使用每个人的绝招的同时,是作为“大皇帝”这个整体对外暴露的。

v2-89f602a9ec39a190bfd9620b97988dce_b.jpg

我们使用外观模式去实现的话,代码逻辑如下所示

首先每个成员我们用一个类去表示

// 索隆
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)去实现

举个栗子

不好意思,这里还是用我熟悉的海贼王打个比方,在七武海-多佛朗明哥刚刚出场的时候,他就用线线果实提供的能力,操控两名海军自相残杀。

如果我们把海军抽象为一个类的话,那么多佛朗明哥就是「海军类」的代理类了,实际上我们发现

  • 实质出手伤人的并不是海军,而是多佛朗明哥,也就是代理类掌握了真正的控制权
  • 直接出手伤人的仍然是海军,也就是代理类仍然调用的是原类的接口
v2-2d6a5378bc730000a03850fd1870dff0_b.jpg

代码如下

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();
    }
}

输出

v2-7ee6460acd1d1dd3e27ae4362626e926_720w.jpg


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK