본 게시물은 조영호님의 저서 오브젝트를 학습하면서 정리한 내용입니다.

객체지향 개발자라면 꼭 알아야할 내용들이 가득한 책이기 때문에 참고만하시고 반드시 책을 구매해서 읽어보길 권해드려요!

(네이버 도서 구매링크 클릭)

저작물에 대한 문제가 발생할 경우 비공개 처리하겠습니다.


 

 

 

객체지향 프로그래밍을 향해

협력, 객체, 클래스

  • 객체지향은 객체를 지향하는 것
  • 프로그램을 작성할 때 클래스를 가장 먼저 떠올릴게 아니라, 객체 자체를 지향해야 함
    • 어떤 클래스가 아닌, 어떤 객체가 필요한지를 고민하라
      • 객체가 어떤 상태와 행동을 가지는지 결정
    • 객체를 기능 구현하기 위해 협력하는 공동체의 일원으로 보아라
      • 객체를 고립된 존재로 보면 안됨
      • 객체를 협력자로 바라보아야 함
    ⇒ 객체의 모양과 윤각이 잡힌 후, 공통된 특성과 상태를 가진 객체들을 타입으로 분류 → 이 타입을 기반으로 클래스 구현

 

도메인의 구조를 따르는 프로그램 구조

  • 도메인은 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야
  • 일반적으로 클래스의 이름은 대응되는 도메인 개념의 이름과 동일 또는 유사하게 지어야 함 클래스 사이의 관계도 최대한 도메인 개념의 관계와 유사하게 만들어야 함→ 프로그램 구조를 이해하기 쉽고 예상 가능하게 만들기 위함⇒ 현실의 도메인 구조와 동일하게 매핑되어 있는 클래스 구조

 

클래스 구현하기

  • 도메인 개념들의 구조를 반영하는 적절한 클래스 구조를 만든 후 → 적절한 프로그래밍 언어로 구현
  • 클래스를 구현할 때 가장 중요한 것 : 클래스 내부와 외부의 경계를 지어주는 것
    • 어떤 부분을 외부에 공개할지 정하는 작업
    ex)

자율적인 객체

  • 객체는 상태행동을 함께 가지는 복합적인 존재
  • 객체는 스스로 판단하고 행동하는 자율적인 존재

⇒ 객체지향이란 객체라는 단위 안에 데이터와 기능을 한 덩어리로 묶음으로써 적절하게 표현

  • 스스로 상태를 관리하고, 판단하고, 행동하는 자율적인 객체 공동체 구성하는 것
    • 외부에서 객체가 어떤 상태인지, 어떤 생각을 하고있는지 알면 안 됨
    • 직접적으로 개입해도 안 됨

 

프로그래머의 자유

  • 프로그래머의 역할
    • 클래스 작성자
      • 프로그램에 새로운 데이터 타입 추가
      • 클라이언트 프로그래머에게 필요한 만큼만 공개 (구현은닉) → 클라이언트 프로그래머에 대한 영향 최소화
    • 클라이언트 프로그래머
      • 클래스 작성자가 추가한 데이터 타입 사용
      • 필요한 클래스들을 엮어 애플리케이션 구축
      • 내부의 구현은 무시하고 인터페이스만 구현하여 프로그램 구축 → 코드를 자유롭게 수정 가능
    ⇒ 클래스를 개발할 때마다 인터페이스와 구현을 깔끔하게 분리하기 위한 노력을 해야 함
  •  
  • 설계가 필요한 이유는 변경을 관리하기 위함 → 파급효과를 제어, 변경으로 인한 혼란 최소화
💡
프로그래머의 역할을 나누는 시각이 참신하다. 프로그래머의 역할을 나누게되니 설계를 더 명확하게 바라볼 수 있는 것 같다. 간혹 우리는 클래스작성자가되기도 하고 동시에 클라이언트 프로그래머가 되기도 한다. 이 때, 역할을 머릿속으로 나누어 두었다면 구현하는 과정에서도 명확하게 외부와 내부를 구분하여 개발할 수 있지 않을까

 

