白话设计模式之(95):状态模式——优化代码状态管理的利器

白话设计模式之(95):状态模式——优化代码状态管理的利器

大家好!在软件开发的学习过程中,我们都在不断探索如何让代码更加高效、灵活且易于维护。设计模式作为编程领域的重要工具,为我们解决各种复杂问题提供了有效的方案。今天,咱们深入探讨状态模式,它是一种优化代码状态管理的利器,能帮助我们处理对象在不同状态下的复杂行为。希望通过这篇博客,能和大家一起全面掌握状态模式,从基础概念到实际应用,深入理解其原理和优势,在实际编程中灵活运用,让代码更加简洁、健壮。

一、写作初衷

在软件开发的实际工作中,我们常常会遇到对象行为依赖于其内部状态的情况。就像文档中提到的在线投票功能,用户根据投票次数处于不同状态,系统需要执行不同操作。如果采用传统的编程方式,使用大量的条件判断语句来处理这些不同状态下的行为,代码会变得冗长、复杂且难以维护。一旦业务规则发生变化,比如增加新的状态或修改现有状态的处理逻辑,就需要在众多条件判断中进行修改,这不仅容易出错,还会增加开发和维护的成本。状态模式正是为解决这类问题而诞生的。它通过将不同状态下的行为封装到独立的类中,使得代码结构更加清晰,易于扩展和维护。我希望通过分享这篇博客,能和大家一起深入学习状态模式,从基础概念到实际应用中的各种细节,全面掌握这一模式,让大家在面对类似的复杂业务场景时能够游刃有余,编写出更优质的代码。

二、状态模式解析

(一)定义与概念

状态模式的定义是:允许一个对象在其内部状态改变时改变它的行为。简单来说,想象一个手机,它有开机、关机、静音、飞行模式等不同状态。在开机状态下,手机可以正常接打电话、收发短信;在静音状态下,来电不会有铃声提示;在飞行模式下,所有通信功能都会被关闭。在编程中,状态模式就是把对象在不同状态下的行为分别封装到不同的类中,当对象的状态发生变化时,它的行为也会相应地改变,就好像对象“变身”了一样。

(二)代码示例

为了让大家更直观地理解,我们以一个简单的游戏角色状态系统为例。游戏中的角色有不同的状态,如正常状态、中毒状态、隐身状态等,在不同状态下角色的行为有所不同。

首先定义一个状态接口:

// 角色状态接口
public interface CharacterState {
    void performAction();
}

接着创建正常状态类,实现状态接口:

// 正常状态类
public class NormalState implements CharacterState {
    @Override
    public void performAction() {
        System.out.println("角色处于正常状态,可以正常移动、攻击");
    }
}

再创建中毒状态类:

// 中毒状态类
public class PoisonedState implements CharacterState {
    @Override
    public void performAction() {
        System.out.println("角色处于中毒状态,持续掉血,移动速度减慢");
    }
}

然后创建隐身状态类:

// 隐身状态类
public class InvisibleState implements CharacterState {
    @Override
    public void performAction() {
        System.out.println("角色处于隐身状态,敌人无法看到,攻击有额外加成");
    }
}

最后创建角色类,在其中使用状态模式:

// 角色类
public class Character {
    private CharacterState state;

    public Character(CharacterState state) {
        this.state = state;
    }

    public void setState(CharacterState state) {
        this.state = state;
    }

    public void executeAction() {
        state.performAction();
    }
}

在客户端代码中使用状态模式:

public class Client {
    public static void main(String[] args) {
        // 创建处于正常状态的角色
        Character character = new Character(new NormalState());
        character.executeAction();

        // 角色进入中毒状态
        character.setState(new PoisonedState());
        character.executeAction();

        // 角色进入隐身状态
        character.setState(new InvisibleState());
        character.executeAction();
    }
}

在这个示例中,CharacterState接口定义了角色状态的行为方法。不同的状态类(如NormalStatePoisonedStateInvisibleState)实现了这个接口,提供了具体的状态行为逻辑。Character类通过持有一个CharacterState接口的实例,在executeAction方法中调用该实例的performAction方法,实现了根据角色不同状态执行不同行为的功能。客户端可以通过修改角色的状态对象,轻松切换角色的行为。

