Sealed 클래스
Coddy C# 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 70개 중 22번째.
때때로 상속 체인을 중단하고 싶을 때가 있습니다. sealed 키워드는 클래스가 상속되는 것을 방지하며, 다른 어떤 클래스도 이를 확장할 수 없도록 구현을 고정합니다.
public sealed class FinalDog : Animal
{
public override void Speak()
{
Console.WriteLine("Woof!");
}
}
// 이 코드는 컴파일 오류를 발생시킵니다:
// public class SuperDog : FinalDog { }왜 클래스를 봉인(seal)할까요? 보안과 설계 무결성이 일반적인 이유입니다. 클래스를 신중하게 설계했고 상속을 통해 누군가가 그 동작을 변경하는 것을 원하지 않을 때, 클래스를 봉인하면 구현이 그대로 유지되도록 보장할 수 있습니다.
클래스 전체가 아닌 개별 메서드를 봉인할 수도 있습니다. 이렇게 하면 클래스 자체는 상속될 수 있도록 허용하면서도, 해당 메서드가 더 이상 오버라이딩되지 않도록 합니다:
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와 결합되어야 한다는 점에 유의하세요. 부모 클래스에서 이미 가상(virtual)이었던 메서드만 봉인할 수 있습니다. 이를 통해 상속 계층 구조에서 일부 사용자 정의를 허용한 다음, 특정 지점에서 이를 고정하는 정밀한 제어가 가능합니다.
챌린지
쉬움sealed를 사용하여 중요한 구현을 잠그는 방법을 보여주는 결제 처리 시스템을 구축해 보겠습니다. 특정 결제 수단은 확장할 수 있지만, 가장 보안이 중요한 수단은 수정을 방지하기 위해 봉인(sealed)되는 계층 구조를 만들 것입니다.
코드를 정리하기 위해 네 개의 파일을 생성합니다:
PaymentMethod.cs:Payments네임스페이스에PaymentMethod클래스를 정의합니다. 이 기본 클래스는Name속성(string)과 이를 설정하는 생성자를 가집니다."Processing ${amount} via {Name}"를 반환하는virtual메서드ProcessPayment(decimal amount)를 포함하세요.CreditCardPayment.cs:PaymentMethod를 상속받는CreditCardPayment클래스를Payments네임스페이스에 정의합니다.CardNumber속성(string)을 추가합니다. 생성자는 이름과 카드 번호를 받으며,base를 사용하여 이름을 전달합니다. 더 이상의 파생 클래스가 이 동작을 변경할 수 없도록sealed키워드를 사용하여ProcessPayment를 오버라이드합니다. 이 메서드는"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: 메인 파일에서 입력 값을 사용하여 세 가지 결제 유형의 인스턴스를 모두 생성합니다. 이들을PaymentMethod참조로 저장하고 각각에 대해ProcessPayment를 호출하여 봉인된 구현을 통한 다형성을 보여줍니다.
네 가지 입력을 받게 됩니다:
- 신용카드 번호 (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기본 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 { }봉인된(Sealed) 클래스는 보안 및 설계 무결성을 위해 사용되며, 상속을 통해 구현이 수정되지 않도록 보장합니다.
메서드 봉인하기:
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를 호출하고 결과를 출력
// 각 결과는 별도의 줄에 출력되어야 함
}
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.