1. 需求分析

在开发手游的过程中,后端系统往往需要处理大量的玩家请求和游戏逻辑。为了提高系统的灵活性和可维护性,采用设计模式进行架构设计是一种常见的做法。其中,命令模式(Command Pattern)因其能够将请求封装成对象,从而允许参数化不同的请求、队列化或记录请求日志,以及支持可撤销的操作等特点,在处理复杂的业务逻辑时显得尤为有用。 image.png

主要需求点:

  • 支持多种操作,如玩家登录、充值、购买道具等。
  • 能够灵活地添加新的操作而不影响现有系统。
  • 提供操作日志记录功能,以便于追踪和审计。
  • 支持事务回滚机制,确保数据一致性。

2. 架构思路

设计原则

  • 高内聚低耦合:每个命令类只负责执行特定的任务,减少模块间的依赖。
  • 易于扩展:通过定义统一的接口,使得新增命令变得简单。
  • 可维护性:通过记录操作日志,方便后期问题排查。

核心组件

  • Command接口:定义所有命令应该实现的方法。
  • 具体命令类:实现Command接口,封装具体的业务逻辑。
  • Invoker:请求的发起者,负责调用具体的命令。
  • Receiver:真正的执行者,执行与请求相关的操作。

3. 实施方案

步骤一:定义Command接口

public interface Command {
    void execute();
}

步骤二:创建具体命令类

以处理玩家登录为例:

public class LoginCommand implements Command {
    private Player player;

    public LoginCommand(Player player) {
        this.player = player;
    }

    @Override
    public void execute() {
        // 执行登录逻辑
        player.login();
        // 记录日志
        logOperation("Player " + player.getName() + " logged in.");
    }
}

步骤三:实现Invoker

public class GameServer {
    private List<Command> commands = new ArrayList<>();

    public void addCommand(Command command) {
        commands.add(command);
    }

    public void executeCommands() {
        for (Command command : commands) {
            command.execute();
        }
    }
}

步骤四:集成到系统中

// 创建接收者
Player player = new Player("Alice");
// 创建命令
Command loginCommand = new LoginCommand(player);
// 创建调用者并添加命令
GameServer server = new GameServer();
server.addCommand(loginCommand);
// 执行命令
server.executeCommands();

4. 案例分享

背景介绍

假设我们正在开发一款多人在线角色扮演游戏(MMORPG)。游戏中,玩家不仅可以与其他玩家互动,还可以购买和赠送各种虚拟物品。为了更好地管理这些交互行为,我们决定使用命令模式来实现玩家之间的礼物赠送功能。

功能需求

  • 玩家可以向其他玩家赠送礼物。
  • 系统需要记录赠送记录,以便后续查询和审计。
  • 如果赠送过程中出现问题(如库存不足、网络错误等),需要支持回滚操作。

设计方案

我们将通过以下步骤实现这一功能:

  1. 定义Command接口:定义一个通用的命令接口。
  2. 创建具体命令类:实现具体的赠送礼物逻辑。
  3. 实现Invoker:定义一个游戏服务器类,负责执行命令。
  4. 集成到系统中:将新功能集成到现有系统中,并确保其正常工作。

步骤一:定义Command接口

public interface Command {
    void execute();
}

步骤二:创建具体命令类

首先,我们需要定义一个Gift类来表示虚拟礼物,并且定义一个Player类来表示玩家。然后,创建一个具体的命令类GiftCommand来实现礼物赠送的逻辑。

Gift
public class Gift {
    private String name;
    private int quantity;

    public Gift(String name, int quantity) {
        this.name = name;
        this.quantity = quantity;
    }

    public String getName() {
        return name;
    }

    public int getQuantity() {
        return quantity;
    }
}
Player
public class Player {
    private String name;
    private Map<String, Integer> inventory;

    public Player(String name) {
        this.name = name;
        this.inventory = new HashMap<>();
    }

    public void addGift(Gift gift) {
        String giftName = gift.getName();
        int currentQuantity = inventory.getOrDefault(giftName, 0);
        inventory.put(giftName, currentQuantity + gift.getQuantity());
    }

    public boolean removeGift(Gift gift) {
        String giftName = gift.getName();
        int currentQuantity = inventory.getOrDefault(giftName, 0);
        if (currentQuantity >= gift.getQuantity()) {
            inventory.put(giftName, currentQuantity - gift.getQuantity());
            return true;
        }
        return false;
    }

    public String getName() {
        return name;
    }
}
GiftCommand
public class GiftCommand implements Command {
    private Player sender;
    private Player receiver;
    private Gift gift;

    public GiftCommand(Player sender, Player receiver, Gift gift) {
        this.sender = sender;
        this.receiver = receiver;
        this.gift = gift;
    }

    @Override
    public void execute() {
        if (sender.removeGift(gift)) {
            receiver.addGift(gift);
            logOperation(sender.getName() + " gifted " + gift.getName() + " to " + receiver.getName());
        } else {
            System.out.println("Insufficient quantity of " + gift.getName() + " in " + sender.getName() + "'s inventory.");
        }
    }

    private void logOperation(String message) {
        // 记录操作日志
        System.out.println(message);
    }
}

步骤三:实现Invoker

定义一个GameServer类来作为命令的调用者,并负责执行命令。

public class GameServer {
    private List<Command> commands = new ArrayList<>();

    public void addCommand(Command command) {
        commands.add(command);
    }

    public void executeCommands() {
        for (Command command : commands) {
            command.execute();
        }
    }
}

步骤四:集成到系统中

将新功能集成到现有系统中,并确保其正常工作。

public class Main {
    public static void main(String[] args) {
        // 创建玩家
        Player alice = new Player("Alice");
        Player bob = new Player("Bob");

        // 添加礼物
        Gift apple = new Gift("Apple", 5);
        alice.addGift(apple);

        // 创建命令
        Command giftCommand = new GiftCommand(alice, bob, apple);

        // 创建游戏服务器并添加命令
        GameServer server = new GameServer();
        server.addCommand(giftCommand);

        // 执行命令
        server.executeCommands();
    }
}

运行结果

运行上述代码后,输出如下:

这表明礼物成功从Alice转移到了Bob,并且系统记录了这一操作。

5. 注意事项

  • 在设计命令类时,应考虑到可能存在的并发问题,确保线程安全。
  • 对于复杂的应用场景,可能需要结合其他设计模式一起使用,例如观察者模式用于通知相关方操作结果。
  • 应合理设计命令接口,避免出现过多的具体命令类导致系统臃肿。

总结

通过这个案例,我们可以看到命令模式在实现玩家之间礼物赠送功能时的优势:

  • 清晰的职责划分:命令模式将请求封装成对象,使各个部分职责更加明确。
  • 易于扩展:如果需要增加新的功能,只需添加新的具体命令类即可。
  • 可维护性:通过记录操作日志,方便后期问题排查和审计。