[오브젝트] 6주차 (25일차 ~ 30일차)

da__ell·2023년 9월 23일
0

독서 - 오브젝트

목록 보기
25/25
post-thumbnail

[오브젝트] 25일차

p. 322 - 335

10-2. 취약한 기반 클래스 문제

상속은 자식 클래스와 부모 클래스의 결합을 높인다. → 취약한 기반 클래스 문제

겉으로 보기에는 안전한 방식으로 기반 클래스를 수정한 것처럼 보이더라도 이 새로운 행동이 파생 클래스에 상속될 경우 파생 클래스의 잘못된 동작을 초래할 수 있기 때문

상속을 통해 자식 클래스를 점진적으로 추가해서 기능을 확장하는데는 용이하지만 높은 결합도는 부모 클래스의 개선을 어렵게 만듦 + 최악의 경우 자식 클래스를 모두 동시에 수정해야 할 수도

객체는 변경될지 모르는 불안정한 요소들을 캡슐화함으로써 파급효과를 걱정하지 않고 자유롭게 내부를 변경할 수 있다.

상속을 사용하면 부모 클래스의 구현을 변경해도 자식 클래스가 영향을 받기 쉽다.

불필요한 인터페이스 상속 문제

stack의 vector의 상속 : stack의 규칙을 무너뜨리는 vector의 퍼블릭 인터페이스 상속
(ex. stack은 add 메서드를 상속하지 않지만 vector를 상속하므로 해당 메서드를 상속함)

상속받은 부모 클래스의 메서드가 자식 클래스의 내부 구조에 대한 규칙을 깨뜨릴 수 있다.

메서드 오버라이딩의 오작용 문제

조슈아 블로치 : 클래스가 상속되기를 원한다면 상속을 위해 클래스를 설계하고 문서화해야 하며, 그렇지 않은 경우 상속을 금지시켜야 한다. → 내부 구현의 문서화 but 객체지향의 핵심인 구현을 캡슐화하는 것과 충돌

설계는 트레이드 오프 : 상속은 캡슐화의 희생. 완벽환 캡슐화를 원한다면 코드 재사용을 포기 or 상속 외의 다른 방법

부모 클래스와 자식 클래스의 동시 수정 문제

상속을 이용하면 자식클래스가 부모 클래스의 구현에 강하게 결합되기 때문에 부모 클래스를 수정하면 자식 클래스를 수정해야 한다.

클래스를 상속하면 결합도로 인해 자식 클래스와 부모 클래스의 구현을 영원히 변경하지 않거나 동시에 변경하거나 선택해야 한다.

10-3.

추상화에 의존하자

상속으로 인해 취약한 기반 클래스 문제가 발생한다. → 이를 완전히 해결할 수는 없지만 추상화를 통해 어느 정도 위험을 완화시킬 수 있다.

상속을 도입 할 때 2가지 원칙을 지켜라

  1. 두 메서드가 유사하게 보인다면 차이점을 메서드로 추출하라
  2. 부모 클래스의 코드를 하위로 내리지 말고 자식 클래스의 코드를 상위로 올려라

차이를 메서드로 추출하라

중복 코드 안에서 차이점을 별도의 메서드로 추출하는 것이다.

중복 코드를 부모 클래스로 올려라

목표는 모든 클래스들이 추상화에 의존하도록 만드는 것이다.

공통 코드를 옮길 때 인스턴스 변수보다 메서드를 먼저 이동시키는게 편하다. (옮기고 나면 메서드에 필요한 메서드나 인스턴스 변수가 뭔지 컴파일 에러를 통해 확인이 가능)

[오브젝트] 26일차

p. 336 ~ p.348

중복 코드를 부모 클래스로 올려라

공통 코드를 옮길 때 인스턴스 변수보다 메서드를 먼저 이동시키는게 편하다. → 메서드를 옮기고 나면 필요한 것을 컴파일 에러를 통해 자동으로 알 수 있기 때문

자식 클래스들 사이의 공통점을 부모 클래스로 옮김으로써 추상화에 의존한 설계 구축 가능

위로 올리기 전략은 실패하더라도 쉽게 찾을 수 있고 쉽게 고칠 수 있는 문제를 발생시킨다. 구체적인 구현을 아래로 내리는 방식으로 변경한다면 작은 실수 한 번으로 구체적인 행동을 상위 클래스에 남겨 놓게 된다.

추상화가 핵심이다

공통 코드를 이동시킨 후 각 클래스는 서로 다른 변경의 이유를 가진다.

각 클래스가 각각 다른 변경 이유를 가지게 된다 → 단일 책임의 원칙 → 높은 응집도

