状态设计模式

29 阅读3分钟

解决什么问题?

实际开发中,面对符合状态转移的场景(**状态遇到相应事件会执行相应动作并转移到下一个状态) 比如:超级马力奥的游戏,会有小马里奥,超级马里奥,火焰马力奥,斗篷马力奥等多种状态。小马力奥(状态)分别在遇到不同的事件(比如吃了蘑菇)会变成超级马里奥(下一个状态),并对应触发增加100积分(动作)

image.png E1: 吃了蘑菇 E2: 获得斗篷 E3:获得火焰 E4:遇到怪物

设计方式

package org.example.statusPattern;

/**
 *   Mario 的状态
 */
public enum MarioStatus {
    MarioSmall(0),
    MarioSuper(1),
    MarioFire(2),
    MarioCape(3);

    private final int status;

    MarioStatus(int status) {
        this.status = status;
    }

    public int getStatus() {
        return status;
    }
}

/**
 * 事件
 */
public enum MarioEvent {
    ObtainMushRoom(0),
    ObtainCape(1),
    ObtainFireFlower(2),
    MeetMonster(3);

    private int value;

    MarioEvent(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

分支逻辑法

package org.example.statusPattern;

public class LoginMarioMachine {
    private MarioStatus status;
    private int score;

    public LoginMarioMachine() {
        this.status = MarioStatus.MarioSmall;
        this.score = 0;
    }

    public void obtainMushRoom(){
        if(status == MarioStatus.MarioSmall){
            status = MarioStatus.MarioSuper;
            score += 100;
        }
    }

    public void obtainFireFlower(){
        if(status == MarioStatus.MarioSmall || status == MarioStatus.MarioSuper){
            status = MarioStatus.MarioFire;
            score += 300;
        }
    }

    public void meetMonster(){
        if(status == MarioStatus.MarioCape){
            score -= 200;
            status = MarioStatus.MarioSmall;
        }
        if(status == MarioStatus.MarioSuper){
            score -= 100;
            status = MarioStatus.MarioSmall;
        }
        if(status == MarioStatus.MarioFire){
            score -= 300;
            status = MarioStatus.MarioSmall;
        }

    }

    public void obtainCape(){
        if(status == MarioStatus.MarioSmall || status == MarioStatus.MarioSuper){
            score += 200;
            status = MarioStatus.MarioCape;
        }
    }

}

查表法

package org.example.statusPattern;

public class TableMarioMachine {

    private MarioStatus marioStatus;
    private int marioScore;

    public TableMarioMachine(MarioStatus marioStatus, int marioScore) {
        this.marioStatus = marioStatus;
        this.marioScore = marioScore;
    }

    private static MarioStatus[][] status = new MarioStatus[][]{
            {
                MarioStatus.MarioSuper, MarioStatus.MarioCape, MarioStatus.MarioFire, MarioStatus.MarioSmall
            },
            {
                MarioStatus.MarioSuper, MarioStatus.MarioCape, MarioStatus.MarioFire, MarioStatus.MarioSmall
            },
            {
                MarioStatus.MarioFire, MarioStatus.MarioFire, MarioStatus.MarioFire, MarioStatus.MarioSmall
            },
            {
                MarioStatus.MarioCape, MarioStatus.MarioCape, MarioStatus.MarioCape, MarioStatus.MarioSmall
            }
    };

    private static int[][] score = new int[][]{
            {
                100, 200, 300, 0
            },
            {
                0, 200, 300, -100
            },
            {
                0, 0, 0, -300
            },
            {
                0, 0, 0, -200
            }
    };

    public void obtainMushRoom(){
        changeStatus(MarioEvent.ObtainMushRoom);
    }

    public void obtainFireFlower(){
        changeStatus(MarioEvent.ObtainFireFlower);
    }

    public void meetMonster(){
        changeStatus(MarioEvent.MeetMonster);
    }

    public void obtainCape(){
        changeStatus(MarioEvent.ObtainCape);
    }

    private void changeStatus(MarioEvent event){
        marioScore += score[marioStatus.getStatus()][event.getValue()];
        marioStatus = status[marioStatus.getStatus()][event.getValue()];
    }
}

状态模式1

package org.example.statusPattern.statusObject;

public interface IMarioBehave {

     void obtainMushRoom(StatusMarioMachine statusMarioMachine);

     void obtainFireFlower(StatusMarioMachine statusMarioMachine);

     void meetMonster(StatusMarioMachine statusMarioMachine);

     void obtainCape(StatusMarioMachine statusMarioMachine);
}

package org.example.statusPattern.statusObject;

public class SmallMario implements IMarioBehave {

    @Override
    public void obtainMushRoom(StatusMarioMachine statusMarioMachine) {
        statusMarioMachine.setMarioBehave(new SuperMario());
        statusMarioMachine.setMarioScore(statusMarioMachine.getMarioScore() + 100);
    }
    @Override
    public void obtainCape(StatusMarioMachine statusMarioMachine) {
        statusMarioMachine.setMarioBehave(new CapeMario());
        statusMarioMachine.setMarioScore(statusMarioMachine.getMarioScore() + 200);
    }

    @Override
    public void obtainFireFlower(StatusMarioMachine statusMarioMachine) {
        statusMarioMachine.setMarioBehave(new FireMario());
        statusMarioMachine.setMarioScore(statusMarioMachine.getMarioScore() + 300);
    }

    @Override
    public void meetMonster(StatusMarioMachine statusMarioMachine) {

    }

}

package org.example.statusPattern.statusObject;

public class StatusMarioMachine {

    private IMarioBehave marioBehave;
    private int marioScore;

    public StatusMarioMachine() {
        marioBehave = new SmallMario();
        marioScore = 0;
    }

    public IMarioBehave getMarioBehave() {
        return marioBehave;
    }

    public void setMarioBehave(IMarioBehave marioBehave) {
        this.marioBehave = marioBehave;
    }

    public int getMarioScore() {
        return marioScore;
    }

    public void setMarioScore(int marioScore) {
        this.marioScore = marioScore;
    }

    public void obtainMushRoom(){
        marioBehave.obtainMushRoom(this);
    }

    public void obtainFireFlower(){
        marioBehave.obtainFireFlower(this);
    }

    public void meetMonster(){
        marioBehave.meetMonster(this);
    }

    public void obtainCape(){
        marioBehave.obtainCape(this);
    }
}

改进,上述的 SmallMario 等可以用个工厂模式给它存起来

反思

对于较为简单的状态转移,比如超级马力游戏,适合用查表法,但是对于一个状态转移伴随着很复杂的操作,比如连接数据库等,那么应该采用状态模式,将其进行很好的代码分离。

在我的项目中,有关边缘任务卸载问题,其实之前是一个分支逻辑法实现的状态模式,可以将其改装为状态模式。

# 轮流判断任务的七个阶段
if task.status == TaskStatus.BIRTH_PERIOD:
    """
        (1)处理期:(诞生期)判断用户应该将任务卸载到本地还是云上
            处理原则: 若将该任务放上本地设备,使其能满足时延并且其他本满足时延要求的任务并没有因此而有延迟, 则可以放入
                    卸载到用户设备/ 传输给服务器运行
    """
    policy = OffloadingAlgorithm.offloadingInLocalOrServers(Manage.users[task.userId], task)
    if policy == 1:
        # 决策阶段: 卸载到本地设备
        # 做出卸载决策 则不需要将任务放到轮转队列中执行
        task.status = TaskStatus.EXECUTION_PERIOD

    else:
        # 决策阶段: 传输到家服务器
        task.status = TaskStatus.TRANSPORT_TO_HOME_SERVER
        # 计算到达家服务器的 breakPoint 点: 无线电传输时延 + 家Ap 跳到 家server的时间
        # (1)无线点传输速率
        u = Manage.users[task.userId]
        usert = len(Manage.aps[u.apId].users)
        # Mb / s
        Rt = Constants.B * math.log(1 + Constants.g0 * (
                (
                            Constants.d0 / u.di) ** Constants.xita) * Constants.maxP / usert / Constants.N0 / Constants.B,
                                    2)
        Manage.maxRn = max(Manage.maxRn, Rt)
        # (2)家Ap 跳到 家server的时间
        apId = Manage.users[task.userId].apId
        # 进行到家家服务器的时延计算
        task.breakPoint = MathUtil.fracTruncation3(task.breakPoint + task.a / Rt + (
                    Manage.serverAps[apId][task.homeServerId] - 1) * task.a / Constants.ap2ap)
        # 放入排序队列
        Manage.maxBreakPoint = max(Manage.maxBreakPoint, task.breakPoint - task.startTime)
        heapq.heappush(heap, task)

elif task.status == TaskStatus.TRANSPORT_TO_HOME_SERVER:
    # 到达家服务器 进入卸载决策阶段
    task.status = TaskStatus.DECISION_PERIOD
    heapq.heappush(heap, task)

elif task.status == TaskStatus.DECISION_PERIOD:
    # 执行卸载决策: 决定往哪个服务器卸载
    # print("卸载决策")
    if task.taskId in sset:
        print("111")
        exit(1)
    sset.add(task.taskId)
    servers = Manage.servers
    Manage.homeServerIds.add(task.homeServerId)
    res = OffloadingAlgorithm.offloadingInWhichServers(Manage.users[task.userId], task)

    # # 卸载在云服务器上
    if res == 0:
        # 将任务卸载到云服务器上 进行相关任务的保存  以便后面存入excel
        Manage.cloudServerOffloadTasks.append([task.userId, task.id, task.t])

参考文章:设计模式之美