15장 정리

애플리케이션을 설계하다 보면 과거에 경험하고 해결했던 방법을 다시 사용하는 경우가 있다.
이렇게 소프트웨어 설계에서 반복적으로 발생하는 문제에 대해 반복적으로 적용할 수 있는 방법을 디자인 패턴이라고 한다.
디자인 패턴의 목적은 설계를 재사용하기 위함이다.
프레임워크는 설계와 코드를 함께 재사용하기 위한 것이다.
디자인 패턴과 프레임워크 모두 일관성 있는 협력과 관련이 있다.
디자인 패턴은 특정한 변경을 일관성 있게 다루는 협력 템플릿을 제공하고,
프레임워크는 특정한 변경을 일관성 있게 다룰 수 있는 확장 가능한 코드 템플릿을 제공한다.

디자인 패턴과 설계 재사용

소프트웨어 패턴

패턴은 한 컨텍스트에서 유용한 동시에 다른 컨텍스트에서도 유용한 아이디어다.
패턴이 지닌 가장 큰 가치는 경험을 통해 축적된 실무 지식을 효과적으로 요약하고 전달할 수 있다는 점이다.
패턴은 경험의 산물이기 때문에 초보자라고 하더라도 패턴을 익히고 반복적으로 적용하는 과정 속에서 유연하고 품질 높은 소프트웨어를 개발하는 방법을 익힐 수 있다.
패턴은 '이름'을 통해 지식 전달과 커뮤니케이션의 순단으로 사용할 수 있기 때문에 이름 짓기가 중요하다.

패턴과 책임-주도 설계

패턴은 공통으로 사용할 수 있는 역할, 책임, 협력의 템플릿이다.
적절한 패턴을 따르면 특정한 상황에 적용할 수 있는 설계를 쉽고 빠르게 떠올릴 수 있다.
하지만 디자인 패턴을 따른다는건 역할, 책임, 협력의 관점에서 유사성을 공유하는것이지 특정한 구현 방식을 강제하는 것은 아니다.
대부분의 디자인 패턴의 목적은 특정한 변경을 캡슐화함으로써 유연하고 일관성 있는 협력을 설계할 수 있는 경험을 공유하는 것이다.
따라서 디자인 패턴에서 중요한 것은 디자인 패턴의 구현 방법이나 구조가 아니라 어떤 변경을 캡슐화하는지를 이해하는 것이 중요하다.

패턴은 출발점이다.

대부분의 패턴 입문자는 패턴을 적용하는 컨텍스트의 적절성을 무시한 채 맹목적으로 패턴의 구조에만 초점을 맞추기 쉽다.
따라서 부적절한 상황에서 부적절하게 사용된 패턴으로 인해 유지보수 하기 어려운 시스템을 만드는 것을 주의해야 한다.
패턴을 남용하지 않기 위해서는 다양한 트레이드오프 관계 속에서 패턴을 적용하고 사용해 본 경험이 필요하다.
정당한 이유 없이 사용된 모든 패턴은 설계를 복잡하게 만드는 장애물이다.
패턴을 적용할 때는 항상 설계를 좀 더 단순하고 명확하게 만들 수 잇는 방법이 없는지를 고민해야 한다.
또한 코드를 공유하는 모든 사람들이 적용된 패턴을 알고 있어야 한다.
패턴을 적용할 때는 함께 작업하는 사람들이 패턴에 익숙한지 여부를 확인하고, 그렇지 않다면 설계에 대한 지식과 더불어 패턴에 대한 지식도 함께 공유하는 것이 필요하다.
패턴을 가장 효과적으로 적용하는 방법은 패턴을 지향하거나 패턴을 목표로 리팩터링하는 것일 수 있다.

프레임워크와 코드 재사용

프레임워크란 '추상 클래스나 인터페이스를 정의하고 인스턴스 사이의 상호작용을 통해 시스템 전체 혹은 일부를 구현해 놓은 재사용 가능한 설계' 또는 '애플리케이션 개발자가 현재의 요구사항에 맞게 커스터마이징 할 수 있는 애플리케이션의 골격'을 의미한다.

제어 역전 원리

의존성 역전 원리를 따르는 설계는 변경에 유연하게 대처할 수 있다.
이런 의존성 역전 원리는 프레임워크의 가장 기본적인 설계 메커니즘이다.
의존성 역전은 의존성의 방향 뿐만 아니라 제어 흐름의 주체 역시 역전시킨다.
협력을 제어하는 것은 프레임워크이고, 우리는 프레임워크가 적절한 시점에 실해할 것으로 예상되는 코드를 작성한다.


인상깊었던 점

"정당한 이유 없이 사용되는 패턴은 유지보수를 어렵게 만드는 장애물이 된다."
이 부분이 이번 장에서는 인상깊었다.
디자인 패턴을 공부하고서 의욕이 넘쳐서 이리저리 써봤지만 결국 적용하고 보니 과한 설계였던 경험들이 있었다.
이 경험들이 떠오르면서 정당한 이유 없이 사용되는 패턴은 유지보수를 어렵게 만드는 장애물이 된다는 부분이 더 와닿았다.

