Menu
Coddy logo textTech

Sealed クラス

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...");
    }
}

メソッドにおける sealedoverride と組み合わせる必要があることに注意してください。親クラスですでに virtual であったメソッドのみを seal することができます。これにより、継承階層内でのある程度のカスタマイズを許可し、その後特定のポイントでそれを固定するという、正確な制御が可能になります。

challenge icon

チャレンジ

簡単

sealed を使用して重要な実装をロックダウンする方法を示す、決済処理システムを構築しましょう。特定の決済方法は拡張可能ですが、最も安全なものは変更を防ぐために封印(sealed)されるという階層構造を作成します。

コードを整理するために 4 つのファイルを作成します:

  • PaymentMethod.cs: Payments 名前空間に PaymentMethod クラスを定義します。この基底クラスは Name プロパティ(string)と、それを設定するコンストラクタを持ちます。"Processing ${amount} via {Name}" を返す virtual メソッド ProcessPayment(decimal amount) を含めてください。
  • CreditCardPayment.cs: PaymentMethod を継承する CreditCardPayment クラスを Payments 名前空間に定義します。CardNumber プロパティ(string)を追加します。コンストラクタは名前とカード番号を受け取り、base を使用して名前を渡します。ProcessPaymentsealed キーワードでオーバーライドし、これ以降の派生クラスがこの動作を変更できないようにします。このメソッドは "Charging ${amount} to card ending in {last 4 digits of CardNumber}" を返す必要があります。
  • BankTransfer.cs: PaymentMethod を継承する BankTransfer という sealed クラスを Payments 名前空間に定義します。このクラス全体が封印されており、誰も継承することはできません。BankName プロパティ(string)を追加します。コンストラクタは名前と銀行名を受け取ります。ProcessPayment をオーバーライドして "Transferring ${amount} from {BankName}" を返すようにします。
  • Program.cs: メインファイルで、入力値を使用して 3 つの決済タイプすべてのインスタンスを作成します。それらを PaymentMethod 参照として保存し、それぞれで ProcessPayment を呼び出して、封印された実装によるポリモーフィズム(多態性)を確認します。

3 つの入力を受け取ります:

  • クレジットカード番号(16桁)
  • 銀行名
  • 決済金額

各メソッドを通じて決済を処理した結果を出力してください:

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

例えば、入力が 1234567890123456First National150.00 の場合、出力は以下のようになります:

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

基底の PaymentMethod インスタンスの名前には "Generic" を、クレジットカード決済には "Credit Card" を、銀行振込には "Bank Transfer" を使用してください。

CreditCardPayment の封印されたメソッドは、誰かがサブクラスを作成したとしても、クレジットカード決済の処理方法を変更できないことを保証します。封印されたクラス BankTransfer は継承自体を完全に防ぎます。これらはどちらも決済システムにおける重要なセキュリティパターンです!

チートシート

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() をオーバーライドできません - 封印されています
    // しかし、新しいメソッドを追加することはできます
    public void Whimper()
    {
        Console.WriteLine("Whimper...");
    }
}

メソッドにおける sealed キーワードは、override と組み合わせる必要があります。親クラスですでに virtual であったメソッドのみを封印できます。これにより、階層内の特定のポイントまではカスタマイズを許可し、その後はロックすることができます。

自分で試してみよう

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腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン