푸터 콘텐츠로 바로가기
Iron Academy Logo
C# 배우기
C# 배우기

다른 카테고리

C#의 객체지향 프로그래밍 이해하기

Tim Corey
49분 41초

상속과 인터페이스는 객체 지향 프로그래밍(OOP)의 필수적인 부분입니다. Tim Corey는 자신의 비디오 " C#에서의 상속 vs 인터페이스: 객체 지향 프로그래밍 "에서 상속을 언제 사용하고 인터페이스를 언제 선택해야 하는지에 대해 자세히 설명합니다.

이 글은 팀 코리의 비디오에 대한 종합적인 가이드 역할을 합니다. 이 글은 영상에서 제공된 핵심 개념, 예시 및 코드 설명을 분석하여 상속과 인터페이스의 차이점과 각각을 언제 사용해야 하는지를 강조합니다.

소개

Tim은 (0:00)에서 상속과 인터페이스를 구분하는 것의 중요성을 강조하면서 시작합니다. 그는 최상의 결과를 얻기 위해 각 개념을 언제 사용해야 하는지 이해하는 것이 중요하다고 강조합니다. 그의 목표는 단일 상속의 잘못된 사용 사례를 시작으로 이를 바로잡는 과정을 통해 이를 입증하는 것입니다.

프로젝트 생성

(1:08)에서 Tim은 .NET 5를 사용하여 간단한 콘솔 애플리케이션을 만듭니다. 그는 프로젝트 이름을 "OODemoApp"으로 지정하고 프로덕션 환경에서 사용할 수 있는 코드를 만드는 것보다 개념을 보여주는 것이 주요 목표라고 설명합니다.

상속 이해하기

Tim은 (1:55)에서 상속의 기본 사항을 자세히 살펴봅니다. 그는 상속을 기본 클래스의 속성과 메서드가 파생 클래스에 의해 상속되는 메커니즘으로 정의합니다. 그는 상속이 단순히 코드 재사용 및 공유를 위한 용도로만 사용되어서는 안 되며, 논리적인 "is-a" 관계를 확립하는 데 사용되어야 한다고 강조합니다.

핵심 사항:

  • 동일성 관계 : 파생 클래스가 기본 클래스의 유형인지 확인합니다.
  • 공통 로직 : 상속받은 클래스는 속성이나 메서드 시그니처뿐만 아니라 공통 로직을 공유해야 합니다.

예시 클래스: 렌터카

Tim은 (7:52)에서 상속의 기본 개념을 설명하기 위해 RentalCar 클래스를 생성합니다. 이 클래스는 플로리다주 마이애미의 렌터카 업체에서 대여할 수 있는 차량을 나타냅니다.

public class RentalCar
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public int NumberOfPassengers { get; set; }

    public void StartEngine()
    {
        Console.WriteLine("Turn key to ignition setting");
        Console.WriteLine("Turn key to on");
    }

    public void StopEngine()
    {
        Console.WriteLine("Turn key to off");
    }
}
public class RentalCar
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public int NumberOfPassengers { get; set; }

    public void StartEngine()
    {
        Console.WriteLine("Turn key to ignition setting");
        Console.WriteLine("Turn key to on");
    }

    public void StopEngine()
    {
        Console.WriteLine("Turn key to off");
    }
}

잘못된 상속의 함정을 보여주는 사례

Tim은 (10:15)에서 상속을 잘못 사용하면 문제가 발생할 수 있다고 설명합니다. 그는 상속을 잘못 사용하면 관리 및 확장이 어려운 코드가 될 수 있다고 강조합니다. 그는 단순히 코드 공유를 위해 상속을 사용하는 것을 권장하지 않습니다.

자동차 및 트럭 유형에 대한 열거형 추가

Tim은 (10:45)에 자동차 유형에 대한 열거형을 추가합니다. 그는 모든 열거형을 한 곳에 보관하기 위해 Enums.cs라는 새 클래스 파일을 만듭니다. 이 열거형은 서로 다른 자동차 스타일을 구분하는 데 도움이 될 것입니다.

// Enums.cs
public enum CarType
{
    Hatchback,
    Sedan,
    Compact
}
// Enums.cs
public enum CarType
{
    Hatchback,
    Sedan,
    Compact
}

