[Ch.2] 객체지향 프로그래밍

신은지·2022년 11월 19일
0


오브젝트 : 코드로 이해하는 객체지향 설계 (조영호 저) 를 읽고 정리합니다.


객체지향 프로그래밍

객체지향의 핵심은 애플리케이션 기능 구현을 위해, 객체들이 협력하며 상호작용하는 것!

  • 프로그래밍 관점에 치우쳐서 객체지향을 바라보지말자.
    • 프로그래밍 관점에서 생각해보면, 우리는 기능을 만들기 위해 클래스를 짜고, 클래스 안에 메소드와 변수들을 채워넣어야한다.
    • 하지만? 우리는 클래스들이 서로 상호작용한다고 생각하는게 아니라, 객체들이 서로 상호작용한다고 생각해야한다.

🤷 그럼 뭘 생각하며 객체지향을 구현해요?

  1. 어떤 객체가 필요한가?
    : 기능 구현을 위해 어떤 클래스가 필요한지를 고민하는게 아니라, 어떤 객체가 필요한지 고민해야 한다.

  2. 객체는 기능을 구현하기 위한 협력체의 일원!
    : 기능 구현을 위해 다른 객체에게 도움을 주거나 의존하고 있는지 생각해야 한다.


🤷‍♀️ 예?

무슨 소린지 모르겠다면, 도메인을 활용해 이해해보자.

  • 도메인 (domain) : 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야

    • 우리가 구현하려는 기능 = 문제를 해결하려는 수단 = 도메인
      • Ex. 문제 : 영화를 보고 싶은데, 보려면 티켓이 필요해!
      • Ex. 수단(도메인) : 티켓 예매
  • 요구사항부터 구현까지 전부 객체의 관점으로 바라보자

    • 도메인을 구성하는 개념들을 나열하고, 그 개념들을 객체와 클래스로 재구성하자.
      • Ex. 티켓 예매 도메인을 구현하려면 어떤 상태와 행동이 필요할까?
        : 영화, 예매, 상영, 영화별 적용되는 할인 정책, 할인 정책1, 정책 2...
      • Ex. 이 상태와 행동들 중 유사한 친구들을 묶어서 하나의 개념으로 만들자.
        : 할인정책1과 할인정책2는 >할인 정책< 이라는 점에서 공통점을 가지니까, 하나로 묶자.
      • Ex. 이렇게 만든 개념을 클래스로 구현하자.
        : 할인정책은 할인정책1이라는 객체와 할인정책2라는 객체를 가질 수 있는 클래스로 정리된다.
  • 즉, 클래스의 구조가 도메인의 구조와 유사할 때 프로그램의 구조를 이해하고 예상하기 쉽다.


오케. 그럼 클래스는 어떻게 구현해야 하나요?

좋은 코드는? 변경하기 쉬운 코드. 변경하기 쉬운 코드는? 이해하기 쉬운 코드!

🧚 우리는 객체가 자율적인 존재라고 약속했어요!

  • 객체가 자율적이고, 외부에 덜 의존적이게 되려면 외부 간섭을 최소화해야 한다.
    • 나는 떡볶이 먹고 싶을 때 떡볶이 먹을거고, 만두 먹고 싶으면 만두 먹을건데 이상한 애가 찾아와서 아냐 넌 만두를 먹으려면 단무지가 필요한 애야~~ 하면 성질나니까..!
  • 이를 위해 객체는 상태와 행동을 객체 내부로 묶고 (= 캡슐화), 외부에서의 접근은 허가된 친구만 가능하도록 제어한다 (= 접근 제어).
    • 인터페이스와 구현의 분리 원칙
      • 퍼블릭 인터페이스 (public interface) : 외부에서 접근 가능
      • 구현 (implementation) : 외부에서 접근 불가능, 내부에서만 접근 가능