부모 클래스 역시 내부에 구현된 추상 메서드를 호출하면서 추상화에 의존하게 된다. 새로운 클래스를 추가하기도 쉬워진다. → 확장에 열려 있고 수정에 닫혀 있는 개방-폐쇄의 원칙

상속 계층이 코드를 진화시키는데 걸림돌이 된다면 추상화를 찾아내고 상속 계층 안의 클래스들이 그 추상화에 의존하도록 코드를 리팩터링하라 → 차이점을 메서드로 추출, 공통적인 부분은 부모 클래스로 이동

의도를 드러내는 이름 선택

클래스의 이름은 포괄하는 의미를 명확하게 전달하여야 한다.

새로운 인스턴스 변수 추가

클래스라는 도구는 메서드 뿐만 아니라 인스턴스 변수도 함께 포함한다. → 클래스 사이의 상속은 자식 클래스가 부모 클래스가 구현한 행동뿐만 아니라 인스턴스 변수에 대해서도 결합되게 만든다.

인스턴스 변수의 목록이 변하지 않고 객채의 행동만 변경된다면 상속계층에 속한 각 클래스들을 독립적으로 진화시킬 수 있다.
하지만 인스턴스 변수가 추가되는 경우 자식 클래스는 자신의 인스턴스를 생성할 때 부모 클래스에 정의된 인스턴스 변수를 초기화 해야 한다. → 자식 클래스의 초기화 로직에 영향

그래도 객체 생성에 대한 로직 변경보다 핵심 로직의 중복을 막는 것이 더 중요하다. 상속으로 인한 클래스 사이의 결합은 피할 수 없다.

10-4. 차이에 의한 프로그래밍

상속을 사용하면 이미 존재하는 클래스의 코드를 기반으로 다른 부분을 구현함으로써 새로운 기능을 쉽고 빠르게 추가할 수 있다.

기존 코드와 다른 부분만 추가함으로써 애플리케이션의 기능을 확장하는 방법 → 차이에 의한 프로그래밍

차이에 의한 프로그래밍 목표는 중복코드를 제거하고 코드를 재사용하는 것이다.재사용 가능한 코드는 심각한 버그가 존재하지 않는 코드 → 품질을 유지하면서도 코드를 작성하는 노력과 테스트를 줄일 수 있다.

코드를 재사용하기 위해 맹목적으로 상속을 사용하는 것은 위험하다.

상속의 단점을 피하면서도 코드를 재사용할 수 있는 보다 좋은 방법 → 합성


11. 합성과 유연한 설계

상속은 부모 클래스와 자식 클래스를 연결해서 부모 클래스의 코드를 재사용하는 것

합성은 전체를 표현하는 객체가 부분을 표현하는 객체를 포함해서 부분 객체의 코드를 재사용한다.

상속과 반대로 합성은 두 객체 사이의 의존성이 런타임에 해결된다.

합성은 구현에 의존하지 않는다.

합성은 내부에 포함되는 객체의 구현이 아닌 퍼블릭 인터페이스에 의존한다. 합성을 이용하면 포함된 객체의 내부 구현이 변경 되더라도 영향을 최소화 할 수 있다 → 변경에 더 안정적이 코드

합석은 객체 사이의 동적인 관계이다. 실행 시점에 동적으로 변경할 수 있기 때문에 보다 변경하기 쉽고 유연한 설계를 얻을 수 있다.

GOF64 - 코드 재사용을 위해서는 객체합성이 클래스 상속보다 더 좋은 방법이다.

상속 대신 합성을 사용하면 포함되는 객체의 퍼블릭 인터페이스를 재사용함으로써 구현에 대한 의존성을 인터페이스에 대한 의존성으로 변경할 수 있다.

11-1. 상속을 합성으로 변경하기

상속을 남발할 때 직면하는 세가지 문제가 있다.

  1. 불필요한 인터페이스 상속문제
  2. 메서드 오버라이딩의 오작용 문제
  3. 부모 클래스와 자식 클래스의 동시 수정 문제

상속을 합성으로 바꾸는 방법은 매우 간단하다 → 자식 클래스에 선언된 상속 관계를 제거하고 부모 클래스의 인스턴스를 자식 클래스의 인스턴스 변수로 선언하면 된다.

불필요한 인터페이스 상속 문제 : java.util.Properties와 java.util.Stack

내부 구현에 밀접하게 결합되는 상속과 달리 합성으로 변경한 Properties는 Hashtable의 내부 구현에 관해 알지 못한다.

합성을 통해 불필요한 인터페이스 상속으로 인한 문제를 제거하게 된다.

[오브젝트] 27일차

p.349 ~ p.363

