You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#骑士锦标赛面向对象控制台应用代码评审请求

C#骑士锦标赛代码评审请求

我正在练习C#面向对象编程,开发了一个模拟骑士锦标赛(KnightTournament)的控制台应用。该应用包含武器基类Weapon与派生类(Sword、Lance、Club)、Knight类、Squire类,以及处理重复名称的自定义异常NameAlreadyExistsException,还实现了基于节点的参与者结构(ParticipantList类实现已省略)。现正式请求对代码进行评审,重点关注:

  1. 整体OOP设计与类结构
  2. 继承及virtual/override方法的合理使用
  3. C#最佳实践与命名规范

代码实现

using System;

namespace KnightTournament
{
    public abstract class Weapon
    {
        private string _designation;

        public Weapon(string designation)
        {
            this._designation = designation;
        }

        public string Designation
        {
            get { return _designation; }
        }

        public virtual string GetWeaponKind()
        {
            return "Unknown";
        }

        public override string ToString()
        {
            return _designation + " Kind: " + GetWeaponKind();
        }
    }
}

namespace KnightTournament
{
    public class Sword : Weapon
    {
        public Sword(string designation) : base(designation)
        {
        }

        public override string GetWeaponKind()
        {
            return "S";
        }
    }
}

namespace KnightTournament
{
    public class Knight
    {
        // Static variable to keep track of the next available ID
        // s_ stands for "static" and is a common convention for static fields
        // --> is a quasi-standard in the industry
        private static int s_nextId = 1;

        private int _id;
        private string _name;
        private string _phoneNumber;
        private string _nickname;
        private Squire _squire;
        private Weapon _weapon;

        public Knight(string name, string phoneNumber, string nickname)
        {
            this._id = s_nextId;
            s_nextId++;
            this._name = name;
            this._phoneNumber = phoneNumber;
            this._nickname = nickname;
            this._squire = null;
            this._weapon = null;
        }

        public int Id
        {
            get { return _id; }
        }

        public string Name
        {
            get { return _name; }
        }

        public string PhoneNumber
        {
            get { return _phoneNumber; }
        }

        public string Nickname
        {
            get { return _nickname; }
        }

        public void AddSquire(Squire squire)
        {
            this._squire = squire;
        }

        public void AddWeapon(Weapon weapon)
        {
            this._weapon = weapon;
        }

        public string GetWeaponKind()
        {
            if (_weapon != null)
            {
                return _weapon.GetWeaponKind();
            }
            return "";
        }

        public override string ToString()
        {
            string output = "Knight " + _id + "\t\tName: " + _name + "\t\tNickname: " + _nickname;

            if (_weapon != null)
            {
                output += "\n\t\tWeapon: " + _weapon.Designation + "\t\tKind: " + _weapon.GetWeaponKind();
            }

            if (_squire != null)
            {
                output += "\n\t\tSquire: " + _squire.ToString();
            }

            return output;
        }
    }
}

namespace KnightTournament
{
    internal class Program
    {
        static void Main(string[] args)
        {
            ParticipantList list = new ParticipantList();

            // Knight 1: August the Brave with a lance and a squire
            Knight knight1 = new Knight("August", "0664/1234567", "the Brave");
            knight1.AddWeapon(new Lance("Battle Lance"));
            knight1.AddSquire(new Squire("Craven", "0664/7654321", 20));
            list.AddParticipant(knight1);

            // Knight 2: Bernulf the Fast without a weapon and squire
            Knight knight2 = new Knight("Bernulf", "0664/1111111", "the Fast");
            list.AddParticipant(knight2);

            // Knight 3: Christoph the Loyal with a sword
            Knight knight3 = new Knight("Christoph", "0664/2222222", "the Loyal");
            knight3.AddWeapon(new Sword("Battle Sword"));
            list.AddParticipant(knight3);

            // Output all participants
            list.ListAllParticipants();

            Console.WriteLine();

            // Attempt to add a knight with an already existing name
            try
            {
                Console.WriteLine("Attempting to add a knight with an existing name again");
                Knight knight4 = new Knight("Christoph", "0664/3333333", "the Strong");
                list.AddParticipant(knight4);
            }
            catch (NameAlreadyExistsException ex)
            {
                Console.WriteLine("Exception: " + ex.Message);
            }

            Console.WriteLine();

            // Output all knights with a sword (Weapon kind "S")
            list.AllWithWeaponKind("S");

            //Console.ReadLine();
        }
    }
}

namespace KnightTournament
{
    public class Node
    {
        private Knight _data;
        private Node _next;

        public Node(Knight data)
        {
            this._data = data;
            this._next = null;
        }

        public Knight Data
        {
            get { return _data; }
            set { _data = value; }
        }

        public Node Next
        {
            get { return _next; }
            set { _next = value; }
        }
    }
}

