객체 협력

객체 협력이란?

객체 지향 프로그래밍에서 객체 협력Object Collaboration은 여러 객체가 함께 상호작용하며 시스템의 기능을 수행하는 방식입니다. 객체들은 독립적으로 존재하는 것이 아니라, 서로의 기능을 활용하고 메시지를 주고받으며 협력하여 더 큰 작업을 수행합니다. 객체 협력은 OOP에서 중요한 개념으로, 객체가 서로 어떻게 상호작용 하는 지에 따라 시스템의 구조와 유연성이 크게 달라집니다.

객체 협력의 중요성

객체 협력은 책임 분리와 역할 분담을 통해 시스템을 구성합니다. 이를 통해 단일 객체에 너무 많은 책임이 몰리지 않도록 하고, 변화에 유연하게 대응할 수 있는 구조를 만듭니다. 각 객체가 자신의 역할을 충실히 수행하면서, 필요한 작업을 다른 객체와 협력하여 처리하기 때문에 객체 협력은 시스템의 확장성과 유지보수성에 중요한 영향을 미칩니다.

객체 협력의 주요 구성 요소

객체 협력은 크게 메시지 전송, 역할 분담, 책임 할당의 세 가지로 이루어집니다.

메시지 전송

객체 협력의 핵심은 객체 간의 메시지 전송입니다. 객체는 다른 객체에게 메시지를 보내어 요청을 수행하거나 정보를 요청합니다. 여기서 메시지는 단순한 함수 호출을 의미할 수 있으며, 메시지를 받은 객체는 자신이 가진 정보를 바탕으로 요청을 처리하거나 다른 객체와 다시 협력합니다.

역할 분담

각 객체는 협력 과정에서 고유한 역할을 수행합니다. 예를 들어, 도서관 관리 시스템에서 사용자 객체는 도서를 대출하는 역할을 맡고, 도서 객체는 대출 가능 여부를 판단하는 역할을 수행합니다. 각 객체가 담당하는 역할은 시스템의 요구 사항과 비즈니스 로직에 맞추어 정의됩니다.

책임 할당

책임 할당은 객체가 수행해야 하는 작업을 정의하는 과정입니다. 객체 협력에서는 각 객체가 자신이 맡은 책임을 충실히 이행하며, 필요할 경우 다른 객체에 작업을 위임합니다. 이를 통해 객체들은 독립적으로 동작할 수 있으며, 시스템이 변경되더라도 각 객체가 독립적으로 수정되거나 대체될 수 있습니다.

객체 협력 예시

도서관 관리 시스템을 예로 들어, 객체 협력이 어떻게 이루어지는지 살펴보겠습니다. 이 시스템에서는 User, Book, LibraryService 객체들이 서로 협력하여 도서 대출을 처리합니다.

public class User
{
    public int BooksBorrowed { get; private set; }
    private const int MaxBooksAllowed = 5;
    public bool CanBorrow() => BooksBorrowed < MaxBooksAllowed;
    public void BorrowBook(Book book)
    {
        if (CanBorrow())
        {
            book.Borrow();
            BooksBorrowed++;
        }
        else
        {
            throw new InvalidOperationException("Cannot borrow more than 5 books");
        }
    }
}
public class Book
{
    public bool IsBorrowed { get; private set; }
    public void Borrow()
    {
        if (IsBorrowed)
        {
            throw new InvalidOperationException("Book is already borrowed");
        }
        IsBorrowed = true;
    }
    public void Return() => IsBorrowed = false;
}
public class LibraryService
{
    public void BorrowBook(User user, Book book) => user.BorrowBook(book);
}
  • LibraryService: 사용자로부터 도서를 대출하려는 요청을 받습니다.
  • User 객체: 도서를 빌릴 수 있는지 확인하고, 가능하다면 Book 객체에 대출을 요청합니다.
  • Book 객체: 자신이 대출 가능한 상태인지 확인하고, 대출 가능한 경우 상태를 변경합니다. 이 예제에서는 LibraryService, User, Book 세 객체가 협력하여 도서 대출 기능을 구현하고 있습니다. 각각의 객체는 자신의 역할을 맡고 있으며, 서로 메시지를 주고받으며 협력합니다. User 객체는 책을 빌릴 수 있는지를 결정하고, Book 객체는 대출 가능한 상태인지 여부를 판단합니다.