'공부방 > ' 카테고리의 다른 글

[도서] 함께 자라기 - 애자일로 가는 길을 읽고  (0) 2023.11.02

14장 정리

객체는 협력을 위해 존재하며, 객체지향 설계의 목표는 적절한 책임을 수행하는 객체들의 협력을 기반으로

결합도가 낮고 재사용 가능한 코드 구조를 창조하는 것이다.
객체지향 패러다임의 장점은 설계를 재사용할 수 있다는 것이다.
하지만 재사용을 공짜로 얻어지지 않으며, 객체들의 협력 방식을 일관성 있게 만들어야 한다.
일관성 있는 협력 패턴을 적용하면 코드 전체를 이해하기 쉽고 직관적이며 유연해진다.


설계에 일관성 부여하기

유사한 기능을 서로 다른 방식으로 구현해서는 안된다.
객체지향에서 기능을 구현하는 유일한 방법은 객체 사이의 협력을 만드는 것이다.
따라서 유지보수 가능한 시스템을 구축하는 첫걸음은 협력을 일관성 있게 만드는 것이다.
협력을 일관성 있게 만들기 위해 다음과 같은 기본 지침을 따르는 것이 도움이 된다.

변하는 개념을 변하지 않는 개념으로부터 분리하라.

변하는 개념들을 개별적인 객체로 분리하고, 이 객체들과 일관성 있게 협력하기 위해 타입 계층을 구성한다.
타입 계층을 클라이언트로 분리하기 위해 역할을 도입하고, 최종적으로 역할을 추상 클래스와 인터페이스로 구현한다.
이렇게 하면 결과적으로 변하는 개념을 별도의 서브타입으로 분리한 후 이 서브타입들을 클라이언트로부터 캡슐화할 수 있다.

변하는 개념을 캡슐화하라.

핵심은 훌륭한 추상화를 찾아 추상화에 의존하도록 만드는 것이다.
타입을 캡슐화하고 낮은 의존성을 유지하기 위해서는 이전 장들에서 살펴본 설계 원칙들을 적용하면 도움이 된다.

  • 6장의 인터페이스 설계 원칙들을 적용하면 구현을 효과적으로 캡슐화하는 코드를 구현할 수 있다.
  • 8, 9장의 의존성 관리 기법을 적용하면 타입을 캡슐화하기 위해 낮은 결합도를 유지할 수 있는 방법을 보여준다.
  • 10장을 통해 타입을 캡슐화 하는게 아닌, 코드 재사용을 위해 상속을 사용하는 것의 단점을 보여준다.
  • 11장에서 배운 상속 대신 합성을 사용하면 훌륭한 캡슐화를 구현할 수 있다.
  • 13장에서 설명한 원칙을 따르면 리스코프 치환 원칙을 준수하는 타입 계층을 구현하는 데 상속을 이용할 수 있다.

변경에 초점을 맞추고 캡슐화의 관점에서 설계를 바라보면 일관성 있는 협력 패턴을 얻을 수 있다.

캡슐화 다시 살펴보기

많은 사람들은 객체의 캡슐화를 들으면 데이터 은닉을 떠올리지만, 캡슐화는 데이터 은닉 그 이상의 개념이다.
단순히 데이터를 감추는것이 아닌, 소프트웨어 안에서 변할 수 있는 모든 '개념'을 감추는 것이다.
변경을 캡슐화 할 수 있는 다양한 방법이 존재하지만 협력을 일관성 있게 만들기 위해 가장 일반적으로 사용하는 방법은 서브타입 캡슐화와 객체 캡슐화를 조합하는 것이다.
서브타입 캡슐화는 인터페이스 상속을 사용하고, 객체 캡슐화는 합성을 사용한다.
서브타입 캡슐화와 객체 캡슐화를 적용하는 방법은 다음과 같다.

변하는 부분을 분리해서 타입 계층을 만든다.

변하지 않는 부분으로부터 변하는 부분을 분리한다.
변하는 부분들의 공통적인 행동을 추상화하여 추상 클래스나 인터페이스로 추상화한 후 이를 타입 계층화한다.

변하지 않는 부분의 일부로 타입 계층을 합성한다.

앞에서 구현한 변하는 부분을 변하지 않는 부분에 합성한다.
의존성 주입과 같이 결합도를 느슨하게 유지할 수 있는 방법을 이용해 오직 추상화에만 의존하게 만든다.

지속적으로 개선하라

처음에는 일관성을 유지하는 것처럼 보이던 협력 패턴이 시간이 흐르면서 새로운 요구사항이 추가되는 과정에서 일관성의 벽에 조금씩 금이 가는 경우를 자주 보게 된다.
협력을 설계하는 초기 단계에서 모든 요구사항을 미리 예상할 수 없기 때문에 이는 자연스러운 현상이다.
협력은 고정된것이 아니므로 현재의 협력 패턴이 변경의 무게를 지탱하기 어렵다면 변경을 수용할 수 있는 협력 패턴으로 과감하게 리팩터링하라.
요구사항의 변경에 따라 협력 역시 지속적으로 개선해야 한다.
현재의 설계에 맹목적으로 일관성을 맞추는 것이 아니라 달라지는 변경의 방향에 맞춰 지속적으로 코드를 개선해야 한다.


