Earn More by Sharing What You Love
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

Tim Corey
49분 41초
상속과 인터페이스는 객체 지향 프로그래밍(OOP)의 필수적인 부분입니다. Tim Corey는 자신의 비디오 " C#에서의 상속 vs 인터페이스: 객체 지향 프로그래밍 "에서 상속을 언제 사용하고 인터페이스를 언제 선택해야 하는지에 대해 자세히 설명합니다.
이 기사는 Tim Corey의 비디오에 대한 포괄적인 가이드를 제공합니다. 이 글은 영상에서 제공된 핵심 개념, 예시 및 코드 설명을 분석하여 상속과 인터페이스의 차이점과 각각을 언제 사용해야 하는지를 강조합니다.
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 Property RentalId As Integer
Public Property CurrentRenter As String
Public Property PricePerDay As Decimal
Public Property NumberOfPassengers As Integer
Public Sub StartEngine()
Console.WriteLine("Turn key to ignition setting")
Console.WriteLine("Turn key to on")
End Sub
Public Sub StopEngine()
Console.WriteLine("Turn key to off")
End Sub
End ClassTim은 (10:15)에서 상속을 잘못 사용하면 문제가 발생할 수 있다고 설명합니다. 그는 상속을 잘못 사용하면 관리 및 확장이 어려운 코드가 될 수 있다고 강조합니다. 그는 단순히 코드 공유를 위해 상속을 사용하는 것을 권장하지 않습니다.
Tim은 (10:45)에 자동차 유형에 대한 열거형을 추가합니다. 그는 모든 열거형을 한 곳에 보관하기 위해 Enums.cs라는 새 클래스 파일을 만듭니다. 이 열거형은 서로 다른 자동차 스타일을 구분하는 데 도움이 될 것입니다.
// Enums.cs
public enum CarType
{
Hatchback,
Sedan,
Compact
}' Enums.vb
Public Enum CarType
Hatchback
Sedan
Compact
End Enum그는 차량 유형을 지정하기 위해 RentalCar 클래스에 속성을 추가합니다.
public class RentalCar : RentalVehicle
{
public CarType Style { get; set; }
// Other properties and methods
}Public Class RentalCar
Inherits RentalVehicle
Public Property Style As CarType
' Other properties and methods
End ClassTim이 (12:27)에서 설명했듯이, 렌터카 회사는 자사 차량에 트럭을 추가하기로 결정했고 이로 인해 새로운 요구 사항이 발생했습니다. 그는 부모 클래스인 RentalVehicle을 상속받는 RentalTruck 클래스를 생성합니다.
public class RentalTruck : RentalVehicle
{
public TruckType Style { get; set; }
// Other properties and methods
}Public Class RentalTruck
Inherits RentalVehicle
Public Property Style As TruckType
' Other properties and methods
End Class그는 트럭 유형에 대한 새로운 열거형을 정의합니다.
// Enums.cs
public enum TruckType
{
ShortBed,
LongBed
}' Enums.vb
Public Enum TruckType
ShortBed
LongBed
End EnumTim은 (15:28)에서 두 속성이 같은 이름을 공유한다고 해서 같은 속성이라는 의미는 아니라고 강조합니다. 그는 Style 속성을 예로 들어 이를 설명하는데, 이 속성은 서로 다른 열거형(자동차의 경우 CarType, 트럭의 경우 TruckType)을 의미할 수 있습니다.
렌터카 업체가 보유 품목을 확장하여 보트를 추가했습니다. Tim은 RentalBoat 클래스를 만들어 이 문제를 처리하는 방법을 보여줍니다. 처음에는 배가 자동차나 트럭과 몇 가지 특성을 공유하기 때문에 다루기 쉬워 보입니다.
public class RentalBoat : RentalVehicle
{
// Properties and methods specific to boats
}Public Class RentalBoat
Inherits RentalVehicle
' Properties and methods specific to boats
End Class돛단배는 엔진이 없기 때문에 도입에 어려움이 따른다. 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
Inherits RentalVehicle
Public Overrides Sub StartEngine()
Throw New NotImplementedException("I do not have an engine to start")
End Sub
Public Overrides Sub StopEngine()
Throw New NotImplementedException("I do not have an engine to stop")
End Sub
End ClassTim은 기본 클래스의 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 MustInherit Class RentalVehicle
' Common properties
Public Overridable Sub StartEngine()
Console.WriteLine("Engine started")
End Sub
Public Overridable Sub StopEngine()
Console.WriteLine("Engine stopped")
End Sub
End ClassTim은 (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
Inherits RentalVehicle
Public Overrides Sub StartEngine()
Throw New NotImplementedException("I do not have an engine to start")
End Sub
Public Overrides Sub StopEngine()
Throw New NotImplementedException("I do not have an engine to stop")
End Sub
End ClassTim은 (24:06)에서 상속이 더 이상 논리적 의미가 없을 때 어떻게 복잡하고 지저분한 코드베이스로 이어질 수 있는지 강조합니다. 예를 들어, 범선은 엔진이 달린 대여 차량으로 취급해서는 안 됩니다. 이는 상속의 한계와 더 나은 설계 접근 방식의 필요성을 보여줍니다.
이러한 문제들을 해결하기 위해 팀은 인터페이스를 활용한 더 나은 디자인을 제안합니다. 그는 개선된 접근 방식을 시연하기 위해 "BetterOODemo"라는 이름의 새로운 콘솔 애플리케이션 프로젝트를 만드는 것으로 시작합니다.
팀은 모든 임대 숙소에 공통적인 속성을 통합하기 위해 IRental 인터페이스를 소개합니다.
public interface IRental
{
int RentalId { get; set; }
string CurrentRenter { get; set; }
decimal PricePerDay { get; set; }
}Public Interface IRental
Property RentalId As Integer
Property CurrentRenter As String
Property PricePerDay As Decimal
End Interface팀은 육상 차량에 대한 기본 클래스를 만들어 차량 대여라는 개념과 차량 자체를 분리합니다.
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 MustInherit Class LandVehicle
Public Property NumberOfPassengers As Integer
Public Overridable Sub StartEngine()
Console.WriteLine("Engine started")
End Sub
Public Overridable Sub StopEngine()
Console.WriteLine("Engine stopped")
End Sub
End ClassTim은 기본 차량 클래스의 이름을 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
Inherits LandVehicle
Implements IRental
Public Property RentalId As Integer Implements IRental.RentalId
Public Property CurrentRenter As String Implements IRental.CurrentRenter
Public Property PricePerDay As Decimal Implements IRental.PricePerDay
Public Property Style As CarType
End Class
Public Class Truck
Inherits LandVehicle
Implements IRental
Public Property RentalId As Integer Implements IRental.RentalId
Public Property CurrentRenter As String Implements IRental.CurrentRenter
Public Property PricePerDay As Decimal Implements IRental.PricePerDay
Public Property Style As TruckType
End Class이 설계는 관심사를 명확하게 분리하여 각 클래스가 해당 유형에 관련된 속성과 메서드만 갖도록 합니다.
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
Implements IRental
Public Property RentalId As Integer
Public Property CurrentRenter As String
Public Property PricePerDay As Decimal
' Additional properties and methods specific to sailboats
End Class팀은 트럭, 요트, 자동차 등 다양한 렌탈 유형을 저장하기 위해 IRental 인터페이스를 활용하여 여러 유형의 렌탈 목록을 설정합니다.
List<IRental> rentals = new List<IRental>
{
new Truck { CurrentRenter = "Truck Renter" },
new Sailboat { CurrentRenter = "Sailboat Renter" },
new Car { CurrentRenter = "Car Renter" }
};Dim rentals As New List(Of IRental) From {
New Truck With {.CurrentRenter = "Truck Renter"},
New Sailboat With {.CurrentRenter = "Sailboat Renter"},
New Car With {.CurrentRenter = "Car Renter"}
}이 디자인은 대여 품목을 반복적으로 검사하고 CurrentRenter, PricePerDay 및 RentalId와 같은 공통 속성에 접근할 수 있도록 허용합니다.
foreach (var rental in rentals)
{
Console.WriteLine($"Renter: {rental.CurrentRenter}, Price Per Day: {rental.PricePerDay}");
}For Each rental In rentals
Console.WriteLine($"Renter: {rental.CurrentRenter}, Price Per Day: {rental.PricePerDay}")
Next다른 대여 유형의 특정 속성과 메서드에 접근하기 위해, 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
}
}For Each rental In rentals
If TypeOf rental Is Truck Then
Dim truck As Truck = CType(rental, Truck)
Console.WriteLine($"Truck Style: {truck.Style}, Passengers: {truck.NumberOfPassengers}")
ElseIf TypeOf rental Is Sailboat Then
Dim sailboat As Sailboat = CType(rental, Sailboat)
' Access sailboat-specific properties
ElseIf TypeOf rental Is Car Then
Dim car As Car = CType(rental, Car)
' Access car-specific properties
End If
Next팀은 인터페이스를 사용하면 향후 변경 사항에 대한 유연성을 확보할 수 있다고 강조합니다. 예를 들어, 탱크나 TV와 같은 새로운 유형의 임대 품목을 추가하더라도 기존 구조를 해치지는 않을 것입니다.
팀은 코드 공유를 위해 상속을 과도하게 사용하는 것을 권장하지 않습니다. 상속이 복잡하고 유연성이 떨어지는 코드베이스로 이어질 수 있기 때문입니다. 대신 그는 '이다'라는 관계를 논리적 범위를 넘어서까지 확장하지 않고 원하는 결과를 얻기 위해 인터페이스와 구성을 활용할 것을 권장합니다.
팀 코리의 객체지향 프로그래밍에서의 상속과 인터페이스에 대한 설명은 유지보수 가능하고 유연한 코드를 작성하는 명확한 방법을 제시합니다. 그는 흔히 발생하는 문제점을 보여주고 세련된 인터페이스 디자인을 제공함으로써 개발자들이 애플리케이션 구조를 효과적으로 설계하는 데 필요한 정보를 바탕으로 결정을 내릴 수 있도록 돕습니다.
이러한 개념을 더 자세히 알아보고 실제로 어떻게 적용되는지 보려면 팀의 전체 영상을 시청하세요. 그의 채널 은 프로그래밍 튜토리얼의 보고입니다. 놓치지 마세요!
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!
Join our newsletter, you’ll get exclusive access on article updates. We value your privacy