객체 협력의 장점

  • 유연한 확장성: 객체들이 독립적으로 동작하면서도 협력하기 때문에, 새로운 기능을 추가하거나 객체의 내부 동작을 수정할 때 전체 시스템에 미치는 영향을 최소화할 수 있습니다.
  • 책임 분리: 각 객체가 자신의 책임을 명확히 갖고 있기 때문에, 코드를 읽고 유지보수하기가 쉬워집니다. 특정 객체의 역할이 명확해지면, 시스템이 복잡해지더라도 객체 간 상호작용을 이해하기 쉽습니다.
  • 변경 용이성: 객체 협력 구조를 잘 설계하면, 특정 객체가 변경되더라도 다른 객체와의 협력 관계가 유지됩니다. 이는 시스템 유지보수에 큰 도움이 됩니다.

객체 협력의 한계

  • 복잡성 증가: 객체가 너무 많거나, 협력 관계가 복잡해지면 시스템 전체의 복잡도가 증가할 수 있습니다. 이를 해결하기 위해 각 객체의 책임을 명확히 정의하고, 과도한 협력을 피하는 것이 중요합니다.
  • 높은 결합도: 협력 관계가 지나치게 밀접하면, 객체 간 결합도가 높아져 하나의 객체가 변경될 때 여러 객체를 동시에 수정해야 하는 문제가 발생할 수 있습니다. 이를 피하기 위해 적절한 인터페이스와 메시지 설계를 사용하는 것이 필요합니다.

맺음말

객체 협력은 객체지향 설계에서 매우 중요한 개념으로, 시스템을 모듈화하고 확장성 있는 구조로 만드는 데 핵심 역할을 합니다. 객체 간 명확한 책임 분담과 메시지 전송을 통해 협력을 설계하면, 시스템의 유연성과 유지보수성을 높일 수 있습니다.

심화 학습

인터페이스를 통한 객체 협력

객체 간 협력에서 인터페이스는 매우 중요한 역할을 합니다. 인터페이스를 사용하면, 객체가 구체적인 구현에 의존하지 않고 추상적인 계약에 의존하게 됩니다. 이는 객체 간의 결합도를 낮추고, 시스템의 유연성을 높입니다.

예시: 도서관 관리 시스템에서의 인터페이스 사용

public interface IBorrowable
{
    bool CanBorrow();
    void Borrow();
}
public class User : IBorrowable
{
    public int BooksBorrowed { get; private set; }
    private const int MaxBooksAllowed = 5;
    public bool CanBorrow() => BooksBorrowed < MaxBooksAllowed;
    public void Borrow() => BooksBorrowed++;
}
public class LibraryService
{
    public void BorrowItem(IBorrowable item)
    {
        if (item.CanBorrow())
        {
            item.Borrow();
        }
        else
        {
            throw new InvalidOperationException("Item cannot be borrowed");
        }
    }
}
  • 인터페이스의 활용: IBorrowable 인터페이스를 통해 User와 같은 여러 객체가 동일한 방식으로 대출 기능을 제공할 수 있습니다.
  • 유연한 설계: LibraryServiceIBorrowable 인터페이스를 구현한 객체를 인수로 받아 협력할 수 있기 때문에, 사용자 외에 다른 대출 가능한 객체들도 같은 방식으로 협력할 수 있습니다.

디미터 법칙을 적용한 협력

디미터 법칙은 객체가 직접적으로 자신의 관계에 있는 객체에게만 메시지를 보내도록 제한하는 원칙입니다. 이는 객체 간 결합도를 낮추고, 객체가 서로 지나치게 밀접한 관계를 갖지 않도록 하는 데 유용합니다.

예시: 도서관 시스템에서 디미터 법칙 적용