협력하는 객체들의 공동체

  • 객체지향은 도메인의 의미를 풍부하게 표현할 수 있음
  • 어떤 기능을 구현하기 위해 객체들 사이에 상호작용을 함 (협력)
  • 협력 중심의 프로그래밍
    • 협력관점에서 어떤 객체가 필요한지 식별
    • 객체들의 공통상태, 행위를 구현하기위한 클래스 작성
  • 추상화, 상속, 다형성을 활용해 위임을 함 → 객체 간의 협력
    • 추상클래스
      • 여러 클래스의 중복 코드를 제거할 때 활용
      • 실제 애플리케이션에서는 추상클래스로 직접 인스턴스를 만들 필요가 없을 때 사용 가능
    • TEMPLATE METHOD 패턴
      • 부모 클래스에서는 기본적인 알고리즘 흐름을 구현,
      • 중간에 필요한 처리는 자식 클래스에게 위임하는 방식
    • ex) 직접 계산을 수행하지 않고 discountPolicy에게 위임

 

  • 생성자를 통해 객체 생성시 필요한 정보를 강제함 → 온전한 객체 생성 보장

 

💡
프로그램을 설계할 때, 협력을 염두에 두어야 한다. 구체적으로.. 협력관점에서 객체를 설계하고 책임에 따라 적절한 메서드를 수행할 수 있도록 위임해야 한다. 필요에 따라서는 패턴을 활용해서 위임이 적합하게 될 수 있도록 해야하고 생성자를 통해 객체가 온전하게 생성될 수 있도록 관리해야 한다.

 

 

 

 

상속과 다형성

  • 상속과 다형성을 사용해서 실행 시점에 의존성을 갖도록 구현

 

compile time 의존성 vs runtime 의존성

  • 의존성을 갖는 케이스
    • 어떤 클래스가 다른 클래스에 접근할 수 있도록 경로 가지고 있는 경우
    • 다른 클래스의 객체의 메서드를 호출하는 경우
  • 인스턴스 실행 시에 인스턴스간의 의존성 ≠ 코드 레벨에서 클래스 간의 의존성
    • ex)
    • discountPolicy에 의존하고 있고 discountPolicy의 상세 구현은 생성자를 통해 생성 시점에 결정됨
  • 클래스 사이의 의존성과 객체 사이의 의존성은 동일하지 않음
  • 클래스 사이의 의존성과 객체 사이의 의존성이 다를수록
    • 코드 이해가 어려워짐 (코드의 의존성 이외의 객체의 의존성까지 파악해야하기 때문)
    • 코드가 더 유연해지고 확장 가능해짐
  • 유연해질수록 디버깅이 어려워짐
  • 유연성을 억제할수록 디버깅은 쉬워지지만 확장 가능성과 유연성은 떨어짐

 

⇒ 트레이드 오프가 존재 → 무조건 유연한 것도, 무조건 읽기 좋은 것도 다 정답은 아님

💡
의존성을 두가지로 분리하고 일치하는지, 일치하지 않는지에 따라 분석하는 것이 인상깊다. 무조건 유연한 설계만을 추구하는 것이 옳은 것인가? 유연한 설계에 따라오는 댓가가 존재한다는 것을 잊고 있었다. 유연한 설계로 나아가되 어떤걸 트레이드오프했는지 정확하게 이해하고 있는 것이 중요하다.

 

차이에 의한 프로그래밍

  • 보모 클래스와 다른 부분만 추가해서 새로운 클래스를 얻어내는 것을 차이에 의한 프로그래밍이라고 함
  • 상속은 코드재사용에 널리 사용되는 방법
  • 기존 클래스가 가지고 있는 모든 속성과 행동을 물려받음
    • 기반 클래스를 통해 쉽고 빠르게 새로운 클래스를 추가할 수 있음

 

상속과 인터페이스

  • 상속이 가치있는 이유 : 부모 클래스가 제공하는 모든 인터페이스를 자식 클래스가 물려받을 수 있는 것→ 자식클래스는 부모클래스의 모든 메시지를 수신할 수 있음 ⇒ 외부 객체가 클래스를 부모클래스와 동일한 타입으로 간주함 (업캐스팅)
    • 일반적으로 코드를 재사용하기 때문에 가치있다고 보지만 이러한 관점과 거리가 있음
  • 구현상속 vs 인터페이스 상속
    • 구현상속 (서브클래싱) : 코드를 재사용하기 위해서 상속을 사용
    • 인터페이스상속 (서브타이핑) : 인터페이스를 공유하기위해서 상속
    → 코드 재사용을 상속의 목적으로 이해하면 안됨! (변경에 취약한 코드를 만들어지기 때문)인터페이스 상속을 위해 사용되어야 함

 