메서드 오버라이딩의 오작용 문제

부모 클래스에 대한 구현 결합도는 제거하면서도 퍼블릭 인터페이스는 그대로 상속받을 수 있는 방법 → 자바의 인터페이스

IntrumentedHashSet이 Set의 오퍼레이션을 오버라이딩한 인스턴스 메서드에서 내분의 HashSet 인스턴스에게 동일한 메서드 호출을 그대로 전달 → 포워딩(forwarding) → 동일한 메서드를 호출하기 위해 추가된 메서드를 포워딩 메서드라고 부른다.

기존 클래스의 인터페이스를 그대로 외부에 제공하면서 구현에 대한 결합 없이 일부 작동 방식을 변경하고 싶은 경우 사용할 수 있는 유용한 기법

부모 클래스와 자식 클래스의 동시 수정 문제

합성으로 변경하더라도 동시 수정 문제가 해결되지 않는 경우가 있다. 그렇다고 하더라도 상속보다는 합성을 사용하는게 좋다 → 내부구현을 변경하더라도 파급효과를 내부로 캡슐화할 수 있기 때문.

대부분의 경우 구현에 대한 결합보다는 인터페이스에 대한 결합ㅇ이 좋다.

cf) 몽키 패치 : 현재 실행 중인 환경에만 영향을 미치도록 지역적으로 코드를 수정하거나 확장하는 것

11-2. 상속으로 인한 조합의 폭발적인 증가

상속으로 인해 결합도가 높아지면 코드를 수정하는데 필요한 작업의 양이 과도하게 늘어난다.

가장 일반적인 상황 → 작은 기능들을 조합해서 더 큰 기능을 수행하는 객체를 만들어야 하는 경우

  1. 하나의 기능을 추가하거나 수정하기 위해 불필요하게 많은 수의 클래스를 추가하거나 수정해야 한다.
  2. 단일 상속만 지원하는 언어에서는 상속으로 인해 오히려 중복 코드의 양이 늘어날 수 있다.

합성을 사용하면 해당 문제를 간단히 해결할 수 있다.

기본 정책과 부가 정책 조합하기

요구사항을 구현하는 데 가장 큰 장벽은 기본 정책과 부가 정책의 조합 가능한 수가 매우 많다는 것이다. 따라서 설계는 다양한 조합을 수용할 수 있도록 유연해야 한다.

부모 클래스의 메서드를 재사용하기 위해 super 호출을 사용하면 원하는 결과를 쉽게 얻을 수 있지만 자식-부모클래스의 결합도가 높아진다. 이를 낮추기 위한 방법은 자식 클래스가 부모 클래스의 메서드를 호출하지 않도록 부모 클래스에 추상 메서드를 제공하는 것이다.

부모 클래스에 추상 메서드를 추가하면 모든 자식 클래스들이 추상 메서드를 오버라이딩해야 하는 문제가 발생한다. 자식 클래스가 많다면 번거롭다.

cf) 훅 메서드

추상 메서드과 동일하게 자식 클래스에서 오버라이딩할 의도로 메서드를 추가했지만 편의를 위해 기본 구현을 제공하는 메서드를 훅 메서드라고 부른다.

자바를 비롯한 대부분의 객체지향 언어는 단일 상속만 지원하기 때문에 상속으로 발생하는 중복 코드 문제를 해결하기 쉽지 않다.

[오브젝트] 28일차

p. 364 ~ p. 377

중복 코드의 덫에 걸리다

부가 정책을 자유롭게 조합할 수 있어야 하고 적용되는 순서 역시 임의로 결정할 수 있어야 한다.

상속을 이용할 경우 모든 가능한 조합별로 자식 클래스를 하나씩 추가하는 것이다. 이럴 경우 상속 계층이 복잡해지고 새로운 정책을 추가하기 위해서는 불필요하게 많은 수의 클래스를 추가해야한다는 문제가 발생한다.

상속의 남용으로 하나의 기능을 추가하기 위해 필요 이상으로 많은 수의 클래스를 추가해야 하는 경우를 가리켜 클래스 폭발(class explosion)문제 또는 조합의 폭발(combinational explosion)문제 라고 부른다.

클래스 폭발 문제는 자식 클래스가 부모 클래스의 구현에 강하게 결합되도록 강요하는 상속의 근본적인 한계 때문에 발생하는 문제.

→ 이 문제를 해결할 수 있는 최선의 방법은 상속을 포기하는 것이다

11-2. 합성 관계로 변경하기

상속 관계는 컴파일 타임에 결정되고 고정되기 때문에 코드를 실행하는 도중에는 변경할 수 없다. 따라서 여러 기능을 조합해야 하는 설계에 상속을 이용하면 모든 조합 가능한 경우별로 클래스를 추가해야 한다.

