You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

C#中如何正确编写可复用类?多游戏状态类的深拷贝与方法调用设计困惑

解决方案:类型安全的多游戏状态抽象

看起来你已经找对了方向——用接口+抽象类来实现多游戏的状态管理,但确实在返回类型和多态实现上踩了几个小坑。我来帮你梳理下问题,然后给出一个更健壮的实现方案。

首先,咱们先拆解下你当前代码里的几个核心问题:

  1. Game2的方法实现错误:你没有用override关键字重写父类的make_moveduplicate,这会导致这些方法实际上是子类的新方法(隐藏了父类方法),而非多态实现,调用时会触发父类的空逻辑。
  2. 返回类型丢失子类信息duplicate返回GameState类型,拷贝后的对象无法直接调用子类的特有方法,还需要强制转换,既麻烦又不安全。
  3. 接口设计不够灵活:非泛型的接口无法在编译期保证返回类型的一致性。

推荐实现方案:泛型接口+抽象类

这个方案既能保证多态性,又能在编译期确保类型安全,完美适配你支持多游戏的需求:

1. 定义泛型接口

用泛型约束让接口的Duplicate方法返回具体的子类类型:

public interface IGameState<T> where T : IGameState<T>
{
    // 执行移动操作
    void MakeMove();
    // 创建当前对象的深拷贝,返回子类类型
    T Duplicate();
}

2. 抽象基类统一基础逻辑

创建抽象类实现接口,让子类只需要关注自身游戏的特有逻辑:

public abstract class GameState<T> : IGameState<T> where T : GameState<T>
{
    // 抽象方法,强制子类实现移动逻辑
    public abstract void MakeMove();
    // 抽象方法,强制子类实现深拷贝
    public abstract T Duplicate();
}

3. 实现具体游戏类

每个游戏类继承抽象基类,重写方法并返回自身类型:

public class OthelloGame : GameState<OthelloGame>
{
    // 示例:奥赛罗的游戏状态字段
    public int[,] Board { get; private set; }
    public bool IsBlackTurn { get; private set; }

    public OthelloGame()
    {
        Board = new int[8,8];
        // 初始化奥赛罗棋盘...
    }

    public override void MakeMove()
    {
        // 奥赛罗的移动逻辑:翻转棋子、切换回合等
        Console.WriteLine("奥赛罗执行移动,切换回合");
        IsBlackTurn = !IsBlackTurn;
    }

    public override OthelloGame Duplicate()
    {
        // 深拷贝:复制所有状态字段
        var copy = new OthelloGame();
        // 深拷贝二维数组(浅拷贝会导致引用共享)
        for (int i = 0; i < 8; i++)
        {
            for (int j = 0; j < 8; j++)
            {
                copy.Board[i,j] = this.Board[i,j];
            }
        }
        copy.IsBlackTurn = this.IsBlackTurn;
        return copy;
    }
}

public class TicTacToeGame : GameState<TicTacToeGame>
{
    public char[] Board { get; private set; }
    public char CurrentPlayer { get; private set; }

    public TicTacToeGame()
    {
        Board = new char[9];
        CurrentPlayer = 'X';
    }

    public override void MakeMove()
    {
        // 井字棋的移动逻辑:切换玩家等
        Console.WriteLine("井字棋执行移动,切换玩家");
        CurrentPlayer = CurrentPlayer == 'X' ? 'O' : 'X';
    }

    public override TicTacToeGame Duplicate()
    {
        var copy = new TicTacToeGame();
        // 深拷贝数组
        Array.Copy(this.Board, copy.Board, 9);
        copy.CurrentPlayer = this.CurrentPlayer;
        return copy;
    }
}

4. 通用方法调用

修改some_method为泛型方法,接受任意实现了IGameState<T>的对象,无需强制转换:

public void ProcessGameState<T>(T state) where T : IGameState<T>
{
    // 创建深拷贝,类型为具体游戏类
    T stateCopy = state.Duplicate();
    // 对拷贝执行移动操作,原对象不受影响
    stateCopy.MakeMove();
    
    // 这里可以直接调用子类的特有方法(如果有的话),无需强制转换
    // 比如:stateCopy.SomeOthelloSpecificMethod();
}

调用时直接传入具体游戏对象即可:

var othello = new OthelloGame();
var ticTacToe = new TicTacToeGame();

ProcessGameState(othello);
ProcessGameState(ticTacToe);

为什么这个方案更好?

  • 类型安全Duplicate返回具体子类类型,编译期就能检查类型错误,避免运行时强制转换的风险。
  • 多态性保障:所有游戏类都遵循统一的接口规范,确保ProcessGameState能处理任意游戏状态。
  • 扩展性强:新增游戏时只需要继承GameState<T>并实现两个方法即可,无需修改现有代码。

备选方案:非泛型接口(协变返回)

如果不想用泛型,C#支持协变返回类型,你可以让接口返回IGameState,子类返回自身类型:

public interface IGameState
{
    void MakeMove();
    IGameState Duplicate();
}

public abstract class GameState : IGameState
{
    public abstract void MakeMove();
    public abstract IGameState Duplicate();
}

public class OthelloGame : GameState
{
    public override void MakeMove() { /*...*/ }
    // 协变返回:子类可以返回更具体的类型
    public override OthelloGame Duplicate() { /*...*/ }
}

这种方案更简洁,但如果需要在通用方法里调用子类特有方法,还是需要强制转换,适合不需要子类特有的场景。

内容的提问来源于stack exchange,提问作者33fred33

火山引擎 最新活动