다형성

  • 메시지와 클래스가 다른 개념이라는 것에서부터 출발
  • 동일한 메시지를 수신하는 클래스에 따라서 다른 방식(메서드구현체)로 실행되는 것
  • 다형성으로 인해 컴파일 시간 의존성과 실행 시간 의존성이 달라짐
  • 메시지와 메서드를 실행 시점에 바인딩하게 함 ( 동적바인딩 or 지연바인딩)

 

인터페이스와 다형성

  • 순수하게 인터페이스만 공유하고 싶을 때 사용
  • 구현에 대한 고려 없이 다형적인 협력에 참여하는 클래스들이 공유 가능한 외부인터페이스 정의

 

💡
상속을 일반적의 코드의 재사용성 극대화하기 위해 생각하곤 했다. 하지만 상속이 가치있는 이유는 부모클래스에서의 메시지를 사용할 수 있다는 관점이 새로웠다. 오히려 소스를 재사용하면 강력하게 의존되기 때문에 변경에 취약하다는 것을 유의하자.

 

추상화와 유연성

추상화의 힘

  • 추상화 계층만 따로 떼어 놓고 살펴 볼 수 있음 → 요구사항의 정책을 높은 수준에서 서술
    • 새로운 클래스가 추가되더라도 상위 단계에서의 협력 흐름을 그대로 따름
  • 설계가 유연해짐
    • 구체적인 상황에 결합되는 것을 방지함
    • 조건문으로 분기하거나 기존 코드를 수정하는 것이 아닌 확장할 수 있음 → 협력의 일관성을 유지하기 쉬워짐
    • 컨텍스트 독립성 키워드 참고

 

추상 클래스와 인터페이스 트레이드오프

  • 구현과 관련된 모든 것들은 트레이드오프의 대상이 됨
  • 의도를 효과적으로 전달하고 코드가 명확해질 수 있지만 반대로 과잉 조치가 될 수 있음

 

코드 재사용

  • 코드 재사용에 가장 널리 사용되는 방법은 상속
  • 그렇지만 상속이 가장 좋은 방법은 아님
  • 상속보다는 합성
    • 합성 : 다른 객체의 인스턴스를 자신의 인스턴스 변수로 포함해서 재사용하는 방법

 

상속

  • 설계에 악영향
    • 상속은 캡슐화를 위반함
      • 부모클래스의 구현이 자식클래스에 노출됨
      • 캡슐화의 약화로 인해 부모클래스와 자식클래스가 강하게 결합될 수 있음
    • 설계를 유연하지 못하게 만듦
      • 상속으로 인한 클래스간 관계는 컴파일 시점에 결정됨 → 실행시점에 변경하는 것이 불가능

 

합성

  • 인터페이스를 통해 약하게 결합
  • 상속에서의 두가지 문제를 해결함
    • 인터페이스 메시지를 통해 재사용하기 때문에 효과적인 캡슐화 가능
    • 메시지를 통해 느슨하게 결합 → 설계 유연 해짐

 

코드를 재사용하기 위해선 상속보단 합성

다형성을 위해 인터페이스를 재사용하는 경우 상속과 합성을 조합해서 사용할 수 있음

 

💡
상속보단 합성이라는 이 유명한 말에 대해서 ‘왜?’라는 의문을 제대로 던져 보지 않았다. 이유있는 소스라는 건 별 것 아닌것 같아보이거나 당연해 보이는 것에 대해서도 물음을 던져봐야 하는게 아닐까? 결합도가 높아진다는 것 이면에 존재하는 근본적인 이유는 상속을 하면 부모의 클래스에 자식 클래스가 직접 접근하는데에 있다. 유연한 설계는 런타임과 컴파일시간의존성이 달라야 하는데 상속은 같을 수밖에 없다는 부분은 생각을 못했던 부분이기도 하다.

 

 

+ Recent posts

"여기"를 클릭하면 광고 제거.