인상깊었던 점

"유지보수 가능한 시스템을 구축하는 첫걸음은 협력을 일관성 있게 만드는 것이다."
이번 장에서는 일관성 있는 협력의 중요함을 말하고 있는것 같다.
이전 장들에서 공부했던 인터페이스 분리원칙, 서브타입/슈퍼타입 관계와 리스코프 치환 원칙 등을 따라서

변하는 부분과 변하지 않는 부분을 분리하는 방법을 설명해준 부분을 통해 이전 장의 내용과 연관지어 생각할 수 있었다.
또한 시간이 지나면서 협력의 일관성이 깨질 수 있지 않을까? 라고 생각했는데

마지막 장에서 이에 대한 가능성을 언급하며 현재의 설계에 맹목적으로 협력의 형태를 맞추는게 아닌,

지속적으로 코드를 개선해야 함을 언급한 점도 좋았다.

'공부방 > 북스터디' 카테고리의 다른 글

[오브젝트] 13장  (0) 2024.04.08
[오브젝트] 12장  (0) 2024.03.26
[오브젝트] 11장  (0) 2024.03.18
[오브젝트] 10장  (0) 2024.03.04
[오브젝트] 9장  (0) 2024.02.20

13장 정리

상속의 첫번째 용도는 타입 계층을 구현하는 것이고, 두번째 용도는 코드 재사용이다.
상속은 코드를 쉽게 재사용할 수 있는 방법을 제공하지만 부모 클래스와 자식 클래스를 강하게 결합시킨다.
타입 계층을 목표로 상속을 사용하면 다형적으로 동작하는 객체들의 관계에 기반해 확장 가능하고 유연한 설계를 얻을 수 있게 된다.
따라서 상속을 사용하는 일차적인 목표는 코드 재사용이 아닌 타입 계층을 구현하는 것이어야 한다.
동일한 메시지에 대해 서로 다르게 행동할 수 있는 다형적인 객체를 구현하기 위해서는 객체의 행동을 기반으로 타입 계층을 구성해야 한다.
상속의 가치는 이러한 타입 계층을 구현할 수 있는 쉽고 편안한 방법을 제공한다는데 있다.
타입 사이의 관계를 고려하지 않은 채 단순히 코드를 재사용하기 위해 상속을 사용해서는 안된다.


타입 

프로그래밍 언어 관점의 타입

프로그래밍 언어 관점에서 타입은 두 가지 목적을 위해 사용된다.

  • 타입에 수행될 수 있는 유효한 오퍼레이션의 집합을 정의한다.
  • 타입에 수행되는 오퍼레이션에 대해 미리 약속된 문맥을 제공한다.
    타입은 적용 가능한 오퍼레이션의 종류와 의미를 정의함으로써 코드의 의미를 명확하게 전달하고 개발자의 실수를 방지하기 위해 사용된다.

객체지향 패러다임 관점의 타입

프로그래밍 언어 관점에서 타입은 호출 가능한 오퍼레이션의 집합을 정의한다면,

객체지향 프로그래밍에서 오퍼레이션은 객체가 수신할 수 있는 메시지를 의미한다.
따라서 객체의 타입이란 객체가 수신할 수 있는 메시지의 종류를 정의하는 것이다.
우리는 이미 퍼블릭 인터페이스라는 객체가 수신할 수 있는 메시지의 집합을 정의한 단어를 알고 있다.
객체지향에서는 객체가 수신할 수 있는 메시지를 기준으로 타입을 분류한다.
따라서 동일한 퍼블릭 인터페이스를 가지는 객체들은 동일한 타입으로 분류할 수 있다.
즉, 객체에게 중요한 것은 속성이 아니라 행동이다.
어떤 객체들이 동일한 상태를 가지고 있더라도 퍼블릭 인터페이스가 다르다면 이들은 서로 다른 타입으로 분류된다.

타입 계층

위 그림처럼 타입은 집합의 관점에서 더 세분화된 타입의 집합을 부분집합으로 포함할 수 있다.
다른 타입을 포함하는 타입은 다른 타입에 속해 있는 타입보다 좀 더 일반화된 의미를 표현할 수 있다.
반면 다른 타입에 속해 있는 타입은 좀 더 특수하고 구체적이다.
이것은 포함 관계로 연결된 타입 사이에 개념적으로 일반화와 특수화 관계가 존재한다는 것을 의미한다.
이를 그림으로 표현하면 아래와 같다.


타입 계층을 구성하는 두 타입 간의 관계에서 더 일반적인 타입을 슈퍼타입이라고 부르고 더 특수한 타입을 서브타입이라고 부른다.
이를 객체의 관점에서 보자면 일반화란 추상화를 의미하며, 특수화란 구체화를 의미한다고 할 수 있다.

객체지향 프로그래밍과 타입 계층

일반화와 특수화를 객체지향 프로그래밍 관점에서 보자면 더 일반적인 퍼블릭 인터페이스를 갖는 객체는 슈퍼타입이고 더 특수한 퍼블릭 인터페이스를 갖는 객체는 서브타입이라고 할 수 있다.
서브타입의 인스턴스는 슈퍼타입의 인스턴스로 간주될 수 있다.
이는 상속과 다형성의 관계를 이해하기 위한 출발점이며, 이 장의 핵심이다.