(三)应用场景

  1. 对象行为依赖状态的场景:在很多业务场景中,对象的行为会根据其内部状态的改变而发生变化。比如文档中的在线投票功能,用户根据投票次数的不同状态有不同的处理方式;再比如游戏中的角色状态,如前面代码示例中的正常、中毒、隐身状态等。
  2. 状态转换复杂且频繁的场景:当状态之间的转换频繁,并且每个状态下的处理逻辑较为复杂时,使用状态模式可以将复杂的逻辑分散到各个状态类中,使代码结构更加清晰。例如在一个电梯控制系统中,电梯有上升、下降、停止、开门、关门等不同状态,每个状态下的行为和状态转换都有特定的逻辑,使用状态模式可以更好地管理这些逻辑。
  3. 避免大量条件判断的场景:如果在代码中存在大量的条件判断语句来处理不同状态下的行为,代码会变得冗长且难以维护。状态模式可以将这些条件判断逻辑封装到具体的状态类中,减少代码中的条件判断,提高代码的可读性和可维护性。

(四)状态模式的结构与角色

  1. Context(环境类):也称为上下文类,通常用来定义客户感兴趣的接口,同时维护一个来具体处理当前状态的实例对象。在游戏角色示例中,Character类就是环境类,它定义了executeAction方法供外部调用,并且持有一个CharacterState实例来表示当前角色的状态。
  2. State(状态接口):用来封装与上下文的一个特定状态所对应的行为。所有具体的状态类都必须实现这个接口。在游戏角色示例中,CharacterState接口就是状态接口,它定义了performAction方法,规定了所有具体状态类必须实现的行为。
  3. ConcreteState(具体状态类):实现状态接口,每个类实现一个跟上下文相关的状态的具体处理。在游戏角色示例中,NormalStatePoisonedStateInvisibleState等类都是具体状态类,它们分别实现了角色在不同状态下的具体行为。

(五)状态模式的优缺点

  1. 优点
    • 提高代码的可维护性:将不同状态下的行为封装到独立的类中,使得代码结构更加清晰。当需要修改某个状态下的行为时,只需要修改对应的具体状态类,而不会影响其他状态类和整个系统的其他部分,提高了代码的可维护性。
    • 增强代码的扩展性:增加新的状态非常方便,只需要创建一个新的具体状态类并实现状态接口即可,不需要修改现有代码的核心逻辑。例如在游戏角色状态系统中,如果要添加一个“眩晕状态”,只需要创建一个StunnedState类并实现CharacterState接口,然后在需要的地方切换角色状态即可,对原有代码的改动较小。
    • 避免复杂的条件判断:通过状态模式,将复杂的条件判断逻辑封装到具体状态类中,减少了代码中大量的if - elseswitch语句,使代码更加简洁、易读。
  2. 缺点
    • 增加系统复杂度:对于简单的业务场景,使用状态模式可能会增加系统的复杂度。因为需要定义状态接口、多个具体状态类以及环境类,增加了类的数量和代码的层级结构。
    • 状态转换的管理可能复杂:在一些复杂的系统中,状态之间的转换可能有多种规则和条件,管理这些状态转换可能会变得复杂。如果状态转换逻辑不清晰,可能会导致代码难以理解和维护。

三、状态模式在实际场景中的应用——在线投票系统

(一)场景问题

在在线投票系统中,用户的投票行为会根据投票次数处于不同的状态,包括正常投票、重复投票、恶意投票以及进入黑名单等状态,每个状态下系统需要执行不同的操作,如记录投票、提示用户、取消投票资格、禁止登录等。传统的实现方式可能会在一个方法中使用大量的条件判断来处理这些不同状态下的操作,导致代码复杂且难以维护。

(二)不用模式的解决方案

如果不使用设计模式,可能会在一个类中通过大量的条件判断来处理投票逻辑,就像文档中给出的示例代码那样:

// 投票管理类(不用状态模式)
public class VoteManager {
    private Map<String, String> mapVote = new HashMap<>();
    private Map<String, Integer> mapVoteCount = new HashMap<>();

    public void vote(String user, String voteItem) {
        // 增加投票次数
        Integer oldVoteCount = mapVoteCount.get(user);
        if (oldVoteCount == null) {
            oldVoteCount = 0;
        }
        oldVoteCount = oldVoteCount + 1;
        mapVoteCount.put(user, oldVoteCount);

        // 判断投票类型并处理
        if (oldVoteCount == 1) {
            // 正常投票
            mapVote.put(user, voteItem);
            System.out.println("恭喜你投票成功");
        } else if (oldVoteCount > 1 && oldVoteCount < 5) {
            // 重复投票
            System.out.println("请不要重复投票");
        } else if (oldVoteCount >= 5 && oldVoteCount < 8) {
            // 恶意投票
            String s = mapVote.get(user);
            if (s != null) {
                mapVote.remove(user);
            }
            System.out.println("你有恶意刷票行为,取消投票资格");
        } else if (oldVoteCount >= 8) {
            // 黑名单
            System.out.println("进入黑名单,将禁止登录和使用本系统");
        }
    }
}

