第一次接触C#继承时,我盯着那个冒号看了半天——就这?不就是子类能调用父类方法吗?直到接手电商平台的优惠券系统重构,才真正明白这个语法糖背后的设计哲学。想象一下:满减券、折扣券、赠品券各有特殊逻辑,但都要校验有效期、适用商品范围等基础规则。如果每个券类都重复写校验代码,不仅维护困难,新加个"限购次数"功能时还得改十几个地方。
这里就是继承的舞台。我们创建Coupon基类封装通用逻辑,派生FullReductionCoupon、DiscountCoupon等处理差异逻辑。实测下来,代码量减少40%,新增券类型只需关注核心差异。比如最近要加的"组合优惠券",继承基类后只需实现组合计算逻辑:
csharp复制public class ComboCoupon : Coupon
{
public override decimal ApplyDiscount(Order order)
{
// 专属逻辑:组合其他优惠券计算最终折扣
var combinedDiscount = ...;
return base.ApplyBaseDiscount(combinedDiscount);
}
}
继承真正的价值在于建立业务语义的层次结构。就像生物分类系统,犬科→犬属→家犬的层级关系,对应到代码就是Payment→CreditCardPayment→AlipayPayment。这种IS-A关系让代码读起来像业务文档,新人也能快速理解系统脉络。
去年重构跨境电商系统时,我们遇到了订单类型爆炸的问题——普通订单、预售订单、拼团订单、秒杀订单... 每个类型都有细微差异。最终用继承解决的方案值得细说:
首先定义OrderBase抽象类,包含所有订单的骨架:
csharp复制public abstract class OrderBase
{
public Guid Id { get; protected set; }
public abstract void Validate();
protected virtual void ProcessPayment()
{
// 默认支付流程
}
public void CompleteOrder()
{
Validate();
ProcessPayment();
// 其他通用逻辑
}
}
关键技巧在于:
abstract强制派生类实现校验逻辑virtual方法提供可选重写的默认实现CompleteOrder)固定流程骨架拼团订单需要额外校验成团状态,我们这样扩展:
csharp复制public class GroupBuyOrder : OrderBase
{
public override void Validate()
{
base.Validate(); // 复用基础校验
if(!IsGroupFormed)
throw new InvalidOperationException("拼团未成功");
}
protected override void ProcessPayment()
{
// 拼团专属支付逻辑
ApplyGroupDiscount();
base.ProcessPayment();
}
}
这里有个实际踩过的坑:忘记调用base.Validate()导致基础校验失效。后来我们引入模板方法+钩子函数的组合:
csharp复制protected void ValidateCore()
{
// 基础校验逻辑
}
public sealed override void Validate()
{
ValidateCore();
AdditionalValidate(); // 抽象方法
}
最近开发塔防游戏时遇到个典型场景:角色需要同时具备攻击、移动、装备等能力。C#虽然不支持多继承,但用接口组合反而更灵活:
csharp复制public interface IMovable
{
void Move(Vector2 direction);
}
public interface IAttacker
{
void Attack(Character target);
}
public class Hero : Character, IMovable, IAttacker
{
// 实现接口方法
}
这种设计让角色能力像乐高积木般可组装。比如要新增会施法的怪物:
csharp复制public class SpellCasterMonster : Monster, ISpellCaster
{
public void CastSpell(Spell spell) {...}
}
C# 8.0的接口默认方法更是锦上添花。比如所有可攻击对象都要计算伤害,但多数使用标准公式:
csharp复制public interface IAttacker
{
int CalculateDamage(Character target)
{
// 默认实现
return AttackPower - target.Defense;
}
}
特殊Boss可以只重写计算逻辑:
csharp复制public class DragonBoss : Monster, IAttacker
{
int IAttacker.CalculateDamage(Character target)
{
return IsEnraged ? AttackPower * 2 : base.CalculateDamage(target);
}
}
不是所有场景都适合继承。曾见过有同事用继承实现不同日志输出方式:
csharp复制public abstract class Logger {}
public class FileLogger : Logger {}
public class DatabaseLogger : Logger {}
这会导致类层次过深。后来改用组合策略模式:
csharp复制public interface ILogWriter
{
void Write(string message);
}
public class Logger
{
private readonly ILogWriter _writer;
public Logger(ILogWriter writer) => _writer = writer;
}
最痛的领悟来自违反LSP(里氏替换原则)。早期设计的Bird基类有个Fly()方法,直到加入企鹅类型... 现在我们会这样设计:
csharp复制public abstract class Bird {}
public interface IFlyingBird { void Fly(); }
public class Eagle : Bird, IFlyingBird {}
public class Penguin : Bird {}
关键经验:基类应该是最小公共抽象,差异能力用接口补充。就像电商系统中的RefundService,普通订单和虚拟商品订单的退款流程完全不同,这时继承反而不如策略模式灵活。