서브클래싱과 서브타이핑

그렇다면 퍼블릭 인터페이스가 더 특수하다는 것은 어떤 의미일까?
이제부터 타입 계층을 구현할 때 지켜야하는 제약사항을 클래스와 상속 관점에서 살펴보자.

언제 상속을 사용해야 하는가?

반복하지만 상속의 올바른 용도는 타입 계층을 구현하는 것이다.
마틴 오더스키는 다음 두 질문을 해보고 두 질문에 모두 '예'라고 답할 수 잇는 경우에만 상속을 사용하라고 조언한다.

  • 상속 관계가 is-a 관계를 모델링하는가?
  • 클라이언트 입장에서 부모 클래스의 타입으로 자식 클래스를 사용해도 무방한가?

슈퍼타입과 서브타입 관계에서는 is-a보다 행동 호환성이 더 중요하므로 설계 관점에서는 첫번째보다는 두번째 질문에 초점을 맞추는게 중요하다.

행동 호환성

행동의 호환 여부를 판단하는 기준은 클라이언트의 관점이다.
클라이언트가 기대하는 행동에 따라 두 타입을 타입 계층으로 묶을지 여부를 판단해야 한다.

클라이언트의 기대에 따라 계층 분리하기

클라이언트에 따라 인터페이스를 분리하면 변경에 대한 영향을 더 세밀하게 제어할 수 있게 된다.
요구 사항이 바뀌더라도 영향의 파급 효과를 효과적으로 제어할 수 있다.
이처럼 클라이언트의 기대에 따라 분리함으로써 변경에 의해 영향을 제어하는 설계 원칙을 인터페이스 분리 원칙이라고 부른다.

리스코프 치환 원칙

리스코프 치환 원칙을 한마디로 정의하면 아래와 같다.

  • 서브타입은 그것을 기반하는 슈퍼타입에 대해 대체 가능해야한다
    즉 자식 클래스가 부모 클래스와 행동 호환성을 유지함으로써 부모 클래스를 대체할 수 있도록 구현된 상속 관계만을 서브타이핑이라고 불러야한다.

클라이언트와 대체 가능성

어떤 모델의 유효성은 클라이언트의 관점에서만 검증 가능하다.

따라서 상속 관계에 있는 두 클래스 사이의 관계를 클라이언트와 떨어뜨려 놓고 판단해서는 안된다.
대체 가능성을 결정하는 것은 클라이언트다.

계약에 의한 설계와 서브타이핑

리스코프 치환 원칙과 계약에 의한 설계 사이의 관계를 다음과 같은 한 문장으로 요약할 수 있다.

  • 서브타입이 리스코프 치환 원칙을만족시키기 위해서는 클라이언트와 슈퍼타입 간에 체결된 '계약'을 준수해야 한다.

인상깊었던 점

"상속에 있어서 어떤 모델의 유효성은 클라이언트의 관점에서만 검증 가능하다."
이번 장에서는 타입을 프로그래밍 언어와 객체지향적 관점에서 바라보며 일반화와 특수화 관계를 설명하였다.
이후 일반화와 특수화를 추상화와 구체화로 서브타입과 슈퍼타입을 정의하고, 올바른 서브타입 슈퍼타입 관계인지 아닌지는 클라이언트의 기대에 따라 달라진다고 설명하면서 빌드업 과정을 거쳐서 설명한게 인상깊었다.
상속은 단순히 코드 재사용을 위해 사용하는게 아니라는걸 행동 호환성과 클라이언트의 연관성을 설명하면서 인터페이스 분리 원칙과 리스코프 치환 원칙과 함께 설명해 주는 부분이 좋았다.

'공부방 > 북스터디' 카테고리의 다른 글

[오브젝트] 14장  (0) 2024.04.15
[오브젝트] 12장  (0) 2024.03.26
[오브젝트] 11장  (0) 2024.03.18
[오브젝트] 10장  (0) 2024.03.04
[오브젝트] 9장  (0) 2024.02.20

12장 정리

코드 재사용을 목적으로 상속을 사용하면 변경하기 얼벼고 유연하지 못한 설계에 이를 확률이 높아진다.
상속의 목적은 코드 재사용이 아니다. 상속은 타입 계층을 구조화하기 위해 사용해야한다.
클라이언트 관점에서 인스턴스들을 동일한 행동 그룹군으로 묶기 위해서 사용해야한다.
이번 장에서는 상속의 관점에서 다형성이 구현되는 기술적인 메커니즘을 살펴본다.
다형성이 런타임에 메시지를 처리하기에 적합한 메서드를 동적으로 탐색하는 과정을 통해 구현되며, 상속이 이런 메서드를 찾기 위한 일종의 탐색 경로를 클래스 계층의 형태로 구현하기 위한 방법임을 이해할 수 있을 것이다.


다형성

