구성과 상속의 차이
구성과 상속의 차이
객체지향 프로그래밍에서 상속Inheritance과 구성Composition은 객체 간 관계를 설정하는 두 가지 주요 방식입니다. 두 가지 모두 객체 간 협력을 설계하는 강력한 방법이지만, 각각 장단점이 있으며, 상황에 따라 적절한 선택이 필요합니다.
상속의 특징과 단점
상속Inheritance은 객체 간 IS-A 관계를 표현하며, 하위 클래스가 상위 클래스의 속성과 메서드를 물려받아 재사용할 수 있는 특징을 가지고 있습니다. 이를 통해 코드 재사용성과 일관성을 높일 수 있지만, 강한 결합과 유연성 부족이라는 단점이 있습니다.
- 강한 결합: 상위 클래스가 변경되면, 하위 클래스에 영향을 미치게 되어 유지보수성이 떨어집니다.
- 유연성 부족: 상위 클래스의 모든 기능을 물려받아야 하므로, 필요 없는 기능도 상속되어 복잡성이 증가할 수 있습니다.
// 상위 클래스 Library
public class Library
{
public virtual void AddBook() => Console.WriteLine("Book added to library.");
public virtual void BorrowBook() => Console.WriteLine("Book borrowed from library.");
// 모든 도서관이 전자책을 지원하지 않음에도 불구하고, 상속 구조 상 포함됨
public virtual void DownloadEBook() => Console.WriteLine("E-Book downloaded from library.");
}
// 하위 클래스 PhysicalLibrary, 전자책 기능이 필요 없지만 상속 구조로 인해 물려받음
public class PhysicalLibrary : Library
{
public override void AddBook() => Console.WriteLine("Physical book added to library.");
public override void BorrowBook() => Console.WriteLine("Physical book borrowed from library.");
// DownloadEBook 메서드는 필요 없지만 상속받아 물려받음
}
이 예제에서 PhysicalLibrary
는 실제 도서만 취급하지만, 상위 클래스 Library
에서 전자책 다운로드 메서드(DownloadEBook
)를 상속받게 됩니다. 이는 상속의 강한 결합 문제를 보여주며, PhysicalLibrary
가 필요하지 않은 기능을 물려받고, 불필요한 메서드가 포함되는 단점이 발생합니다.
구성이 상속의 단점을 해결하는 방식
구성Composition은 HAS-A 관계를 나타냅니다. 한 객체가 다른 객체를 포함하고, 그 객체의 기능을 활용하는 방식입니다. 상속에 비해 구성은 객체 간의 결합도를 낮추고, 더 유연한 설계를 가능하게 합니다.
- 느슨한 결합: 구성된 객체는 서로 독립적이므로, 한 객체가 변경되더라도 다른 객체에 영향을 미치지 않습니다. 이는 시스템의 유지보수를 용이하게 만듭니다.
- 유연성: 객체 간의 관계를 런타임에 동적으로 변경할 수 있으며, 객체가 다른 객체의 기능을 사용할 때 필요한 기능만 선택적으로 포함할 수 있습니다.
- 재사용성: 구성은 객체가 서로 독립적으로 존재하므로, 같은 기능을 여러 객체에서 필요할 때 쉽게 재사용할 수 있습니다.
// 인터페이스 정의: 도서관 기능을 개별적으로 나누어 필요에 맞게 구성
public interface ILibraryAction
{
void AddBook();
void BorrowBook();
}
public interface IEBookSupport
{
void DownloadEBook();
}
// 실물 도서관 기능
public class PhysicalLibraryAction : ILibraryAction
{
public void AddBook() => Console.WriteLine("Physical book added to library.");
public void BorrowBook() => Console.WriteLine("Physical book borrowed from library.");
}
// 전자책 지원 도서관 기능
public class EBookSupport : IEBookSupport
{
public void DownloadEBook() => Console.WriteLine("E-Book downloaded from library.");
}
// 도서관 클래스는 구성된 객체에 기능을 위임
public class Library
{
private ILibraryAction libraryAction;
private IEBookSupport ebookSupport;
// 도서관 구성은 기능에 따라 동적으로 선택 가능
public Library(ILibraryAction action, IEBookSupport ebook = null)
{
libraryAction = action;
ebookSupport = ebook;
}
public void AddBook() => libraryAction.AddBook();
public void BorrowBook() => libraryAction.BorrowBook();
public void DownloadEBook()
{
if (ebookSupport != null)
{
ebookSupport.DownloadEBook();
}
else
{
Console.WriteLine("This library does not support E-Books.");
}
}
}
// 사용 예시
var physicalLibrary = new Library(new PhysicalLibraryAction());
physicalLibrary.AddBook(); // "Physical book added to library."
physicalLibrary.BorrowBook(); // "Physical book borrowed from library."
physicalLibrary.DownloadEBook(); // "This library does not support E-Books."
var ebookLibrary = new Library(new PhysicalLibraryAction(), new EBookSupport());
ebookLibrary.DownloadEBook(); // "E-Book downloaded from library."
이 예제에서는 구성을 사용해 Library
는 필요한 기능만 동적으로 주입받습니다. PhysicalLibrary
는 전자책 기능이 필요 없으므로 EBookSupport
를 주입하지 않고, 필요한 기능만 사용합니다. 이를 통해 상속의 강한 결합 문제를 해결하고, 유연성을 확보할 수 있습니다.
상속과 구성이 적합한 경우
상속과 구성은 각기 다른 상황에서 사용될 수 있으며, 적절한 설계 선택이 필요합니다.
상속이 적합한 경우
- IS-A 관계가 명확할 때: 하위 클래스가 상위 클래스의 모든 기능을 그대로 물려받아야 하고, IS-A 관계가 자연스러울 때 상속이 적합합니다. 예를 들어,
Bird
클래스와Sparrow
클래스의 관계는 상속으로 표현하는 것이 적합합니다. - 다형성을 활용할 때: 상속을 사용하여 공통 인터페이스를 구현하거나, 상위 클래스에서 정의된 메서드를 하위 클래스에서 재정의Override하여 다형성을 구현할 수 있습니다.
구성이 적합한 경우
- HAS-A 관계가 더 적합할 때: 객체가 다른 객체의 기능을 사용할 필요가 있지만, 고정된 상속 구조가 아니라 필요에 따라 동적으로 포함할 수 있는 관계가 필요할 때 구성이 적합합니다. 예를 들어, 도서관(Library)이 여러 책(Book)을 포함하는 관계는 구성으로 표현할 수 있습니다.
- 유연한 객체 교체: 런타임에 객체 간의 관계를 동적으로 변경하거나, 다른 객체로 교체할 필요가 있을 때 구성은 매우 유용합니다.
- 강한 결합을 피하고자 할 때: 상속을 사용하면 상위 클래스에 대한 의존도가 높아지지만, 구성은 객체 간 결합을 느슨하게 유지하면서도 기능을 확장할 수 있습니다.
다형성의 역할
다형성Polymorphism은 객체가 동일한 인터페이스를 통해 다양한 방식으로 동작할 수 있는 객체지향의 중요한 원칙입니다. 다형성은 상속과 구성 모두에서 사용될 수 있으며, 시스템의 유연성을 극대화하는 데 기여합니다.
상속에서의 다형성
상속은 다형성을 자연스럽게 제공합니다. 하위 클래스는 상위 클래스의 메서드를 재정의override하여, 동일한 인터페이스를 통해 다른 방식으로 동작할 수 있습니다.
// 상위 클래스 Book
public class Book
{
public virtual void PrintInfo() => Console.WriteLine("Book information.");
}
// 하위 클래스 PhysicalBook
public class PhysicalBook : Book
{
public override void PrintInfo() => Console.WriteLine("Physical book information.");
}
// 하위 클래스 EBook
public class EBook : Book
{
public override void PrintInfo() => Console.WriteLine("E-Book information.");
}
// 사용 예시
Book physicalBook = new PhysicalBook();
Book ebook = new EBook();
physicalBook.PrintInfo(); // "Physical book information."
ebook.PrintInfo(); // "E-Book information."
이 예제는 상속을 통해 다형성을 활용한 예시입니다. Book
클래스의 하위 클래스들은 각자 다른 방식으로 PrintInfo
메서드를 구현하고 있습니다. 이를 통해 동일한 인터페이스를 통해 다양한 객체가 다른 동작을 수행할 수 있습니다.
구성에서의 다형성
구성에서도 다형성을 활용할 수 있습니다. 구성된 객체가 특정 인터페이스를 구현하면, 상속 없이도 동일한 방식으로 동작을 변경할 수 있습니다. 이를 통해 구성된 객체의 동작을 쉽게 교체할 수 있습니다.
// 인터페이스 정의
public interface IBook
{
void PrintInfo();
}
// 실물 책 클래스
public class PhysicalBook : IBook
{
public void PrintInfo() => Console.WriteLine("Physical book information.");
}
// 전자책 클래스
public class EBook : IBook
{
public void PrintInfo() => Console.WriteLine("E-Book information.");
}
// 도서관 클래스는 IBook 인터페이스를 구현한 객체에 의존
public class Library
{
private List<IBook> books = new List<IBook>();
public void AddBook(IBook book) => books.Add(book);
public void PrintAllBooks()
{
foreach (var book in books)
{
book.PrintInfo();
}
}
}
// 사용 예시
Library library = new Library();
library.AddBook(new PhysicalBook());
library.AddBook(new EBook());
library.PrintAllBooks();
// 출력:
// "Physical book information."
// "E-Book information."
이 예제는 구성을 통한 다형성을 보여줍니다. Library
클래스는 IBook
인터페이스를 구현한 객체들과 협력하며, 상속이 아닌 구성을 통해 다양한 책 객체를 처리합니다. 런타임에 어떤 객체를 주입하느냐에 따라 다형성이 발휘됩니다.
맺음말
상속과 구성은 각각 장단점이 있으며, 상황에 따라 적절한 방식을 선택하는 것이 중요합니다. 상속은 코드의 재사용성을 극대화할 수 있지만, 강한 결합과 유연성 부족이라는 단점이 있을 수 있습니다. 반면, 구성은 느슨한 결합을 제공하고, 객체를 동적으로 변경할 수 있어 더 유연한 설계를 가능하게 합니다. 다형성은 두 방식 모두에서 중요한 역할을 하며, 상속과 구성에서 각각 어떻게 적용될 수 있는지 이해하는 것이 시스템 설계에 큰 도움이 됩니다. 실무에서는 상속과 구성을 적절히 조합하여 유지보수성과 확장성이 높은 설계를 목표로 해야 합니다.