Menu
CoddyTech

密封类

本课属于 Coddy C# 学习旅程的“面向对象编程”单元。第 22 课,共 70 课。

有时,你可能想要停止继承链。sealed 关键字可以防止类被继承,从而锁定其实现,让其他类无法扩展它。

public sealed class FinalDog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Woof!");
    }
}

// 这样会导致编译错误:
// public class SuperDog : FinalDog { }

为什么要密封一个类?安全性和设计完整性是常见原因。如果你精心设计了一个类,不希望任何人通过继承改变它的行为,那么密封它就能确保你的实现保持不变。

你也可以封存单个方法,而不是整个类。这样可以阻止进一步重写,同时仍允许继承这个类:

public class Dog : Animal
{
    public sealed override void Speak()
    {
        Console.WriteLine("Woof!");
    }
}

public class Puppy : Dog
{
    // 不能重写 Speak(),因为它已被 sealed 修饰。
    // 但可以添加新方法。
    public void Whimper()
    {
        Console.WriteLine("Whimper...");
    }
}

注意,方法上的 sealed 必须与 override 结合使用。你只能封闭父类中原本就是虚方法的方法。这样你就能精确控制:允许继承层次结构中的某些定制,然后在特定位置将其锁定。

challenge icon

挑战

简单

我们来构建一个支付处理系统,演示如何使用 sealed 锁定关键实现。你将创建一个层次结构,其中某些支付方式可以被扩展,而最安全的支付方式则使用 sealed 进行保护,以防止任何修改。

你将创建 4 个文件来组织代码:

  • PaymentMethod.cs:在 Payments 命名空间中定义一个 PaymentMethod 类。这个基类有一个 Name 属性(字符串),以及一个用于设置该属性的构造函数。添加一个名为 ProcessPayment(decimal amount) 的 virtual 方法,该方法返回 "Processing ${amount} via {Name}"。
  • CreditCardPayment.cs:在 Payments 命名空间中定义一个 CreditCardPayment 类,使其继承自 PaymentMethod。添加一个 CardNumber 属性(字符串)。构造函数接收名称和卡号,并使用 base 传递名称。使用 sealed 关键字重写 ProcessPayment,这样后续派生类就无法更改此行为。它应返回 "Charging ${amount} to card ending in {last 4 digits of CardNumber}"。
  • BankTransfer.cs:在 Payments 命名空间中定义一个名为 BankTransfer 的 sealed 类,使其继承自 PaymentMethod。整个类都是 sealed 的,也就是说没有人可以继承它。添加一个 BankName 属性(字符串)。构造函数接收名称和银行名称。重写 ProcessPayment,使其返回 "Transferring ${amount} from {BankName}"。
  • Program.cs:在主文件中,使用输入值创建这 3 种支付类型的实例。将它们存储为 PaymentMethod 引用,并对每个引用调用 ProcessPayment,以展示使用 sealed 实现的多态。

你将收到 4 个输入:

  • 信用卡号(16 位数字)
  • 银行名称
  • 支付金额

打印通过每种方式处理支付的结果:

{base PaymentMethod result}
{CreditCardPayment result}
{BankTransfer result}

例如,如果输入是 1234567890123456、First National 和 150.00,输出应为:

Processing $150.00 via Generic
Charging $150.00 to card ending in 3456
Transferring $150.00 from First National

将 "Generic" 用作基类 PaymentMethod 实例的名称,将 "Credit Card" 用作信用卡支付的名称,并将 "Bank Transfer" 用作银行转账的名称。

CreditCardPayment 中的 sealed 方法可确保即使有人创建了子类,也无法更改信用卡支付的处理方式。sealed 类 BankTransfer 则会完全禁止继承。这两者都是支付系统中重要的安全模式!

亲手试试

using System;
using Payments;

class Program
{
    public static void Main(string[] args)
    {
        // 读取输入
        string cardNumber = Console.ReadLine();
        string bankName = Console.ReadLine();
        decimal amount = Convert.ToDecimal(Console.ReadLine());
        
        // TODO: 创建一个名为 "Generic" 的基类 PaymentMethod 实例
        // 将其存储为 PaymentMethod 类型的引用
        
        // TODO: 用名称 "Credit Card" 和卡号创建一个 CreditCardPayment 实例
        // 将其存储为 PaymentMethod 类型的引用
        
        // TODO: 用名称 "Bank Transfer" 和银行名称创建一个 BankTransfer 实例
        // 将其存储为 PaymentMethod 类型的引用
        
        // TODO: 对每个实例调用 ProcessPayment 并打印结果
        // 每个结果单独占一行
    }
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C# 在线编译器