그는 차량 유형을 지정하기 위해 RentalCar 클래스에 속성을 추가합니다.

public class RentalCar : RentalVehicle
{
    public CarType Style { get; set; }
    // Other properties and methods
}
public class RentalCar : RentalVehicle
{
    public CarType Style { get; set; }
    // Other properties and methods
}

트럭까지 포함하도록 확장

Tim이 (12:27)에서 설명했듯이, 렌터카 회사는 자사 차량에 트럭을 추가하기로 결정했고 이로 인해 새로운 요구 사항이 발생했습니다. 그는 부모 클래스인 RentalVehicle을 상속받는 RentalTruck 클래스를 생성합니다.

public class RentalTruck : RentalVehicle
{
    public TruckType Style { get; set; }
    // Other properties and methods
}
public class RentalTruck : RentalVehicle
{
    public TruckType Style { get; set; }
    // Other properties and methods
}

그는 트럭 유형에 대한 새로운 열거형을 정의합니다.

// Enums.cs
public enum TruckType
{
    ShortBed,
    LongBed
}
// Enums.cs
public enum TruckType
{
    ShortBed,
    LongBed
}

다양한 속성 유형 처리

Tim은 (15:28)에서 두 속성이 같은 이름을 공유한다고 해서 같은 속성이라는 의미는 아니라고 강조합니다. 그는 Style 속성을 예로 들어 이를 설명하는데, 이 속성은 서로 다른 열거형(자동차의 경우 CarType, 트럭의 경우 TruckType)을 의미할 수 있습니다.

함대에 새로운 보트를 소개합니다

렌터카 업체가 보유 품목을 확장하여 보트를 추가했습니다. Tim은 RentalBoat 클래스를 만들어 이 문제를 처리하는 방법을 보여줍니다. 처음에는 배가 자동차나 트럭과 몇 가지 특성을 공유하기 때문에 다루기 쉬워 보입니다.

public class RentalBoat : RentalVehicle
{
    // Properties and methods specific to boats
}
public class RentalBoat : RentalVehicle
{
    // Properties and methods specific to boats
}

요트 다루기

돛단배는 엔진이 없기 때문에 도입에 어려움이 따른다. Tim은 (19:57)에서 이 시나리오에서 상속의 한계를 설명합니다.

public class RentalSailboat : RentalVehicle
{
    public override void StartEngine()
    {
        throw new NotImplementedException("I do not have an engine to start");
    }

    public override void StopEngine()
    {
        throw new NotImplementedException("I do not have an engine to stop");
    }
}
public class RentalSailboat : RentalVehicle
{
    public override void StartEngine()
    {
        throw new NotImplementedException("I do not have an engine to start");
    }

    public override void StopEngine()
    {
        throw new NotImplementedException("I do not have an engine to stop");
    }
}

인터페이스를 이용한 문제 해결

Tim은 기본 클래스의 StartEngine 및 StopEngine 메서드를 가상 메서드로 만들어 이러한 메서드를 사용하지 않는 파생 클래스에서 재정의할 수 있도록 하는 것을 제안합니다.

public abstract class RentalVehicle
{
    // Common properties

    public virtual void StartEngine()
    {
        Console.WriteLine("Engine started");
    }

    public virtual void StopEngine()
    {
        Console.WriteLine("Engine stopped");
    }
}
public abstract class RentalVehicle
{
    // Common properties

    public virtual void StartEngine()
    {
        Console.WriteLine("Engine started");
    }

    public virtual void StopEngine()
    {
        Console.WriteLine("Engine stopped");
    }
}

호출해서는 안 되는 메서드 처리

Tim은 (21:56)에 상속된 클래스에 호출해서는 안 되는 메서드가 있는 경우의 문제점을 설명합니다. 엔진이 없는 RentalSailboat 클래스의 예를 들면, 이 클래스는 RentalVehicle 클래스로부터 StartEngine 및 StopEngine 메서드를 상속받습니다. 이러한 상황은 해당 메서드가 의도치 않게 호출될 경우 문제를 야기할 수 있습니다. 왜냐하면 해당 메서드는 적용할 수 없음을 나타내기 위해 예외를 발생시켜야 하기 때문입니다.