🤨 객체 접근 제어가 자율성에 어떻게 영향을 미치는지 잘 모르겠어요.

  • 중요한건 변경을 관리하는 것!
    • 프로그래머를 두 종류로 구분해보자.
      • 클래스 작성자 : 새로운 데이터 타입을 프로그램에 추가한다
      • 클라이언트 프로그래머 : 클래스 작성자가 만든 데이터 타입을 사용한다
      • 클래스 작성자 A씨는 내가 신나게 짠 코드가 클라이언트 B씨에게 어떤 영향을 줄 지 걱정하고, 클라이언트 B씨는 빠르게 기능을 만들고 싶은데 A씨가 코드를 갑자기 바꿀까봐 걱정한다.
    • 하지만 A씨의 코드 핵심은 안에 숨겨두고, 이 코드에 접근하는 간접적인 방법만 B씨에게 알려준다면?
      • A씨는 내부 로직만 변경하면 되고, B씨는 내부 로직에 무관하게 접근 통로만 신경쓰면 된다!
  • 우린 이걸 구현 은닉 (implementation hiding) 이라고 부르기로 했어요
    • 정리 : 객체로의 접근을 제어해 사용자 영향 고려 없이 내부 구현을 변경 가능하게 만들 수 있다.

😯 근데.. 객체는 협력해야 한다고 하지 않으셨나요?

B씨가 A씨의 코드에 접근하는 간접적인 방식 = 객체 상호 작용 = 메시지를 주고 받는 행위

  • 객체는 다른 객체와 협력할 때, 협력을 요청하는 메시지를 보낸다.

    • 주의할 점은, 메시지 != 문제의 해결 이라는 점이다.
      • 문제의 해결은 메시지를 받은 객체가 메서드를 통해 스스로 해결한다.
      • 메시지를 보낸 객체는 메시지를 받은 객체가 문제를 어떻게 해결하는지 전혀 알 수 없다.

근데 우리 아까 비슷한 친구들을 묶어놓지 않았나요?

메시지를 받은 객체가 어떤 친구로 응답해야 하는지 어떻게 알 수 있을까?

🐳 상속은 몬가요..?

  • 기존 클래스를 기반으로 새로운 클래스를 만드는 행위
    • 서로 공통점이 있지만, 차이점도 있는 클래스를 쉽게 추가할 수 있다. (= 차이에 의한 프로그래밍)
  • 그렇다고 목적이 코드 재사용은 아니에요~
    • 부모의 인터페이스를 자식이 물려받을 수 있게 만드는게 중요!
      • 즉 구현이 아닌 인터페이스 상속이 객체지향적으로 가치있는 상속
    • 외부 객체가 봤을 때, 자식과 부모를 동일한 타입이라고 생각하게 만드는게 주 목적이다.
  • 템플릿 메서드 (Template Method) 패턴
    • 부모는 기본적인 알고리즘 흐름을 구현하고, 중간에 필요한 처리는 자식에게 위임하는 패턴
  • 코드 (클래스) 의존성과 실행 시점 (객체) 의존성이 다를 때 확장 가능하다.
    • 객체는 내가 어떤 클래스의 인스턴스와 협력하는지가 아니라, 내가 협력하는 친구가 내 메시지를 수신할 수 있는지 아닌지가 중요하다.
    • 따라서 객체가 소통할 수 있는 창구를 부모에게 터두고 처리 자체는 담당 자식에게 맡기자.
      • 상속을 통해 자식에게 부모의 인터페이스가 존재하기에 자식은 부모를 대신(업캐스팅)할 수 있다.
    • 단, 이 경우 기능은 유연해져도 코드 가독성은 떨어질 수 있다 ^.ㅠ

🐳 다형성은요?

  • 동일한 메시지를 전송했을 때, 메시지 수신 객체 클래스에 따라 실행되는 메서드가 달라지는 것
    • 이를 위해 협력에 참여하는 객체들은 모두 같은 메시지를 이해할 수 있어야 한다.
    • Ex. 부모가 받을 수 있는 동일한 메시지를 전송했을 때, 각기 다른 자식이 메시지를 처리한 경우
  • 메시지에 응답할 (= 실행될) 메서드를 컴파일 시점이 아닌 실행 시점에 정한다.
    • 메시지와 메서드를 실행 시점에 바인딩한다 = 지연 바인딩 = 동적 바인딩
  • 자식 클래스들이 순수하게 인터페이스만 공유하는 경우는?
    • 마찬가지로 동일한 인터페이스를 공유하면서, 부모를 대신해 사용될 수 있기에 업캐스팅이 적용되며 다형적!

