내용 정리: 20240301 TIL, 20240302 TIL
1. 개요
2장은 객체지향 프로그래밍에 사용되는 다양한 요소에 대해 설명한다.
주요 키워드로는 "클래스, 캡슐화, 협력, 다형성, 상속" 정도가 있다.
2장은 1장보다 많은 내용이 등장해 정리하기 쉽지 않았다.
하지만 기존에 오해하고 있던 개념들을 바로 잡을 수 있었다.
2. 클래스
클래스에 대한 고정관념
객체지향 프로그래밍에 대해 생각할 때 대부분의 사람들은 '클래스'라는 단어를 먼저 떠올린다.
그만큼 우리는 알게 모르게 "객체지향 = 클래스"라는 고정된 사고방식을 가지고 있을지도 모른다.
실제로 많은 개발자들이 코드를 작성할 때, 먼저 클래스를 몇 개 생성하고 그 안에 어떤 속성과 메서드를 넣을지 고민한다.
나 역시 그동안 설계를 할 때 클래스를 먼저 작성했던 것 같다.
객체지향은 객체들의 협력을 통해 이루어지며, 각 객체는 자신의 역할과 책임을 가지고 자유롭게 행동한다.
저자는 위 내용을 반복해서 강조한다.
객체지향에서는 클래스가 중심이 되는 것이 아니라 각 객체가 중심이 되어야 한다.
클래스를 먼저 결정하고 그 안에 속성과 메서드를 배치하는 방식은 객체지향의 본질에서 벗어난 것이다.
올바른 설계 순서
올바른 객체지향 설계 방식은 다음 순서로 진행된다:
- 먼저 해결해야 할 문제(도메인)를 구조적으로 그린다.
- 구조화된 내용을 객체들의 관계로 추상화한다.
- 객체들의 관계를 바탕으로 실제 클래스를 작성한다.
클래스 작성은 설계 과정의 마지막 단계에 위치한다.
이는 클래스가 도메인과 객체들을 표현하는 수단에 지나지 않는다는 사실을 보여준다.
올바른 객체지향 설계를 위해서는 클래스의 구조보다는 객체들 간의 관계와 협력에 초점을 맞춰야 함을 알게 되었다.
3. 캡슐화
캡슐화
1장에서본 바와 같이 객체지향은 상태(데이터)와 행동(프로세스)을 하나의 단위, 즉 객체로 결합하는 패러다임이다.
이렇게 상태와 행동을 하나로 결합하는 행위를 캡슐화라고 한다.
캡슐화는 객체의 상태와 행동을 내부에 두어 외부에서의 직접적인 접근을 차단하는 방식으로 이뤄진다.
캡슐화의 목적
캡슐화를 사용하는 이유는 객체 내부 상태에 대한 외부의 간섭을 최소화하여 객체의 자율성을 보장하기 위함이다.
캡슐화가 이뤄지면 외부에서는 객체가 결정을 내리는 동안 내부에서 일어나는 과정에 개입하지 못한다.
덕분에 객체는 외부의 영향 없이 독립적으로 결정을 내릴 수 있게 되고, 자율적인 존재가 된다.
객체가 스스로의 로직에 따라 행동함으로써 시스템의 각 부분은 독립적으로 기능하게 된다.
구현 은닉과 접근 제어
캡슐화와 함께 사용되는 개념이 구현 은닉(implementation hiding)이다.
구현 은닉은 객체의 내부 구현을 숨기고 필요한 부분만을 외부에 공개하는 방식으로 이뤄진다.
이 과정에서 공개된 부분은 퍼블릭 인터페이스라고 부르고, 객체가 외부와 소통하는 메시지를 정의한다.
외부에서 접근할 수 없게 숨겨진 부분은 구현이라고 부르고, 객체가 어떻게 메시지를 처리할지 정의한다.
이러한 분리는 접근 제어자(public, private 등)를 통해 이뤄진다.
접근 제어자를 사용해 클래스의 내부와 외부를 명확히 구분할 수 있다.
구현 은닉은 변경 관리에 있어 중요한 역할을 한다.
내부 구현이 은닉되어 있기 때문에 개발자가 내부 로직을 변경하더라도 외부 코드를 수정할 필요가 없다.
외부에서도 내부 구현에 의존하지 않아 실수로 내부 코드를 오용하는 것을 방지할 수 있다.
4. 협력
객체 간의 협력
협력(collaboration)은 시스템의 어떤 기능을 구현하기 위해 객체들 사이에서 이뤄지는 상호작용을 의미한다.
협력이 이뤄지는 순서는 다음과 같다:
- A 객체가 B 객체에게 메시지를 전송한다. (요청)
- B 객체는 A 객체로부터 메시지를 받는다. (수신)
- B 객체는 메시지를 처리할 방법을 결정한다. (메서드 결정)
- B 객체가 A 객체에게 결과를 반환한다. (응답)
메서드와 메시지의 구분
협력을 이해하는 데 있어 중요한 개념은 메시지와 메서드의 구분이다.
메시지는 객체에게 전달되는 요청(request)이다.
메서드는 이러한 요청을 처리하기 위한 구체적인 방법이다.
일반적으로 '객체의 메서드를 호출한다'라는 표현을 사용한다.
하지만 실제로는 메시지를 통해 요청을 보내고, 객체가 수신된 메시지를 바탕으로 메서드를 선택(바인딩)하여 처리하게 된다.
따라서 '객체에게 메시지를 전송한다'라는 표현이 더 적절하다.
협력에 대한 이해
'객체지향의 사실과 오해'라는 책을 통해 협력이라는 개념을 처음 접했다.
그때는 협력을 단순히 객체들이 함께 작업을 수행하는 것으로 이해했다.
하지만 이번 장을 읽는 동안 협력이라는 과정이 서버와 클라이언트 간의 상호작용과 유사하다는 느낌을 받았다.
서버와 클라이언트는 서로의 내부 구현 방식은 모르지만, 요청과 응답을 통해 필요한 작업을 수행한다.
객체들 또한 서로의 내부 구현을 명확히 알지 못하더라도 메시지 교환을 통해 시스템의 전체 기능을 수행할 수 있을 것이다.
이러한 관점을 통해 객체지향 프로그래밍에서 말하는 협력의 진정한 의미를 이해할 수 있게 되었다.
객체 간의 협력은 단순히 함께 일하는 것을 넘어서, 서로의 독립성을 유지하며 목표를 달성하는 과정이다.
5. 상속
상속이란?
상속(inheritance)은 부모 클래스의 인터페이스를 자식 클래스가 물려받는 것을 의미한다.
상속을 통해 자식 클래스는 부모 클래스가 이해할 수 있는 메시지를 동일하게 이해할 수 있게 된다.
상속에 대한 오해
상속을 단순히 부모 클래스의 코드 재사용 목적으로 사용하는 경우가 많다.
그러나 코드를 재사용하기 위해 상속을 사용하는 것은 좋지 않은 설계 방법이 될 수 있다.
부모 클래스의 변경이 자식 클래스에게 영향을 미칠 수 있고 이는 변경에 취약한 설계가 된다.
따라서 코드 재사용 목적으로의 상속 사용은 권장되지 않는다.
상속은 주로 공통된 인터페이스를 공유할 필요가 있을 때, 즉 같은 메시지를 이해해야 하는 클래스들 사이에서 사용해야 한다.
내가 생각한 상속
상속은 객체지향하면 먼저 떠오르는 키워드 중 하나이다.
그래서인지 상속을 사용하는 것이 객체지향을 잘 활용한 설계라고 생각해 왔다.
하지만 저자의 상속에 대한 평가는 그렇지 않았다.
상속은 설계를 유연하지 못하게 만들 수 있는 요소이기에 피해야 한다.
상속이라는 도구를 사용하기 전에 상속의 장점과 단점이 무엇인지 명확하게 알고 써야겠다.
6. 다형성
다형성이란?
다형성은 실행 시점에 객체가 전송한 메시지에 따라 실제로 실행되는 메서드가 달라질 수 있는 성질을 의미한다.
동일한 메시지를 받더라도 메시지를 받은 객체의 실제 클래스에 따라 다른 메서드가 실행될 수 있다.
즉 실행 시점의 동적 바인딩을 통해 다양한 형태의 객체가 동일한 인터페이스 아래에서 각기 다른 행동을 할 수 있게 하는 것이 바로 다형성이다.
다형성을 구현하는 방법
다형성을 구현하기 위해서는 협력에 참여하는 모든 객체가 동일한 메시지를 이해할 수 있어야 한다.
이는 객체들의 인터페이스가 통일되어 있다는 의미이고, 이를 위해 보통 상속을 사용한다.
하지만 상속 외에도 다형성을 구현할 수 있는 여러 방법이 존재한다.
앞으로 책을 읽다 보면 그 방법들이 제시된다고 한다.
다형성의 양면
다형성을 활용하면 유연한 설계가 가능해지고 객체 간의 의존성이 줄어든다.
이를 통해 변경에 강하고 확장성 있는 코드를 작성할 수 있다.
그러나 다형성이 항상 긍정적인 결과만을 가져오는 것은 아니다.
다형성은 설계의 유연성을 증가시키지만 코드의 가독성과 이해도를 낮춘다.
따라서 다형성을 통한 유연한 설계와 코드의 가독성 사이에 발생하는 트레이드오프를 고려해야 한다.
개인적으로 다형성은 객체지향 프로그래밍의 특징을 가장 잘 드러내는 키워드라고 생각한다.
하지만 다형성도 좋은 요소만 있지 않고 트레이드오프가 필요하다는 것을 알게 되었다.
7. 코드 재사용
상속과 합성
코드 재사용은 개발에서 중요한 목표 중 하나이다.
하지만 앞에서 언급했듯, 상속을 통한 코드 재사용은 좋은 방법이 아니다.
저자는 상속의 대안으로 합성(composition)을 제시한다.
합성이 상속보다 코드 재사용에 좋은 방법인 이유가 뭘까?
상속의 단점
상속이 코드 재사용을 위해 널리 사용되긴 하지만 두 가지 단점이 존재한다.
첫째, 상속은 캡슐화 원칙을 위반한다.
상속을 하게 되면 부모 클래스의 구현이 자식 클래스에게 노출된다.
때문에 부모 클래스의 변경이 자식 클래스에도 영향을 미치게 되고, 결합도를 높인다.
둘째, 상속은 설계의 유연성을 제한한다.
상속 관계는 컴파일 시점에 결정되므로 실행 시점에 객체의 타입을 변경하는 것이 불가능하다.
합성의 장점
합성은 객체의 인스턴스를 다른 객체의 인스턴스 변수로 포함시켜 재사용하는 방법이다.
이를 통해 객체 간의 결합도를 낮추고 인터페이스를 통한 메시지 교환을 유도할 수 있다.
합성을 사용하면 클래스 관계가 런타임에 결정되므로 설계의 유연성이 크게 향상된다.
또한 내부 구현을 숨겨 캡슐화를 유지할 수 있게 해 준다.
상속과 합성을 적절히 사용하자
코드 재사용을 위해서 상속보다는 합성을 사용하자.
합성을 사용하면 더 유연하고 결합도 낮은 설계가 가능하다.
그러나 다형성을 구현하기 위해 인터페이스를 재사용할 필요가 있는 경우에는 상속을 사용해야 한다.
상황에 맞게 상속과 합성을 적절히 사용하여 각각의 장점을 활용하는 것이 좋다.
8. 느낀 점
예전에 C++를 공부하면서 객체지향에 대해 배운 적이 있다.
하지만 이번 장을 읽으며 기존에 알고 있던 객체지향을 새로운 관점으로 볼 수 있게 되었다.
2장을 읽은 후 객체지향 프로그래밍에서 중요한 각 요소들을 보다 잘 이해할 수 있게 되었다.
특히 객체지향 설계에서 왜 메시지에 초점을 맞춰야 하는지에 대한 이유를 다양한 요소들을 통해 알 수 있었다.
책을 따라가다 보니 객체지향의 본질이 메시지에 있다는 설명이 점점 와닿고 있는 것 같다.
'독서 기록' 카테고리의 다른 글
[오브젝트] 5장 - 책임 할당하기 (0) | 2024.03.25 |
---|---|
[오브젝트] 4장 - 설계 품질과 트레이드오프 (0) | 2024.03.19 |
[오브젝트] 3장 - 역할, 책임, 협력 (0) | 2024.03.11 |
[오브젝트] 1장 - 객체, 설계 (0) | 2024.02.22 |
22년 9~10월 독서 기록 (0) | 2022.10.31 |
댓글