다형성이라는 단어는 '많은 형태를 가질 수 있는 능력'을 의미한다.

  • 오버로딩 다형성: 메서드 오버로드
  • 강제 다형성: + 연산자의 피연산자에 따른 타입 강제
  • 매개변수 다형성: 제네릭
  • 포함 다형성: 인터페이스 -> 가장 일반적으로 의미하는다형성 = 서브타입 다형성

상속의 진정한 목적은 코드 재사용이 아닌 다형성을 위한 서브타입 계층을 구축하는 것이다.

상속의 양면성

객체지향의 패러다임에서는 데이터와 행동을 객체라는 하나의 실행 단위 안으로 통합한다. 

따라서 객체 지향 프로그램을 작성하기 위해서는 항상 데이터와 행동이라는 두가지 관점을 함께 고려해야 한다.
단순히 데이터와 행동의 관점에서 보면 상속은 부모 클래스에서 정의한 데이터와 행동을 자식 클래스에서 자동으로 공유할 수있는 재사용 메커니즘으로 보인다.
하지만 상속의 목적은 재사용이 아니라 프로그램을 구성하는 개념들을 기반으로 다형성을 가능하게 하는 타입 계층을 구축하는 것이다.
타입 계층에 대한 고민 없이 코드를 재사용하기 위해 상속을 사용하면 이해하기 어렵고 유지보수하기 버거운 코드가 만들어질 확률이 높다.

데이터 관점의 상속

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

행동 관점의 상속

행동 관점의 상속은 부모 클래스가 정의한 일부 메서드를 자식 클래스의 메서드로 포함시키는 것을 의미한다.
부모 클래스의 모든 퍼블릭 메서드는 자식 클래스의 퍼블릭 인터페이스에 포함된다.
런타임 시점에 자식 클래스에 정의되지 않은 메서드가 있을 경우 이 메서드를 부모 클래스 안에서 탐색하기 때문에 외부의 객체가 부모 클래스의 인스턴스에게 전송할 수 있는 모든 메시지는 자식 클래스의 인스턴스에게도 전송할 수 있다.
위의 그림에서 보듯 메시지를 수신한 객체는 class 포인터로 연결된 자신의 클래스에서 적절한 메서드가 존재하는지를 찾고, 메서드가 존재하지 않으면 클래스의 parent 포인터를 따라 부모 클래스를 차례대로 훑어가면서 적절한 메서드가 존재하는지 탐색한다.

업캐스팅과 동적 바인딩

같은 메시지, 다른 메서드

  • 업캐스팅: 부모 클래스 타입으로 선언된 변수에 자식 클래스의 인스턴스를 할당하는 것
  • 동적 바인딩: 선언된 변수의 타입이 아니라 메시지를 수신하는 객체의 타입에 따라 실행되는 메서드가 결정되는 것

동일한 수신자에게 동일한 메시지를 전송하는 동일한 코드로 서로 다른 메서드를 실행할 수 있는 이유는 업캐스팅과 동적 메서드 탐색이라는 기반 메커니즘이 존재하기 때문이다.
이 두가지를 통해 코드를 변경하지 않더라도 실행되는 메서드를 변경할 수 있다.

업캐스팅

컴파일러의 관점에서 자식 클래스는 아무런 제약 없이 부모 클래스를 대체할 수 있기 때문에 부모 클래스와 협력하는 클라이언트는 현재 상속 계층에 존재하는 자식 클래스 뿐 아니라 앞으로 추가 될 미래의 자식 클래스의 인스턴스와도 협력이 가능하다.
이를 통해 유연하며 확장 용이한 설계가 가능하다.

동적 바인딩

함수를 호출하는 전통적인 언어에서는 컴파일 타임에 호출될 함수를 결정하지만 객체지향 언어에서는 메시지를 수신했을 때 실행될 메서드가 런타임에 결정된다.

동적 메서드 탐색과 다형성

객체지향 시스템은 다음 규칙에 따라 실행할 메서드를 선택한다.

  1. 메시지를 수신한 객체는 자신을 생성한 클래스에 적합한 메서드가 존재하는지 검색한다. 존재하면 메서드를 실행하고 탐색을 종료한다.
  2. 메서드를 찾지 못했다면 부모 클래스에서 메서드 탐색을 계속한다. 이 과정은 적합한 메서드를 찾을 때까지 상속 계층을 따라 올라가며 계속된다.
  3. 상속 계층의 가장 최상위 클래스에 이르러도 메서드를 발견하지 못한 경우 예외를 발생시키며 탐색을 중단한다.

메시지 탐색에서 self 참조란 객체가 메시지를 수신하면 컴파일러는 self 참조라는 임시 변수를 자동으로 생성한 후 메시지를 수신한 객체를 가리키도록 설정한다.
동적 메서드 탐색을 self가 가리키는 객체의 클래스에서 시작해서 상속 계층을 타고 올라가면서 이루어지고, 메서드 탐색이 종료되는 순간 self 참조는 자동으로 소멸된다.
동적 메서드 탐색은 자동적인 메시지 위임동적인 문맥 사용의 두가지 원리로 구성된다.

자동적인 메시지 위임

