글쓰기/독서

[오브젝트 1, 2장] 객체, 설계, 객체지향 프로그래밍

호호맨 2022. 2. 15. 00:55

들어가며

객체지향의 오해와 진실의 심화 버전이라는 느낌이 드는 책입니다. 객체지향에 대해 잘 모르거나 고민을 하고 있다면 오브젝트는 사이다가 되어줄 책인 것 같습니다. 모든 내용을 블로그에 정리하지는 않습니다. 새롭게 알게 된 지식이나 중요하다고 생각하는 부분을 기록한 것이기 때문에 더 자세한 내용은 반드시 책을 읽어보는 것을 추천드립니다.

Chapter 01 객체, 설계

의존성은 설계를 어렵게 만든다. 불필요한 의존성을 제거하고 캡슐화를 통해 객체 사이의 결합도를 낮춘다. 객체의 자율성을 높이고 응집도 높은 객체들이 협력하는 것이 훌륭한 객체지향이다.
단순히 출력을 위한 메서드가 아니라면 get() 메서드를 의식적으로 사용하지 말아 보자. get() 메서드는 의존성을 높이고 책임을 넘기게 된다. 객체 스스로가 수행할 책임이 생기게 되고 의존성이 줄어든다. 하지만 모든 조건을 만족하는 설계는 불가능에 가깝다. 적절한 트레이드오프가 필요하다. 설계는 적절한 균형이 필요하고 훌륭한 설계는 적절한 트레이드오프의 결과다.
객체지향 설계는 요구사항이 변경되어도 유연하게 대응할 수 있는 가능성을 높여준다. 객체가 자신의 데이터를 스스로 책임지는 자율적인 존재로 만들어 메시지를 통해 객체들이 상호작용을 하도록 고민해보자.

Chapter 02 객체지향 프로그래밍

객체지향 프로그램을 작성할 때 어떤 클래스가 필요한지 고민하지만 이건 객체지향의 본질과는 거리가 있다. 객체지향은 말 그대로 객체를 지향하는 것이다.
첫째로 어떤 객체가 필요한지 고민한다. 클래스는 객체의 공통적인 상태와 행동을 추상화한 것이다. 따라서 어떤 상태와 행동이 필요한지 결정한다.
둘째로 객체를 독립적인 존재가 아니라 협력하는 공동체의 일원으로 바라본다. 이것은 설계를 유연하고 확장 가능하게 만든다.
→ 훌륭한 협력이 훌륭한 객체를 만들고 훌륭한 객체가 훌륭한 클래스를 만든다.

2장을 읽으면서 가장 충격(?) 받고 인상 깊었던 코드는 Money라는 객체를 설계한 방식이다. 단순히 double형이나 bigDecimal을 필드로 사용할 수 있는데 Money라는 클래스로 분리하여 만든 방법이다. 간단하게 코드를 작성해보겠다.

public class Movie {
    private BigDecimal money;     // money를 객체로 분리한다.
    
    ...
}
public class Money {
    // 이런식으로 반환할 수 있구나에서 충격
    private static final Money ZERO = Money.wons(0);

    private final BigDecimal amount;

    public Money(BigDecimal amount) {
        this.amount = amount;
    }

    // 외부에서 amount를 가져가 더하는게 아니라 메세지를 던지는 방법
    public Money plus(Money amount) {      
        return new Money(this.amount.add(amount.amount));
    }
    
    ...
    
}

객체

  • 상태와 행동을 가진다.
  • 스스로 판단하고 행동하는 자율적인 존재다.

협력

  • 객체와 객체가 상호작용을 하는 방법은 메시지전송하고 수신하는 것이다.
  • 객체는 스스로의 결정에 따라 자율적으로 메시지를 처리할 방법을 결정한다.
  • 메시지를 처리하기 위한 자신만의 방법이 메서드다.

의존성

  • 코드의 의존성과 런타임 시점의 의존성이 다르면 다를수록 코드를 이해하기 어려워진다.
  • 코드의 의존성과 런타임 시점의 의존성이 다르면 다를수록 더 유연해지고 확장 가능하다.
  • 의존성의 양면성은 트레이드오프다.
  • 그 말은 설계가 유연해질수록 코드를 이해하고 디버깅하기 어려워진다.
  • 유연성을 억제하면 코드를 이해하기 쉽지만 재사용성과 확장 가능성이 낮아진다.
  • 무조건 유연한 설계, 무조건 읽기 쉬운 코드가 정답이 아니다.

다형성

  • 동일한 메시지를 전송하지만 실제로 어떤 메서드가 실행될 것인지는 메시지를 수신한 객체의 클래스가 무엇이냐에 따라 달라진다.
  • 다형성은 컴파일 타임 의존성과 런타임 의존성이 다를 수 있다는 사실을 기반으로 한다.
  • 다형성은 동일한 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답할 수 있는 능력
  • 다형적인 협력에 참여하려면 인터페이스가 동일해야 한다.
  • 다형성은 메시지에 응답하기 위해 실행될 메서드를 컴파일 시점이 아닌 런타임 시점에 결정한다.
  • 이를 지연 바인딩, 동적 바인딩이라고 부른다.
  • 상속은 인터페이스를 재사용할 목적으로 상속을 해야 한다. 구현을 재사용할 목적으로 상속을 사용하면 변경에 취약한 코드를 야기한다.

상속

  • 상속은 캡슐화를 위반한다.
  • 부모 클래스의 내부 구조를 잘 알고 있어야 한다.
  • 부모 클래스의 구현이 자식 클래스에 노출되므로 캡슐화가 약화된다.
  • 캡슐화의 약화는 자식 클래스가 부모 클래스에 강하게 결합되도록 만들어 부모가 변경되면 자식이 변경될 가능성이 크다.
  • 설계를 유연하지 못하게 만든다.
  • 상속은 부모 클래스와 자식 클래스 사이의 관계를 컴파일 시점에 결정한다.
  • 런타임 시점에 객체를 동적으로 변경하는 것이 불가능하다.

합성

  • 인터페이스에 정의된 메시지를 통해서만 코드를 재사용하는 방법
  • 합성은 상속의 2가지 문제를 해결한다.
  • 인터페이스에 정의된 메시지를 통해서만 재사용이 가능하므로 캡슐화가 가능하다.
  • 의존하는 인스턴스를 교체하기 쉽기 때문에 설계를 유연하게 만든다.
  • 그렇다고 상속을 절대 사용하지 말라는 것이 아니다.
  • 다형성을 위해 인터페이스를 재사용하는 경우에는 상속과 합성을 조합해서 사용할 수밖에 없다.



→ 구현과 관련된 모든 것들은 트레이드오프의 대상이 될 수 있다. 그렇기 때문에 모든 코드에는 합당한 이유가 있어야 한다. 트레이드오프를 통해 얻어진 결론과 그렇지 않은 결론은 차이가 크다. 객체지향에 정답은 없다. 언제나 트레이드오프를 해야 하고 무엇이 더 나은지 고민해야 한다. 그래서 객체지향이 어렵게 느껴지면서도 재밌는 이유다.
객체지향에서 가장 중요한 것은 기능을 구현하기 위해 협력에 참여하는 객체들 사이의 상호작용이다. 객체들은 협력에 참여하기 위해 역할을 부여받고 역할에 적합한 책임을 수행한다.



책에서는 인터페이스, 유연한 설계 등 더 많은 내용을 예제 코드와 함께 구체적으로 설명을 해줍니다.
자세한 내용은 조영호님의 오브젝트를 참고하는 걸 추천드립니다.