密封类
本课属于 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 结合使用。你只能封闭父类中原本就是虚方法的方法。这样你就能精确控制:允许继承层次结构中的某些定制,然后在特定位置将其锁定。
挑战
简单我们来构建一个支付处理系统,演示如何使用 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 并打印结果
// 每个结果单独占一行
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C# 在线编译器