如何正确实现C#继承与接口以解决两类员工的代码异味?
问题描述
我有承包商(contractors)和正式员工(permanent staff)两类员工,需要检索合同到期日期在一年内的承包商,原代码存在代码异味,尝试重构后因继承与接口实现错误导致类型转换问题无法运行,请求正确的C#面向对象实现方案。
原代码(模型)
using System; namespace Employee.Model { public class IEmployee { public string Id { get; set; } public string Name { get; set; } public DateTime ContractEndDate { get; set; } } }
原测试代码
namespace Employee.Tests { public class EmployeeTests { public static IEmployee SampleData = new[] { new IEmployee { Id = 1, Name = "permanent staff 1" }, new IEmployee { Id = 2, Name = "contractor 1", ContractEndDate = new DateTime(2025, 12, 30) }, new IEmployee { Id = 3, Name = "permanent staff 2" }, new IEmployee { Id = 4, Name = "contractor 2", ContractEndDate = new DateTime(2027, 12, 30) } }; [Test] public void Contract_Matched() { // Expected Data int countEndWithin2Years = 1; // Test var testCount = ECalculator.GetEndWithinXYears(EmployeeTests.SampleData); //Assert Assert.That(countEndWithin2Years, Is.EqualTo(testCount)); } } }
错误的重构尝试
public interface IPermanent { public int Id { get; set; } public string Name { get; set; } } public class Permanent : IPermanent { private int id; private string name; public int Id { get { return id; } set { id = value; } } public string Name { get { return name; } set { name = value; } } } public interface IContractor: IPermanent { public DateTime ContractEndDate { get; set; } } public class Contractor : IContractor { private int id; private string name; private DateTime contractEndDate; public int Id { get { return id; } set { id = value; } } public string Name { get { return name; } set { name = value; } } public DateTime ContractEndDate { get { return contractEndDate; } set { contractEndDate = value; } } } public interface IEmployee : IPermanent, IContractor { }
修正后的测试用例(仍有问题)
public static IEnumerable<IEmployee> SampleData = new[] { (IEmployee)new Permanent { Id = 1, Name = "permanent staff 1" }, (IEmployee)new Contractor { Id = 2, Name = "contractor 1", ContractEndDate = new DateTime(2025, 12, 30) }, (IEmployee)new Permanent { Id = 3, Name = "permanent staff 2" }, (IEmployee)new Contractor { Id = 4, Name = "contractor 2", ContractEndDate = new DateTime(2027, 12, 30) } }; [Test] public void Contract_Matched() { // Expected Data int countEndWithin2Years = 1; // Test var testCount = ECalculator.GetEndWithinXYears(EmployeeTests.SampleData); //Assert Assert.That(countEndWithin2Years, Is.EqualTo(testCount)); }
正确的C#面向对象实现方案
1. 模型层重构
修正命名规范,拆分不同类型员工的职责,用接口定义通用属性,具体类实现专属逻辑:
using System; namespace Employee.Model { // 通用员工接口,定义所有员工共有的属性 public interface IEmployee { int Id { get; set; } string Name { get; set; } } // 正式员工类,仅实现通用员工属性 public class PermanentEmployee : IEmployee { public int Id { get; set; } public string Name { get; set; } } // 承包商类,继承通用员工接口并添加合同到期属性 public class ContractorEmployee : IEmployee { public int Id { get; set; } public string Name { get; set; } public DateTime ContractEndDate { get; set; } } }
重构说明
- 修正错误命名:类名不再使用
I前缀(I是C#接口的标准命名前缀) - 拆分职责:正式员工无需
ContractEndDate属性,避免无效字段的冗余 - 使用自动属性简化代码,无需手动编写私有字段与get/set逻辑
2. 计算器逻辑实现
实现筛选合同到期承包商的核心逻辑,确保类型安全与逻辑正确:
using System; using System.Collections.Generic; using System.Linq; using Employee.Model; namespace Employee.Calculator { public static class ECalculator { // 筛选合同到期时间在指定年限内的承包商数量,默认年限为1年 public static int GetEndWithinXYears(IEnumerable<IEmployee> employees, int years = 1) { if (employees == null) throw new ArgumentNullException(nameof(employees)); return employees.OfType<ContractorEmployee>() .Count(contractor => contractor.ContractEndDate <= DateTime.Now.AddYears(years)); } } }
逻辑说明
- 使用
OfType<ContractorEmployee>()精准过滤承包商类型,避免类型转换错误 - 用
Count()统计符合到期时间条件的承包商数量 - 添加参数默认值,满足"一年内"的默认业务需求
- 增加空值校验,防止空引用异常
3. 修正后的测试代码
修复类型错误,优化测试场景,确保测试逻辑可靠:
using System; using System.Collections.Generic; using Employee.Model; using Employee.Calculator; using NUnit.Framework; namespace Employee.Tests { public class EmployeeTests { public static IEnumerable<IEmployee> SampleData = new List<IEmployee> { new PermanentEmployee { Id = 1, Name = "permanent staff 1" }, new ContractorEmployee { Id = 2, Name = "contractor 1", ContractEndDate = DateTime.Now.AddMonths(10) }, // 10个月内到期 new PermanentEmployee { Id = 3, Name = "permanent staff 2" }, new ContractorEmployee { Id = 4, Name = "contractor 2", ContractEndDate = DateTime.Now.AddYears(3) } // 3年后到期 }; [Test] public void Contract_EndsWithinOneYear_CountMatches() { int expectedCount = 1; int actualCount = ECalculator.GetEndWithinXYears(SampleData); Assert.That(actualCount, Is.EqualTo(expectedCount)); } [Test] public void Contract_EndsWithinTwoYears_CountMatches() { int expectedCount = 1; int actualCount = ECalculator.GetEndWithinXYears(SampleData, 2); Assert.That(actualCount, Is.EqualTo(expectedCount)); } } }
测试修正说明
- 使用
List<IEmployee>替代数组,类型声明更清晰 - 采用相对时间设置到期日期,避免硬编码日期导致测试失效
- 重命名测试方法为语义化名称,符合测试规范
- 增加多场景测试,验证不同年限参数的逻辑正确性
内容的提问来源于stack exchange,提问作者Jeroen Denny
相关产品推荐
相关产品推荐