namespace KnightTournament
{
    public class NameAlreadyExistsException : Exception
    {
        public NameAlreadyExistsException(string message) 
            : base(message + " already exists!")
        {
        }
    }
}

namespace KnightTournament
{
    public class Lance : Weapon
    {
        public Lance(string designation) : base(designation)
        {
        }

        public override string GetWeaponKind()
        {
            return "L";
        }
    }
}

namespace KnightTournament
{
    public class Squire
    {
        private string _name;
        private string _phoneNumber;
        private int _trainingLevel;

        public Squire(string name, string phoneNumber, int trainingLevel)
        {
            this._name = name;
            this._phoneNumber = phoneNumber;
            this._trainingLevel = trainingLevel;
        }

        public string Name
        {
            get { return _name; }
        }

        public string PhoneNumber
        {
            get { return _phoneNumber; }
        }

        public int GetTrainingLevel()
        {
            return _trainingLevel;
        }

        public override string ToString()
        {
            return _name + " Level: " + _trainingLevel;
        }
    }
}

namespace KnightTournament
{
    public class Club : Weapon
    {
        public Club(string designation) : base(designation)
        {
        }

        public override string GetWeaponKind()
        {
            return "C";
        }
    }
}

代码评审结果

1. 整体OOP设计与类结构

优点

  • 类职责划分清晰:Weapon作为抽象基类定义武器核心属性与行为,派生类实现具体武器类型;Knight、Squire、Node、自定义异常各司其职,符合单一职责原则。
  • 依赖关系合理:Knight关联Squire和Weapon,Node封装Knight数据,贴合现实业务逻辑。

可优化点

  • 扩展性限制:Node类直接绑定Knight,若未来需添加其他参与者类型(如魔法师),无法复用该节点结构。建议改为泛型Node<T>,提升复用性。
  • 关联关系明确化:Knight的AddSquire和AddWeapon是直接赋值逻辑,意味着骑士只能拥有1个侍从和1件武器。若业务允许多个,需改为集合(如List<Squire>);若需求就是一对一,建议添加注释说明设计意图。
  • 属性封装一致性:Squire的GetTrainingLevel方法可改为只读属性TrainingLevel,更符合C#面向对象的封装习惯。

2. 继承及virtual/override方法的合理使用

优点

  • 多态性实现到位:Weapon基类定义virtual的GetWeaponKind方法,派生类Sword、Lance、Club正确override实现各自类型标识,符合继承的设计初衷。
  • ToString方法设计合理:Weapon重写ToString展示武器信息,派生类可直接继承或按需扩展,简化了对象字符串输出逻辑。

可优化点

  • 强制派生类实现核心方法:Weapon的GetWeaponKind可改为abstract方法,因为所有具体武器都必须明确类型,避免基类返回"Unknown"的无效情况,强制派生类提供实现,增强代码严谨性。
  • 减少冗余代码:Knight的GetWeaponKind方法可通过暴露只读的Weapon属性替代,让外部直接判断武器是否存在并调用其方法,更灵活且减少冗余:
    public Weapon Weapon => _weapon;
    
    外部使用时可写:knight.Weapon?.GetWeaponKind() ?? ""。

3. C#最佳实践与命名规范

优点

  • 命名规范合规:类名、方法名、属性采用帕斯卡命名法(PascalCase),私有字段使用下划线前缀(如_designation),静态字段用s_前缀并添加注释说明,符合C#行业规范。
  • 异常处理规范:自定义异常NameAlreadyExistsException正确继承Exception,并实现带消息的构造函数,符合异常处理最佳实践。

可优化点

  • 简化代码语法:
    • 构造函数中可简化赋值逻辑,如_id = s_nextId++;代替两行代码;引用类型字段默认值为null,无需在构造函数中显式赋值(如_squire = null;可省略)。
    • 字符串拼接建议使用插值字符串($""),提升可读性,例如Knight的ToString方法:
      public override string ToString()
      {
          var output = $"Knight {_id}\t\tName: {_name}\t\tNickname: {_nickname}";
      
          if (_weapon != null)
          {
              output += $"\n\t\tWeapon: {_weapon.Designation}\t\tKind: {_weapon.GetWeaponKind()}";
          }
      
          if (_squire != null)
          {
              output += $"\n\t\tSquire: {_squire}";
          }
      
          return output;
      }
      
    • 私有字段可替换为只读自动属性,简化代码,例如Weapon类:
      public abstract class Weapon
      {
          public string Designation { get; }
      
          public Weapon(string designation)
          {
              Designation = designation;
          }
      
          // ... 其他代码
      }
      
  • 统一命名空间:所有类无需单独声明namespace KnightTournament,可统一放在同一个命名空间下,简化代码结构。
  • 异常消息优化:NameAlreadyExistsException的构造函数中,消息拼接使用插值字符串更清晰:
    public NameAlreadyExistsException(string message) 
        : base($"{message} already exists!")
    {
    }
    

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.03 04:05:53