합성은 컴파일타임 관계를 런타임 관계로 변경함으로써 이 문제를 해결한다. 구현이 아닌 퍼블릭 인터페이스에 대해서만 의존할 수 있기 때문에 런타임에 객체의 관계를 변경할 수 있따.

합성을 사용하면 구현 시점에 정책들의 관계를 고정시킬 필요가 없고 실행 시점에 정책들의 관계를 유연하게 변경할 수 있다. → 조합을 구성하는 요소들을 개별 클래스로 구현한 후 실행 시점에 인스턴스를 조립하는 방법

컴파일 타임 의존성과 런타임 의존성의 거리가 멀수록 설계의 복잡도가 상승하면서 코드를 이해하기 어려워진다. → 하지만 변경에 편리한 설계를 위한 복잡성이 오히려 원래의 설계보다 단순해지는 경우도 있다.

기본 정책 합성하기

다양한 종류의 객체와 협력하기 위해 합성 관계를 사용하는 경우에는 합성하는 객체의 타입을 인터페이스나 추상 클래스로 선언하고 의존성 주입을 사용해 런타임에 필요한 객체를 설정할 수 있도록 구현하는 것이 일반적이다.

어떤 클래스의 인스턴스를 조합해야하는지 고민해야하기 때문에 상속보다 복잡해보일 수도 있으나 부가정책을 추가할 경우 합성의 강력함을 실감할 수 있다.

부가 정책 적용하기

부가 정책의 구현은 다음의 2가지 제약을 따른다.

  • 부가 정책은 기본 정책이나 다른 부가 정책의 인스턴스를 참조할 수 있어야 한다. == 부가 정책의 인스턴스는 어떤 종류의 정책과도 합성될 수 있어야 한다.
  • 기본 정책과 부가 정책은 협력 안에서 동일한 역할을 수행해야 한다. 즉 부가정책도 기본 정책과 동일한 인터페이스를 구현해야 한다는 것을 의미한다.

기본 정책과 부가정책 합성하기

상속을 사용한 설계보다 복잡하고 정해진 규칙에 따라 객체를 생성하는 것이 처음에는 이해하기 어려울 수 있다. 하지만 설계에 익숙해지면 더 예측 가능하고 일관성이 있다.

새로운 정책 추가하기

상속을 기반으로 한 설계에 새로운 부가 정책을 추가하기 위해 상속 계층에 불필요할 정도로 많은 클래스를 추가해야 했다. 하지만 합성을 기반으로 한 설계에서는 필요한 구현 클래스 하나만 추가하고 원하는 방식으로 조합하면 된다.

또한 요구 사항을 변경할 때 해당 책임의 클래스 하나만 수정해도 된다는 것이다.

[오브젝트] 29일차

p. 378 ~ p. 392

객체 합성이 클래스 상속보다 더 좋은 방법이다.

코드를 재사용하면서도 건전한 결합도를 유지할 수 있는 방법은 합성을 이용하는 것이다.

상속이 구현을 재사용하는 데 비해 합성은 객체의 인터페이스를 재사용한다.

11-4. 믹스인

합성이 상속과 같이 수정과 확장에 취약한 문제가 발생하지 않는 것은 클래스의 구체적인 구현이 아닌 객체의 추상적인 인터페이스에 의존하기 때문이다.

구체적인 코드를 재사용하면서도 낮은 결합도를 유지할 수 있는 유일한 방법은 재사용에 적합한 추상화를 도입하는 것이다.

믹스인은 객체를 생성할 때 코드 일부를 클래스 안에 섞어 넣어 재사용하는 기법을 가리키는 용어다.

믹스인은 코드 재사용에 특화된 방법이면서도 상속과 같은 결합도 문제를 초래하지 않는다. 믹스인은 합성처럼 유연하면서도 상속처럼 쉽게 코드를 재사용할 수 있는 방법이다.

기본 정책 구현하기

중요한 것은 기본 정책에 부가 정책을 자유롭게 조합할 수 있는 설계다. 따라서 기본 정책을 구현한 후 부가 정책과 관련된 코드를 기본 정책에 어떻게 믹스인 할 수 있는지를 고민해야 한다.

트레이트로 부가정책 구현하기

스칼라에서는 다른 코드와 조합해서 확장할 수 있는 기능을 트레이트로 구현할 수 있다. 여기서 기본 정책에 조합하려는 코드는 부가 정책을 구현하려는 코드들이다.