public class Library
{
    private List<Book> books = new List<Book>();
    public bool IsBookAvailable(string title)
    {
        var book = books.FirstOrDefault(b => b.Title == title);
        return book != null && !book.IsBorrowed;
    }
}
public class User
{
    private Library library;
    public User(Library library)
    {
        this.library = library;
    }
    public void BorrowBook(string title)
    {
        if (library.IsBookAvailable(title))
        {
            // 대출 로직 처리
        }
    }
}
  • 디미터 법칙 적용: UserLibrary에게 직접적으로 도서의 대출 가능 여부를 묻고, Book 객체에 직접 접근하지 않습니다. 이는 객체 간 결합도를 줄이고, 객체 간 협력을 간소화합니다.

위임을 통한 협력

객체 협력에서 위임은 특정 작업을 다른 객체에 맡겨서 처리하는 방식입니다. 위임을 통해 책임을 명확히 분리하고, 객체 간 역할을 효율적으로 분배할 수 있습니다.

예시: 도서 대출 위임 처리

public class User
{
    private BorrowingService borrowingService;
    public User(BorrowingService borrowingService)
    {
        this.borrowingService = borrowingService;
    }
    public void BorrowBook(Book book) => borrowingService.Borrow(book);
}
public class BorrowingService
{
    public void Borrow(Book book)
    {
        if (!book.IsBorrowed)
        {
            book.Borrow();
        }
        else
        {
            throw new InvalidOperationException("Book is already borrowed");
        }
    }
}
  • 위임의 사용: UserBorrowingService에 대출 기능을 위임하여, 직접 대출 로직을 처리하지 않고 서비스에 맡깁니다. 이를 통해 각 객체는 자신이 맡은 역할에만 집중할 수 있습니다.

이벤트 기반 객체 협력

이벤트 기반 객체 협력은 객체 간 결합도를 낮추기 위한 또 다른 방법입니다. 이벤트를 사용하면, 객체 간 직접적인 호출 없이 이벤트를 통해 협력할 수 있습니다. 이는 비동기적인 협력 관계를 지원하며, 객체 간의 유연성을 극대화할 수 있습니다.

예시: 도서 대출 이벤트 시스템

public class BookBorrowedEvent
{
    public string BookTitle { get; }
    public int UserId { get; }
    public BookBorrowedEvent(string bookTitle, int userId)
    {
        BookTitle = bookTitle;
        UserId = userId;
    }
}
public class EventDispatcher
{
    public event Action<BookBorrowedEvent> OnBookBorrowed;
    public void DispatchBookBorrowedEvent(Book book, User user)
    {
        OnBookBorrowed?.Invoke(new BookBorrowedEvent(book.Title, user.Id));
    }
}
public class LibraryService
{
    private EventDispatcher eventDispatcher;
    public LibraryService(EventDispatcher eventDispatcher)
    {
        this.eventDispatcher = eventDispatcher;
    }
    public void BorrowBook(User user, Book book)
    {
        // 대출 처리 로직
        eventDispatcher.DispatchBookBorrowedEvent(book, user);
    }
}
  • 이벤트 기반 협력: LibraryServiceEventDispatcher를 사용하여 책이 대출되었을 때 이벤트를 발생시킵니다. 이를 통해 다른 객체가 이 이벤트를 구독하고 적절히 반응할 수 있습니다.

응집도를 고려한 객체 협력

객체 협력에서 중요한 개념 중 하나는 응집도Cohesion입니다. 객체 내부의 메서드와 데이터가 서로 얼마나 강하게 연관되어 있는지를 나타내며, 높은 응집도를 가진 객체는 협력이 더 효율적입니다.

예시: 도서 대출 서비스에서의 응집도

public class Book
{
    public string Title { get; private set; }
    public bool IsBorrowed { get; private set; }
    public Book(string title)
    {
        Title = title;
        IsBorrowed = false;
    }
    public void Borrow() => IsBorrowed = true;
    public void Return() => IsBorrowed = false;
}
  • 응집도 높은 설계: Book 객체는 자신의 상태를 관리하고, 대출 및 반납에 대한 책임을 가지고 있습니다. 이는 높은 응집도를 보여주는 예로, 객체가 자신의 상태와 관련된 모든 기능을 스스로 처리합니다.