즉 상속과 다형성에는 추상화가 깔려있다.


추상화

최대한 인터페이스에 초점을 맞추고, 자식 클래스에게 결정권을 일임하는 것


📌 추상화를 통해 요구사항 정책을 높은 수준에서 서술할 수 있다.

세부적인 내용을 무시한 채 상위 정책을 쉽고 간단하게 표현할 수 있다는 의미

  • 자식 하나하나를 언급하는게 아니라, 인터페이스 수준에서 설명할 수 있으니까!
    • Ex. 할인 정책 1, 할인 정책 2로 구분하지 않고 전체 할인 정책으로 구분
  • 추상화를 통해 애플리케이션의 기본 협력 흐름을 기술할 수 있다 (= 상위 정책 기술)
    • 디자인 패턴, 프레임워크 등 재사용 가능한 설계들은 모두 위와 같은 메커니즘을 활용한다.

📌 추상화를 통해 유연하게 설계할 수 있다.

책임의 위치를 정하기 위해 조건문을 사용하기보다는, 예외 케이스를 최소화하고 일관성을 유지해라.

  • 설계는 포괄적으로 하고, 세부 사항은 자식이 처리하게 결정 위임
    • 기존 구조를 수정하지 않으면서 새로운 기능을 쉽게 추가 & 확장할 수 있다.
  • 컨텍스트 독립성 (context independency)
    • 설계가 구체적인 상황에 결합되지 않아, 어떤 클래스와도 협력이 가능하다.

📌 트레이드 오프

  • 구현과 관련된 모든 것들이 트레이드 오프의 대상이 될 수 있다.
    • Ex. 이상적으로는 인터페이스를 활용해 책임을 명확하게 하는게 좋지만, 또 현실적으로 생각했을 때 아주 작고 단순한 기능 하나만을 위해 인터페이스를 추가하는건 과할 수 있다.
  • 따라서 모든 코드를 합당한 이유를 기반으로 설계하고 작성해야 한다.

코드 재사용

코드를 재사용할 땐 상속 말고 합성을 쓰기로 약속해요~

  • 합성 : 다른 객체의 인스턴스를 자신의 인스턴스 변수로 포함해서 재사용하는 방법

🔨 상속을 이용한 코드 재사용의 문제점

클래스를 통한 강결합

  • 캡슐화 위반
    • 부모 클래스의 구현이 자식 클래스에게 노출됨
    • 즉, 자식이 부모에게 강하게 결합되고, 부모의 변경에 따라야 할 가능성이 생겨 코드 변경하기 어려워진다
  • 설계를 유연하지 않게 만듬
    • 부모 클래스와 자식 클래스의 관계를 컴파일 시점에 결정한다
    • 따라서 실행 시점에 객체 종류를 변경할 수 없다

🔨 합성을 이용한 코드 재사용의 장점

인터페이스를 통한 약결합

  • 인터페이스에 정의된 메시지를 통해서만 코드를 재사용할 수 있다
    • 메서드를 외부에 제공한다는 것만 알고, 내부 구현에 대해서는 전혀 모르는 상태!
    • 즉, 상속과 달리 인터페이스를 통해 약하게 결합된다.
  • 따라서 구현을 효과적으로 캡슐화할 수 있고, 변경시 의존하는 인터페이스만 교체하면 되어 변경이 쉽다.

⚒ 그렇다고 상속을 쓰지 말라는건 아니고..!

  • 코드를 재사용할 때는 : 합성
  • 다형성을 위해 인터페이스를 재사용할 때는 : 상속과 합성을 함께 조합하기

객체지향의 핵심은 객체들의 협력과 상호작용이 적절하게 이루어져야 한다는 것!

  • 적절한 협력을 식별하고, 적절한 객체에게 협력에 필요한 역할을 할당하자
  • 프로그래밍 관점에 치우치는게 아니라, 객체를 지향하는 관점에서 설계해야한다.
profile
호그와트 장학생

0개의 댓글