메시지를 수신한 객체가 자신이 이해할 수 없는 메시지인 경우 부모 클래스에 처리를 위임한다.
상속을 이용할 경우 프로그래머가 메시지 위임과 관련된 코드를 명시적으로 작성하지 않아도 자동으로 위임된다.

동적인 문맥

메시지를 수신학 객체가 무엇이냐에 따라 메서드 탐색을 위한 문맥이 동적으로 바뀐다. 그리고 이 문맥을 결정하는 것은 메시지를 수신한 객체를 가리키는 self 참조다.
동일한 코드라고 하더라도 self 참조가 가리키는 객체가 무엇인지에 따라 메서드 탐색을 위한 상속 계층의 범위가 동적으로 변하므로 self 참조가 가리키는 객체의 타입을 변경함으로써 객체가 실행될 문맥을 동적으로 바꿀 수 있다.
self 전송은 자식 클래스에서 부모 클래스 방향으로 진행되는 동적 메서드 탐색 경로를 다시 self 참조가 가리키는 원래의 자식 클래스로 이동시킨다.
따라서 self 전송으로 인해 극단적으로 이해하기 어려운 코드가 만들어질 수 있다.

self와 super

self 참조는 메시지를 수신한 객체의 클래스에 따라 메서드 탐색을 위한 문맥을 실행 시점에 결정하며, 어떤 클래스에서 메시지 탐색이 시작될지 알지 못한다.
super 참조는 항상 해당 클래스의 부모 클래스에서부터 메서드 탐색을 시작한다.

상속 대 위임

위임과 self 참조

self 참조가 동적인 문맥을 결정한다는 사실을 이해하고 나면 상속을 자식 클래스에서 부모 클래스로 self 참조를 전달하는 메커니즘으로 바라볼 수 있다.
self 참조는 항상 메시지를 수신한 객체를 가리키므로 메서드 탐색 중에는 자식 클래스의 인스턴스와 부모 클래스의 인스턴스가 동일한 self 참조를 공유하는 것으로 볼 수 있다.


인상깊었던 점

"상속은 타입 계층을 구조화하기 위해 사용해야한다."
이번 장에서 계속 강조했던 문장이었던것 같다.
상속의 관점에서 다형성이 구현되는 메커니즘을 살펴보면서 self 참조와 super 참조에 대해 알 수 있었다.
또한 self 전송의 설명을 통해 상속을 통해 다형성을 구현할 때는 내부 구현을 알아야 하기 때문에 단순히 코드 재사용 측면에서 상속을 이용하기에는 위험할 수 있다는 생각이 들었다.

'공부방 > 북스터디' 카테고리의 다른 글

[오브젝트] 14장  (0) 2024.04.15
[오브젝트] 13장  (0) 2024.04.08
[오브젝트] 11장  (0) 2024.03.18
[오브젝트] 10장  (0) 2024.03.04
[오브젝트] 9장  (0) 2024.02.20

11장 정리

상속은 부모 클래스의 정의 대부분을 물려받으므로 제대로 사용하기 위해서는 부모 클래스의 내부 구현에 대해 상세히 알아야한다.
따라서 코드를 재사용할 수 있는 쉬운 방법이지만, 우아한 방법이라고는 할 수 없다.
합성은 내부에 포함되는 객체의 구현이 아닌 퍼블릭 인터페이스에 의존한다.
그러므로 포함된 객체의 내부 구현이 변경되더라도 영향을 최소화할 수 있기 때문에 변경에 더 안정적인 코드를 얻을 수 있다.

상속을 합성으로 변경하기

10장에서 상속의 문제점들은 합성을 이용하면 구현이 아닌 인터페이스에 의존하기 때문에 보완이 가능하다.

상속으로 인한 조합의 폭발적인 증가

기능을 추가하면서 부모 클래스에 추상 메서드를 추가하면 상속받은 자식 클래스를 모두 수정해야한다.
하지만 여기서 수정하는 부분은 모두 구조가 비슷하지만 내부 구현이 조금씩만 다른 코드이므로 중복 코드의 양이 늘어난다.
이처럼 상속으로 인해 결합도가 높아지면 코드를 수정하는데 필요한 작업량이 과도하게 늘어날 수 있다.
이 또한 아래에서 설명할 합성을 이용하면 중복 코드 문제를 간단하게 해결할 수 있다.

합성 관계로 변경하기

상속 관계는 컴파일타임에 결정되고 고정되므로 코드를 실행하는 도중에는 변경할 수 없다.
따라서 여러 기능을 조합해야 하는 설계에 상속을 이용하면 조합 가능한 경우별로 클래스를 추가해야하는 클래스 폭발 문제를 겪는다.
내부 구현이 아닌 퍼블릭 인터페이스에 대해서만 의존하면 런타임에 객체의 관계를 변경할 수 있다.
합성은 이렇게 컴파일 타임 관계를 런타임 관계로 변경함으로써 이 문제를 해결한다.
물론 컴파일 타임과 런타임 의존성의 거리가 멀면 멀수록 설계의 복잡도가 증가하고 코드를 이해하기 어려워지지만 유지보수를 위한 트레이드 오프이다.

믹스인