스칼라는 특정 클래스에 믹스인한 클래스와 트레이트를 선형화해서 어떤 메서드를 호출할지 결정한다. 클래스의 인스턴스를 생성할 때 스칼라는 클래스 자신과 조상 클래스, 트레이트를 일렬로 나열해서 순서를 정한다.

믹스인되기 전까지는 상속 계층안에서 트레이트의 위치가 정해지지 않는다는 것이다. 어떤 클래스를 믹스인할지에 따라 트레이트의 위치는 동적으로 변경된다.

쌓을 수 있는 변경

전통적으로 믹스인은 특정한 클래스의 메서드를 재사용하고 기능을 확장하기 위해 사용돼 왔다. 즉 대상 클래스의 자식 클래스처럼 사용될 용도로 만들어지는 것이다. → 추상 서브클래스라고 부르기도 한다.

믹스인을 사용하면 특정 클래스에 대한 변경 또는 확장을 독립적으로 구현한 후 필요한 시점에 차례대로 추가할 수 있다.


12. 다형성

최근의 언어들은 상속 이외에도 다형성을 구현할 수 있는 다양한 방법들을 제공하고 있기 때문에 과거에 비해 상속이 중요성이 많이 낮아졌다고 할 수 있다.

상속의 관점에서 다형성이 구현되는 기술적인 메커니즘을 살펴보자. 이를 통해 다형성이 런타임에 메시지를 처리하기에 적합한 메서드를 동적으로 탐색하는 과정을 통해 구현되며, 상속이 이런 메서드를 찾기 위한 일종의 탐색 경로를 클래스 계층의 형태로 구현하기 위한 방법임을 이해하게 된다.

12-1. 다형성

다형성은 여러 타입을 대상으로 동작할 수 있는 코드를 작성하는 방법이라 할 수 있다.

객체지향 프로그래밍에서 사용되는 다형성은

  • 유니버설(Universal) 다형성
  • 임시(Ad Hoc) 다형성

으로 분류할 수 있다.

유니버설 다형성은 다시

  • 매개변수(Parametric) 다형성
  • 포함(Inclusion) 다형성

임시 다형성은

  • 오버로딩(Overloading) 다형성
  • 강제(Coercion) 다형성

으로 분류할 수 있다.

일반적으로 하나의 클래스 안에 동일한 이름의 메서드가 존재하는 경우를 오버로딩 다형성이라고 부른다

[오브젝트] 30일차

p. 393 ~ p. 402

12 - 2. 상속의 양면성

객체지향 패러다임의 근간은 데이터와 행동을 객체라고 불리는 하나의 실행 단위 안으로 통합하는 것이다. 따라서 데이터와 행동이라는 두 가지 관점을 함께 고려해야 한다.

데이터 관점의 상속 - 부모 클래스에서 정의한 모든 데이터를 자식 클래스의 인스턴스에 자동으로 포함

행동 관점의 상속 - 부모 클래스에서 정의한 일부 메서드를 자동으로 자식 클래스에 포함

하지만 상속의 목적은 코드의 재사용이 아니다. → 프로그램을 구성하는 개념들을 기반으로 다형성을 기능하게 하는 타입 계층을 구축하기 위함.

메서드 오버라이딩 - 자식 클래스 안에 상속받은 메서드와 동일한 시그니처의 메서드를 재정의해서 부모 클래스의 구현을 새로운 구현으로 대체하는 것

메서드 오버로딩 - 부모클래스에서 정의한 메서드와 이름을 동일하지만 시그니처는 다른 메서드를 자식 클래스에 추가하는 것

이처럼 상속을 이용해 새로운 기능을 쉽고 빠르게 추가할 수 있다.

데이터 관점의 상속

상속을 인스턴스 관점에서 바라볼 때는 개념적으로 자식 클래스의 인스턴스 안에 부모 클래스의 인스턴스가 포함되는 것으로 생각하는 것이 유용하다.

데이터 관점에서 상속은 자식 클래스의 인스턴스 안에 부모 클래스의 인스턴스를 포함하는 것으로 볼 수 있다. 따라서 자식 클래스의 인스턴스는 자동으로 부모 클래스에서 정의한 모든 인스턴스 변수를 내부에 포함하는 것이다.

행동 관점의 상속

행동 관점의 상속은 부모 클래스가 정의한 일부 메서드를 자식 클래스의 메서드로 포함시키는 것을 의미한다.

어떤 메서드가 자식 클래스에 포함될지는 언어의 종류와 접근제어자에 따라 다르지만 공통적으로 부모 클래스의 모든 퍼블릭 메서드는 자식 클래스의 퍼블릭 메서드에 포함된다.

profile
daelkdev@gmail.com

0개의 댓글