public class RentalSailboat : RentalVehicle
{
    public override void StartEngine()
    {
        throw new NotImplementedException("I do not have an engine to start");
    }

    public override void StopEngine()
    {
        throw new NotImplementedException("I do not have an engine to stop");
    }
}
public class RentalSailboat : RentalVehicle
{
    public override void StartEngine()
    {
        throw new NotImplementedException("I do not have an engine to start");
    }

    public override void StopEngine()
    {
        throw new NotImplementedException("I do not have an engine to stop");
    }
}

상속의 한계를 인식하기

Tim은 (24:06)에서 상속이 더 이상 논리적 의미가 없을 때 어떻게 복잡하고 지저분한 코드베이스로 이어질 수 있는지 강조합니다. 예를 들어, 범선은 엔진이 달린 대여 차량으로 취급해서는 안 됩니다. 이는 상속의 한계와 더 나은 설계 접근 방식의 필요성을 보여줍니다.

인터페이스를 통해 더 나은 디자인으로 나아가기

이러한 문제들을 해결하기 위해 팀은 인터페이스를 활용한 더 나은 디자인을 제안합니다. 그는 개선된 접근 방식을 시연하기 위해 "BetterOODemo"라는 이름의 새로운 콘솔 애플리케이션 프로젝트를 만드는 것으로 시작합니다.

인터페이스 정의

팀은 모든 임대 숙소에 공통적인 속성을 통합하기 위해 IRental 인터페이스를 소개합니다.

public interface IRental
{
    int RentalId { get; set; }
    string CurrentRenter { get; set; }
    decimal PricePerDay { get; set; }
}
public interface IRental
{
    int RentalId { get; set; }
    string CurrentRenter { get; set; }
    decimal PricePerDay { get; set; }
}

지상 차량용 기본 클래스 생성

팀은 육상 차량에 대한 기본 클래스를 만들어 차량 대여라는 개념과 차량 자체를 분리합니다.

public abstract class LandVehicle
{
    public int NumberOfPassengers { get; set; }

    public virtual void StartEngine()
    {
        Console.WriteLine("Engine started");
    }

    public virtual void StopEngine()
    {
        Console.WriteLine("Engine stopped");
    }
}
public abstract class LandVehicle
{
    public int NumberOfPassengers { get; set; }

    public virtual void StartEngine()
    {
        Console.WriteLine("Engine started");
    }

    public virtual void StopEngine()
    {
        Console.WriteLine("Engine stopped");
    }
}

Tim은 기본 차량 클래스의 이름을 LandVehicle로 변경함으로써 적절한 차량만 엔진 관련 메서드를 상속받도록 합니다.

승용차 및 트럭 클래스 구현

Tim은 LandVehicle을 상속받고 IRental 인터페이스를 구현하는 Car 및 Truck 클래스를 만듭니다.

public class Car : LandVehicle, IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public CarType Style { get; set; }
}

public class Truck : LandVehicle, IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public TruckType Style { get; set; }
}
public class Car : LandVehicle, IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public CarType Style { get; set; }
}

public class Truck : LandVehicle, IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }
    public TruckType Style { get; set; }
}

이 설계는 관심사를 명확하게 분리하여 각 클래스가 해당 유형에 관련된 속성과 메서드만 갖도록 합니다.

코드 중복 방지

Tim은 (31:41)에서 불필요한 코드 중복을 피하는 것의 중요성에 대해 논의합니다. 그는 IRental 인터페이스가 여러 클래스에서 동일한 속성을 요구하지만, 이는 논리가 중복되는 것이 아니라 속성 선언만 중복되기 때문에 DRY(Don't Repeat Yourself) 원칙을 위반하는 것으로 간주되지 않는다고 설명합니다.

렌탈 요트 강좌 도입

Tim은 (35:09)에서 LandVehicle을 상속하는 대신 IRental 인터페이스를 구현하여 RentalSailboat 클래스를 별도로 처리하는 방법을 설명합니다. 이러한 접근 방식은 부적절한 상속과 관련된 문제점을 피하는 데 도움이 됩니다.

public class Sailboat : IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }

    // Additional properties and methods specific to sailboats
}
public class Sailboat : IRental
{
    public int RentalId { get; set; }
    public string CurrentRenter { get; set; }
    public decimal PricePerDay { get; set; }