믹스인은 객체를 생성할 때 코드 일부를 클래스 안에 섞어 넣어 재사용하는 기법을 가리킨다.
합성이 실행 시점에 객체를 조합하는 재사용 방법이라면, 믹스인은 컴파일 시점에 필요한 코드 조각을 조합하는 재사용 방법이다.
믹스인은 상속과는 다르게 is-a 관계가 아닌, 그냥 코드 조각을 다른 코드 안에 섞어 넣는 방법이다.
따라서 클래스와 클래스 사이의 관계를 고정시키지 않고 유연하게 관계를 재구성 할 수 있다.
상속처럼 상속 관계의 순서가 정해진게 아닌, 필요한 시점에 차례로 추가할 수 있는 쌓을 수 있는 변경의 특징을 가진다.

# 인상깊었던 점

"최선의 방법은 상속을 포기하는 것이다"
이번 장에서는 이 문장이 인상깊었다. 
합성이 왜 좋은지 알 수 있었고, 단순히 코드를 재사용하는거라면 합성이 상속보다 좋다는걸 알 수 있었다.
하지만 늘 그러하듯 합성을 사용하면 일종의 DSL처럼 자기만의 문법을 만드는것 같아서

변경 가능성이 낮은 단순한 기능에는 상속을 쓰고, 유연함이 필요한 부분에서는 합성을 써야할것 같다.
역시 좋은 설계를 위해서는 경험이 필요하다는 생각이 들었다.

'공부방 > 북스터디' 카테고리의 다른 글

[오브젝트] 13장  (0) 2024.04.08
[오브젝트] 12장  (0) 2024.03.26
[오브젝트] 10장  (0) 2024.03.04
[오브젝트] 9장  (0) 2024.02.20
[오브젝트] 8장  (0) 2024.02.06

커밋 컨벤션

깃을 쓰면서 프로젝트를 하면 할수록 깃 컨벤션을 잘 잡고 가야 할 필요성을 느낀다.

커밋 기록이나 PR 기록을 보면 컨벤션을 잘 맞춰서 작성하면 굉장히 깔끔해 보인다.

기본적으로 아래와 같은 형식으로 작성하는게 보통인것 같다.

(깃모지) 커밋 유형: 커밋 내용 요약
ex) feat: xxx 기능 추가

 

아래 사진도 아직 고쳐야할 부분이 보이지만 컨벤션 없이 중구난방으로 작성한 오른쪽 보단

커밋 컨벤션을 지키면서 작성한 왼쪽이 더 깔끔해 보이는것 같다.

커밋 컨벤션 표

이번 프로젝트에서 쓸 커밋 컨벤션을 표로 만든 것이다.

깃모지를 써보았는데 앞의 prefix만 잘 붙여주면 깃모지는 굳이 필요한가 싶다.

팀원 간의 협의가 있어야겠지만 아마 다음 프로젝트 부터는 안쓰는 방향으로 갈것 같다.

✨ feat: 새로운 기능 추가 및 수정 ✨ Introduce new features
🐛 fix: 버그 수정 🐛 Fix a bug
📝 docs: 문서 작성 및 수정 📝 Add or uppdate documentation
💄 design: CSS 개별 추가 및 변경 💄 Add or update documentation
♻️ refactor: 코드 리팩토링 ♻️ Refactor code
📦 chore: 패키지 추가, 설정 및 수정 📦 Add or update compiled files or packages
💡comment: 필요한 주석 추가 및 변경 💡 Add or update comments in source code
🚚 rename: 파일 또는 폴더 명을 수정하거나 옮기는 작업 🚚 Move or rename resources (e.g.:files, paths, routes)
🔥 remove: 파일을 삭제하는 작업만 수행한 경우 🔥 Remove code or files

JIRA

1차 팀 프로젝트에서 JIRA를 써서 일정 관리를 하니 좋았던 경험이 있었다.

비록 JIRA의 단편적인 기능만 사용하고 있지만 차차 쓰는 기능을 늘려보도록 하고

우선 쓰고 있는 좋은 기능을 정리해보려 한다.

에픽

 

기능을 분담하고 각자 맡은 부분에서 큼직큼직한 주제를 에픽으로 정한다.
각자 데드라인을 설정하고 정리하여 프로젝트 일정 진행 내역을 확인할 수 있다.

하위 이슈

 

에픽 아래에는 에픽을 쪼갠 내용인 하위 이슈들을 나열하여 PR 단위로 정한다.

PR이 너무 길어지면 코드 리뷰 시 피곤함을 유발할 수 있으므로 적절한 단위로 하위 이슈를 나눈다.
추후 이전 작업 내역을 찾을 때 용이하도록 티켓번호와 기능을 제목으로 하여 PR을 올린다.

10장 정리

전통적인 패러다임에서 코드를 재사용하는 방법은 코드를 복사한 후 수정하는 것이다.
객체지향에서는 일반적으로 클래스 안에서 코드를 작성하며, 코드를 재사용하기 위해 새로운 클래스를 추가해서 코드를 추가한다. 
이 때 상속을 이용하며, 재사용 관점에서 상속이란 클래스 안에 정의된 인스턴스 변수와 메서드를 자동으로 새로운 클래스에 추가하는 구현 기법이다.
객체 지향에서는 상속 외에도 합성이라는 새로운 클래스의 인스턴스 안에 기존 클래스의 인스턴스를 포함시키는 방법으로 코드를 효과적으로 재사용할 수 있다.


