구성과 위임의 활용
구성이란?
구성Composition은 객체지향 프로그래밍에서 객체 간 관계를 설계하는 방식 중 하나로, 한 객체가 다른 객체를 포함하여 기능을 사용하는 방법입니다. 상속과 달리 구성은 객체 간의 결합도를 낮추고, 유연성을 높이는 데 유리합니다. 구성은 객체 간의 협력 관계를 통해 더 유연한 설계를 가능하게 하며, 실무에서 자주 사용됩니다.
구성의 정의
구성은 객체 간의 HAS-A 관계를 표현합니다. 즉, 한 객체가 다른 객체를 “가지고 있는(has a)” 상태에서 해당 객체의 기능을 활용하는 것입니다. 이 관계는 객체가 서로 독립적으로 변경될 수 있도록 하며, 시스템의 유연성을 극대화하는 데 기여합니다. 예를 들어, 도서관(Library) 객체는 여러 책(Book) 객체를 “가지고 있습니다.” 도서관은 책을 포함하지만, 도서관의 변경이 책 객체에 직접적인 영향을 미치지 않습니다. 이처럼 구성은 객체가 포함된 다른 객체의 기능을 활용할 수 있게 하면서, 두 객체 간의 결합도를 낮추는 방식으로 설계됩니다.
구성의 장점
결합도 낮추기
구성을 사용하면 객체 간의 결합도가 낮아집니다. 구성된 객체는 서로 독립적이기 때문에, 한 객체가 변경되더라도 다른 객체에 미치는 영향이 적습니다. 예를 들어, 도서관 시스템에서 도서 목록을 수정하더라도, 도서관 자체의 기능에는 영향을 주지 않습니다. 이러한 낮은 결합도는 유지보수성과 확장성에 큰 장점을 제공합니다.
유연한 확장
구성은 시스템의 유연한 확장을 가능하게 합니다. 객체는 필요한 기능을 수행하기 위해 다른 객체를 동적으로 포함할 수 있으며, 런타임에 객체를 교체하거나 새로운 기능을 추가할 수 있습니다. 이는 상속처럼 고정된 계층 구조와는 달리, 요구사항의 변화에 유연하게 대처할 수 있습니다.
동적 객체 사용
구성은 런타임에 객체를 동적으로 생성하고 포함할 수 있기 때문에, 프로그램 실행 중에 객체 간의 관계나 동작을 쉽게 변경할 수 있습니다. 예를 들어, 도서관 시스템에서 새로운 카테고리의 책이 추가되면 해당 카테고리를 동적으로 구성할 수 있습니다.
구성의 단점
설계 복잡성 증가
구성은 상속에 비해 객체 간의 관계가 더 복잡해질 수 있습니다. 여러 객체를 포함하여 기능을 구현하는 경우, 관리해야 할 객체와 그 관계가 늘어나 설계가 복잡해질 수 있습니다. 특히, 큰 시스템에서 구성된 객체 간의 협력 관계가 많아지면, 이를 추적하고 관리하는 데 어려움이 생길 수 있습니다.
성능 오버헤드
구성을 사용하면 객체 간의 메시지 전달과 메서드 호출이 많아질 수 있습니다. 객체가 다른 객체를 포함하여 그 기능을 사용할 때, 직접 상속받는 것에 비해 약간의 성능 오버헤드가 발생할 수 있습니다. 하지만 대부분의 애플리케이션에서는 이 성능 차이가 크게 문제가 되지 않습니다.
구성 예시
다음은 도서관 관리 시스템에서 구성을 사용하는 간단한 예시입니다. 도서관 객체는 여러 책 객체를 포함하며, 각 책의 정보를 출력하는 기능을 직접 처리합니다.
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public Book(string title, string author)
{
Title = title;
Author = author;
}
// 책의 정보를 출력하는 메서드
public string GetBookInfo() => $"Title: {Title}, Author: {Author}";
}
public class Library
{
private List<Book> books = new List<Book>();
// 도서관은 여러 책(Book)을 포함함
public void AddBook(Book book) => books.Add(book);
// 도서관에서 직접 책의 정보를 처리
public void PrintAllBooks()
{
foreach (var book in books)
{
Console.WriteLine($"Library has book - {book.GetBookInfo()}");
}
}
}
// 사용 예시
Book book1 = new Book("The Great Gatsby", "F. Scott Fitzgerald");
Book book2 = new Book("1984", "George Orwell");
Library library = new Library();
library.AddBook(book1);
library.AddBook(book2);
// 도서관에서 직접 책 정보를 처리하여 출력
library.PrintAllBooks();
Library
클래스는 여러Book
객체를 포함하고, 각 책의 정보를 직접 출력하고 있습니다. 이 방식은 구성을 사용하지만, 도서관이 직접 책의 정보를 처리하는 방식입니다.
위임이란?
위임Delegation은 구성Composition의 한 형태로, 한 객체가 다른 객체에게 특정 작업을 넘겨주는 설계 방식입니다. 객체가 직접 작업을 수행하지 않고, 다른 객체에게 위임함으로써 더 유연한 설계를 할 수 있습니다. 이를 통해 기능을 다양한 방식으로 변경할 수 있으며, 런타임에 객체 간의 협력 방식을 동적으로 조정할 수 있습니다. 예를 들어, 도서관 시스템에서 도서 정보를 출력하는 작업을 도서 객체에 위임하는 방식으로 설계할 수 있습니다. 이렇게 하면 도서관 객체가 직접 도서 정보를 처리하지 않고, 도서 객체가 그 작업을 처리하게 됩니다.
위임을 사용한 구성
다음은 앞선 예시를 개선하여, 도서 정보를 출력하는 작업을 위임을 통해 처리하는 방식입니다. 도서관 객체는 책 객체에 이 작업을 맡기고, 자신은 단순히 책의 정보를 출력하도록 요청합니다.
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public Book(string title, string author)
{
Title = title;
Author = author;
}
// 책의 정보를 출력하는 메서드
public void PrintInfo() => Console.WriteLine($"Title: {Title}, Author: {Author}");
}
public class Library
{
private List<Book> books = new List<Book>();
// 도서관은 여러 책(Book)을 포함함
public void AddBook(Book book) => books.Add(book);
// 위임: 책의 정보를 직접 처리하지 않고, 각 Book 객체에 위임
public void PrintAllBooks()
{
foreach (var book in books)
{
book.PrintInfo(); // Book 객체의 메서드를 호출하여 정보 출력
}
}
}
// 사용 예시
Book book1 = new Book("The Great Gatsby", "F. Scott Fitzgerald");
Book book2 = new Book("1984", "George Orwell");
Library library = new Library();
library.AddBook(book1);
library.AddBook(book2);
// 도서관은 각 책(Book) 객체에 정보를 출력하도록 요청함
library.PrintAllBooks();
Library
클래스는 책의 정보를 직접 처리하지 않고, 위임을 통해Book
객체가 스스로 정보를 출력하게 합니다. 이 방식은 객체 간의 역할을 분리하고, 도서관이 정보 처리에 관여하지 않도록 구성된 객체에 작업을 위임한 예시입니다.
맺음말
구성은 객체지향 설계에서 객체 간 결합도를 낮추고, 시스템의 유연성과 확장성을 높이는 매우 유용한 방식입니다. 도서관 관리 시스템과 같은 예시에서 볼 수 있듯이, 구성은 객체가 다른 객체를 포함하여 협력 관계를 형성하는 방식으로, 객체 간의 결합을 최소화하면서 기능을 쉽게 확장할 수 있습니다. 위임을 사용하면, 한 객체가 직접 작업을 처리하지 않고 다른 객체에게 책임을 넘김으로써 더 유연한 설계를 가능하게 합니다. 이러한 구성 방식은 특히 실무에서 요구 사항 변화에 유연하게 대처할 수 있는 강력한 패턴입니다.