    // Additional properties and methods specific to sailboats
}

다양한 임대 관리하기

팀은 트럭, 요트, 자동차 등 다양한 렌탈 유형을 저장하기 위해 IRental 인터페이스를 활용하여 여러 유형의 렌탈 목록을 설정합니다.

List<IRental> rentals = new List<IRental>
{
    new Truck { CurrentRenter = "Truck Renter" },
    new Sailboat { CurrentRenter = "Sailboat Renter" },
    new Car { CurrentRenter = "Car Renter" }
};
List<IRental> rentals = new List<IRental>
{
    new Truck { CurrentRenter = "Truck Renter" },
    new Sailboat { CurrentRenter = "Sailboat Renter" },
    new Car { CurrentRenter = "Car Renter" }
};

이 설계는 대여 항목을 반복하여 CurrentRenter, PricePerDayRentalId와 같은 공통 속성에 접근할 수 있도록 합니다.

foreach (var rental in rentals)
{
    Console.WriteLine($"Renter: {rental.CurrentRenter}, Price Per Day: {rental.PricePerDay}");
}
foreach (var rental in rentals)
{
    Console.WriteLine($"Renter: {rental.CurrentRenter}, Price Per Day: {rental.PricePerDay}");
}

특정 유형으로 캐스팅

다양한 대여 유형의 특정 속성과 메서드에 액세스하려면, Tim이 is 키워드를 사용하여 객체를 해당 유형으로 확인하고 변환하는 방법을 시연합니다.

foreach (var rental in rentals)
{
    if (rental is Truck truck)
    {
        Console.WriteLine($"Truck Style: {truck.Style}, Passengers: {truck.NumberOfPassengers}");
    }
    else if (rental is Sailboat sailboat)
    {
        // Access sailboat-specific properties
    }
    else if (rental is Car car)
    {
        // Access car-specific properties
    }
}
foreach (var rental in rentals)
{
    if (rental is Truck truck)
    {
        Console.WriteLine($"Truck Style: {truck.Style}, Passengers: {truck.NumberOfPassengers}");
    }
    else if (rental is Sailboat sailboat)
    {
        // Access sailboat-specific properties
    }
    else if (rental is Car car)
    {
        // Access car-specific properties
    }
}

인터페이스의 유연성

팀은 인터페이스를 사용하면 향후 변경 사항에 대한 유연성을 확보할 수 있다고 강조합니다. 예를 들어, 탱크나 TV와 같은 새로운 유형의 임대 품목을 추가하더라도 기존 구조를 해치지는 않을 것입니다.

상속의 남용을 피하는 방법

팀은 코드 공유를 위해 상속을 과도하게 사용하는 것을 권장하지 않습니다. 상속이 복잡하고 유연성이 떨어지는 코드베이스로 이어질 수 있기 때문입니다. 대신 그는 '이다'라는 관계를 논리적 범위를 넘어서까지 확장하지 않고 원하는 결과를 얻기 위해 인터페이스와 구성을 활용할 것을 권장합니다.

결론

팀 코리의 객체지향 프로그래밍에서의 상속과 인터페이스에 대한 설명은 유지보수 가능하고 유연한 코드를 작성하는 명확한 방법을 제시합니다. 그는 흔히 발생하는 문제점을 보여주고 세련된 인터페이스 디자인을 제공함으로써 개발자들이 애플리케이션 구조를 효과적으로 설계하는 데 필요한 정보를 바탕으로 결정을 내릴 수 있도록 돕습니다.

이러한 개념을 더 자세히 알아보고 실제로 어떻게 적용되는지 보려면 팀의 전체 영상을 시청하세요. 그의 채널 은 프로그래밍 튜토리얼의 보고입니다. 놓치지 마세요!

Hero Worlddot related to C#의 객체지향 프로그래밍 이해하기
Hero Affiliate related to C#의 객체지향 프로그래밍 이해하기

사랑하는 것을 공유하여 더 많은 수익을 얻으세요

당신은 .NET, C#, Java, Python, 또는 Node.js를 다루는 개발자를 위한 콘텐츠를 만드나요? 당신의 전문성을 추가 수입으로 전환하세요!

아이언 서포트 팀

저희는 주 5일, 24시간 온라인으로 운영합니다.
채팅
이메일
전화해