상속과 중복 코드

DRY 원칙 (Don't Repeat Yourself)

중복 코드는 코드를 수정시 중복된 모든 코드를 수정해야하므로 수정에 드는 노력을 몇 배로 증가시킨다.
따라서 신뢰할 수 있고 수정하기 쉬운 소프트웨어를 만드는 효과적인 방법 중 하나는 중복을 제거하는 것이다.

중복과 변경

많은 코드 더미 속에서 어떤 코드가 중복인지를 파악하는 일은 쉬운 일이 아니다.
중복 코드는 항상 함께 수정돼야 하기 때문에 수정할 때 하나라도 빠트린다면 버그로 이어질 수 있다.

상속을 이용해서 중복 코드 제거하기

상속의 기본 아이디어는 매우 간단하다. 이미 존재하는 클래스와 유사한 클래스가 필요하다면 코드를 복사하지 말고 상속을 이용해 코드를 재사용한다.
하지만 상속을 염두에 두고 설계되지 않은 클래스를 상속해 재사용하는 것은 생각보다 쉽지 않다.
개발자는 재사용을 위해 상속 계층 사이에 무수히 많은 가정을 세웠을 수도 있고, 이는 코드를 이해하기 어렵게 만들 뿐 아니라 직관에도 어긋날 수 있다.
상속을 이용해 코드를 재사용하기 위해서는 부모 클래스의 개발자가 세웠던 가정이나 추론 과정을 정확하게 이해해야 하므로 부모 클래스와 자식 클래스 사이의 강한 결합을 갖게 한다.

취약한 기반 클래스 문제

상속은 자식 클래스와 부모 클래스의 결합도를 높이며, 이로 인해 자식 클래스는 부모 클래스의 구현 세부사항에 엮이게 된다.
이처럼 강하게 결합됨으로써 부모 클래스의 변경에 의해 자식 클래스가 영향을 받을 수 있다. 이 현상을 취약한 기반 클래스 문제라고 부른다.
취약한 기반 클래스 문제는 상속이라는 문맥 안에서 결합도가 초래하는 문제점을 가리킨다. 
상속은 자식 클래스가 부모 클래스의 구현 세부사항에 의존하도록 만들기 때문에 캡슐화를 약화시키며, 이는 상속을 피해야하는 이유이다.

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

인터페이스는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 만들어야 한다.
상속을 이용하면 규칙을 무너뜨릴 여지가 있는 퍼블릭 인터페이스까지도 함께 상속받으므로 주의해야 한다.

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

자식 클래스가 부모 클래스의 메서드를 오버라이딩할 경우 자식 클래스는 부모 클래스의 메서드를 사용하는 방법까지 고려해야 한다.

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

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

상속으로 인한 피해를 최소화할 수 있는 방법

추상화에 의존하자

자식 클래스가 부모 클래스의 구현이 아닌 추상화에 의존하도록 만들면 부모의 변경에 영향을 덜 받을 수 있다.
"변하는 것으로부터 변하지 않는 것을 분리하라", 또는 "변하는 부분을 찾고 이를 캡슐화하라" 라는 조언을 메서드 수준에서 적용한다.
변하는 부분을 메서드로 추출하여 격리시키고, 변하지 않는 부분을 부모 클래스로 올린다.

추상화가 핵심이다

공통 코드를 부모 클래스로 이동시키고 변하는 부분을 추상화하면 자식 클래스들은 각각 하나의 서로 다른 변경의 이유만을 가진다. 따라서 단일 책임 원칙을 준수하므로 응집도가 높다.

합성

상속은 강력한 도구지만 코드를 재사용하기 위해 맹목적으로 상속을 사용하는 것은 위험하다.
상속의 오용과 남용은 애플리케이션을 이해하고 확장하기 어렵게 만드므로 반드시 필요한 경우에만 상속을 사용해야 한다.
코드 재사용 측면에서 대부분의 경우에 상속은 우아한 해결 방법이 아니다.
상속의 단점을 피하면서도 코드를 재사용할 수 있는 더 좋은 방법은 바로 합성이다.


인상깊었던 점

"상속은 강력한 도구지만 코드를 재사용하기 위해 맹목적으로 상속을 사용하는 것은 위험하다."
이번 장에서 말하고 싶었던건 위의 문장인거 같다.
사실상 이번 장은 상속의 문제점을 설명하고, 다음 장의 내용인 합성을 위한 빌드업이었던것 같다.
자바 라이브러리의 예시를 들어서 무분별한 상속의 단점을 잘 설명해줘서 이해에 도움이 되었다.

'공부방 > 북스터디' 카테고리의 다른 글

[오브젝트] 12장  (0) 2024.03.26
[오브젝트] 11장  (0) 2024.03.18
[오브젝트] 9장  (0) 2024.02.20
[오브젝트] 8장  (0) 2024.02.06
[오브젝트] 7장  (0) 2024.01.29

+ Recent posts