这种实现方式在投票规则简单时可能还能应付,但随着投票规则的增加和修改,比如增加新的投票状态(如投票超过8次但不足10次的特殊处理),代码中的条件判断会越来越复杂,维护和扩展的难度也会越来越大。

(三)使用状态模式的解决方案

  1. 定义投票状态接口
// 投票状态接口
public interface VoteState {
    void handleVote(String user, String voteItem, VoteContext context);
}

这个接口定义了处理投票的方法,不同的投票状态类将实现这个接口,提供具体的投票处理逻辑。

  1. 创建具体的投票状态类
    • 正常投票状态类
// 正常投票状态类
public class NormalVoteState implements VoteState {
    @Override
    public void handleVote(String user, String voteItem, VoteContext context) {
        context.getMapVote().put(user, voteItem);
        System.out.println("恭喜你投票成功");
    }
}
- **重复投票状态类**:
// 重复投票状态类
public class RepeatVoteState implements VoteState {
    @Override
    public void handleVote(String user, String voteItem, VoteContext context) {
        System.out.println("请不要重复投票");
    }
}
- **恶意投票状态类**:
// 恶意投票状态类
public class MaliciousVoteState implements VoteState {
    @Override
    public void handleVote(String user, String voteItem, VoteContext context) {
        context.getMapVote().remove(user);
        System.out.println("你有恶意刷票行为,取消投票资格");
    }
}
- **黑名单状态类**:
// 黑名单状态类
public class BlacklistVoteState implements VoteState {
    @Override
    public void handleVote(String user, String voteItem, VoteContext context) {
        System.out.println("进入黑名单,将禁止登录和使用本系统");
    }
}
  1. 创建投票上下文类
// 投票上下文类
public class VoteContext {
    private VoteState state;
    private Map<String, String> mapVote = new HashMap<>();
    private Map<String, Integer> mapVoteCount = new HashMap<>();

    public VoteContext(VoteState state) {
        this.state = state;
    }

    public void setVoteState(VoteState state) {
        this.state = state;
    }

    public void vote(String user, String voteItem) {
        Integer oldVoteCount = mapVoteCount.get(user);
        if (oldVoteCount == null) {
            oldVoteCount = 0;
        }
        oldVoteCount = oldVoteCount + 1;
        mapVoteCount.put(user, oldVoteCount);

        // 根据当前状态处理投票
        state.handleVote(user, voteItem, this);

        // 根据投票次数切换状态
        if (oldVoteCount == 1) {
            setVoteState(new NormalVoteState());
        } else if (oldVoteCount > 1 && oldVoteCount < 5) {
            setVoteState(new RepeatVoteState());
        } else if (oldVoteCount >= 5 && oldVoteCount < 8) {
            setVoteState(new MaliciousVoteState());
        } else if (oldVoteCount >= 8) {
            setVoteState(new BlacklistVoteState());
        }
    }

    public Map<String, String> getMapVote() {
        return mapVote;
    }
}

这个类持有一个VoteState接口的实例,通过vote方法调用具体状态类的handleVote方法来处理投票,并根据投票次数切换状态。

  1. 在客户端代码中使用状态模式
public class Client {
    public static void main(String[] args) {
        VoteContext voteContext = new VoteContext(new NormalVoteState());
        for (int i = 0; i < 8; i++) {
            voteContext.vote("u1", "A");
        }
    }
}

在客户端代码中,创建VoteContext实例并传入初始状态,然后模拟用户投票,VoteContext会根据投票次数自动切换状态并处理投票。

(四)对比分析

与不用状态模式的解决方案相比,使用状态模式的代码结构更加清晰。每个状态的处理逻辑都封装在独立的状态类中,VoteContext类只负责管理状态和调用相应状态的处理方法,避免了大量的条件判断集中在一个方法中。当需要修改某个状态下的处理逻辑时,只需要修改对应的状态类;当需要添加新的状态时,只需要创建新的状态类并实现接口,然后在VoteContext中添加状态切换逻辑即可,大大提高了代码的可维护性和扩展性。

四、总结

状态模式是一种非常实用的设计模式,在处理对象行为随状态变化的复杂业务场景中具有显著的优势。通过合理运用状态模式,我们可以将复杂的状态相关逻辑封装到独立的类中,实现代码的清晰化和模块化,提高代码的可维护性和扩展性。在实际开发中,我们要根据具体的业务需求和场景,选择合适的时机使用状态模式,充分发挥它的优势,同时注意避免其带来的问题。

写作不易,如果这篇文章对你有所帮助,希望大家能关注我的博客,点赞评论支持一下!你的每一个点赞、评论和关注都是对我最大的鼓励,我会持续为大家带来更多设计模式相关的优质内容,咱们下次再见!

你可能感兴趣的:(白话设计模式,设计模式,状态模式,ui)