객체지향개론
객체지향개론 챕터 1
용어 정리
- 기능: 어플리케이션 관점에서 사용자한테 가치를 제공할 수 있는 행위이다.
- 책임: 기능은 시스템이 외부한테 제공하는 것이라면, 시스템 내부 관점에서 그 행위를 누가 할 것인가이다.
- 메시지: 행위를 수행할 수 있는 상세한 내용이다.
시스템을 하나의 팀 단위라고 생각하고, 해당 메시지는 “이 팀원이 수행해야 해” 라고 생각한 뒤, 세분화 하는 것이 편리하다.
클래스보다 객체, 객체보다 메시지
시스템을 구성하는 요소
- 절차적인 방법은 시스템을 함수와 데이터의 집합으로 바라본다.
- 함수형 패러다임은 함수의 집합으로 바라본다.
- 객체지향은 시스템을 객체의 집합으로 바라본다.
객체지향 시스템

- 객체지향 시스템은 객체들이 모여있는 집합체로 바라보는 관점이다.
- 객체를 구성 했다면, 다른 객체와 연결을 통해 협력을 해야 한다.
- 다른 객체와 연결점이 없고 모든 것을 처리하는 객체는 만들지 말아야 한다.

객체 협력 - 시스템의 런타임 측면: 기능 설계 💡
new 키워드를 이용하여 클래스를 생성한 이후 메모리에 할당되기 때문에, 시스템이 런타임에 어떻게 동작하는지를 설계하는 것이다.
목적: 사용자에게 가치를 제공할 수 있는 기능을 제공하는 행위
클래스와 관계 - 시스템의 컴파일타임 측면: 코드 설계
런타임 측면에서 객체간 협력을 만들어내기 위해 클래스를 코드로 구현하는 설계 과정이다.
목적: 개발자가 읽기 쉽고 변경하기 쉬운 품질 좋은 코드
객체 협력 설계하기

책임-주도 설계 객체에 할당할 책임이 설계를 주도한다.
어플리케이션이 사용자에게 어떤 기능을 제공할 것인지 우선적으로 살펴본다. 책임을 먼저 결정하고, 책임에 적합한 객체를 선택하라. ✅ 책임 주도 설계 방법
- 책임을 결정한다.
- 책임을 수행할 적합한 객체를 선택한다. (역할)
- 책임을 기반으로 메시지를 결정한다.
- 스스로 처리할 수 없는 작업을 외부 객체에게 요청한다. (협력)
- 새로운 메시지를 책임질 객체를 선택한다.
객체가 자신의 책임에 맡게 행위를 수행하다가, 자신의 책임에서 벗어난 것 같으면 다른 객체에게 책임을 넘기도록 구성한다.
이 책임은 메시지로 연결이 되고, 다른 객체에게 요청을 보내게 된다.
💡즉, 메시지 찾고 -> 객체 찾고 -> 메시지 찾고 -> 객체 찾는 행위의 반복이 된다.
Q. 시스템에 클래스가 많은 것이 좋은걸까?
A. 사이즈가 작은 클래스가 많은 것이 좋다.
클래스가 적은 대신 100줄의 비즈니스 로직을 담당하는 코드보다, 클래스가 많은 대신 10줄의 비즈니스 코드 중 어느 것을 택할 것인지 묻는다면 당연히 10줄의 비즈니스 코드이다.
객체가 작을수록 클래스도 작아진다. 이 말은 객체는 여러 클래스간의 상속 관계를 표현할 수 있기 때문에 객체가 작을수록 그 구성하고 있는 클래스들도 작아진다는 말이 된다.
클래스 설계하기

메시지와 메서드는 다르다

- 메시지: 런타임 측면에서 객체가 행위에 대한 요청을 받는 것이다.
- 메서드: 다양하게 반응할 수 있는 방법을 코드로 구현한 것이다.
메시지를 처리할 수 있는 하나의 방법을 메서드라고 한다.
객체보다 메시지(책임)가 중요하다.
상태란, 메서드를 구현하는데 필요한 속성이다. 클래스 개념에서 인스턴스 변수로 표현하며 메서드를 구현하는 데 필요한 데이터를 의미한다.> 상태는 구현 과정에서 가장 마지막 단계에서 인스턴스 변수로 활용하게 된다.
상태보다 행동이 중요하다. 구현 과정에서 객체는 어떤 상태를 갖고 있어야 한다는 설계가 먼저 나오면 잘못된 설계가 된다.
추상화
덜 필요한 것을 걷어내고, 최소한의 필요한 부분만을 남기는 과정이다.
인터페이스가 왜 필요할까? 런타임 관점에서 하나의 객체가 다양한 객체와 협력을 해야하는 경우 인터페이스가 도출 되어야 한다. 반면에, 아무것도 없는 상황에서 인터페이스를 도입하면 굉장히 이상한 설계가 된다.
- 클라이언트가 메시지를 통해서 응답에만 집중할 수 있으면 인터페이스 도출이 자연스럽다.
- 클라이언트가 메시지의 응답 값 외에 구현 내용을 공유해야 한다면 추상 클래스 도출이 자연스럽다.
인터페이스는 우리가 만든 협력이 다양한 객체들이랑 같이 엮여서 동작하는 것을 전제하게 만든다. 인터페이스를 보고 클래스를 설계 하는 것이 아니라, 클래스를 설계한 뒤 인터페이스를 통한 추상화를 도출하는 것이 올바르다.
객체지향 포인트
- 클래스보다 객체가 중요하다.
- 객체보다 메시지(책임)가 중요하다.
- 상태보다 행동이 중요하다.
- 메시지와 메서드의 분리는 유연한 설계의 기반이다.
먼저, 메시지를 통해서 정의를 한 뒤 그 내용을 구현할 수 있는 클래스를 구성한다.
도메인 레이어
물리적인 서비스 외 시스템으로 구성할 수 있는 범위가 도메인이다.
도메인 레이어를 설계 하는 방법

레이어 아키텍처에서 비즈니스 영역에 위치해 있고, 도메인 레이어를 객체 지향적으로 설계하는 것이 중점이다.
그 외에 데이터 영역이나 표현 영역에서는 절차 지향적인 방법이 훨씬 편리하고, 도메인 영역에서 일부는 절차 지향적인 설계가 편한 경우가 있다.
절차적 프로그래밍
프로세스와 데이터를 나누어 프로그래밍 하면 절차적인 방향의 설계이다.
절차적인 프로그래밍의 대부분의 방향은 기능을 먼저 정의 하지 않고 데이터부터 정의하는 것이다.
- ERD -> 클래스 -> DAO -> 기능 구현 단계로 흘러간다.
객체 지향 설계 방법의 반대 순서로 흘러가면, 절차지향적인 설계가 발생한다.
절차적 프로그래밍의 사고 흐름은 데이터를 먼저 준비하게 된 뒤 기능을 구현할 때, 필요한 데이터를 하나의 행위 안에서 조회하기 시작한다. 그리고, 정책을 따르기 위해 조건에 대한 검증을 수행한다.
이런 방법론에서 가장 크게 나타나는 부분은 타입을 검사해서, 그 타입에 맞는 행위를 수행하도록 분기점을 생성한다.
객체지향 프로그래밍
결과적으로 프로세스와 데이터가 하나로 합쳐진다.
객체가 어떤 행위를 해야 하는지, 행동 부터 설계하기 시작하고 나중에 행위에 필요한 데이터를 마지막으로 설계한다.

CRC Card
책임과 협력을 표현하기 위한 객체지향 설계 도구를 활용하면 객체지향 설계를 쉽게 접근할 수 있다.
다만, 설계용도는 아니며 교육용으로 활용이 가능하다.
코드의 목적

응집도 / 결합도 / 캡슐화 등 이런 개념은 객체 관점에서 얘기를 하긴 하지만, 사실상 클래스 관점에서 얘기를 하는 것이다.
- 이런 전통적인 정의만으로 코드에서 판단의 기준이될 수 없다.
객체지향개론 챕터 2
설계
설계란, 코드를 배치하는 방식이며 코드를 구현하는 의사 결정 과정이다.
우리가 짜는 프로그램은 두 가지 요구사항을 만족시켜야 한다. “우리는 오늘 완성해야 하는 기능을 구현하는 코드를 짜야 하는 동시에 내일 쉽게 변경할 수 있는 코드를 짜야 한다.” - 샌디 메츠
기능을 사용하는 사용자 관점과 이 코드를 나와 같이 개발하는 동료의 관점으로 설계 해나가야 한다.
인터페이스나 추상 클래스가 없으면서 읽기 쉬운 코드는 고치기도 쉽다. 설계의 트레이드 오프 중 심플함과 유연함을 고르는 과정이 존재하는데, 항상 변경하기 쉬운 방식을 택해야 한다.
좋은 설계란? 변경하기 쉽게 코드를 배치하는 것
코드의 모양만으로 설계의 품질을 판단할 수 없고, 변경 사항이 무엇인지 알아야 한다. 좋은 설계는 변경이 굉장히 중요한 요소이고, 어떤 변경이 있기 때문에 코드를 배치했다는 근거가 필요한 것이다.
설계의 핵심
모든 설계 관련 용어와 원칙은 변경과 관련되어 있으며, 응집도, 결합도, 캡슐화 또한 변경과 관련된 내용이다.
- 응집도 측면에서, 객체지향이 변경하기 더 용이한 설계를 가지고 있다. (응집도 내용 참고)
- 결합도 측면에서, 객체지향이 변경하기 더 용이한 설계를 가지고 있다. (결합도 내용 참고)
- 캡슐화 측면에서, 객체지향이 변경하기 더 용이한 설계를 가지고 있다. (캡슐화 내용 참고)
SOLID
변경하기 쉬운 유연한 설계를 위한 원칙
디자인 패턴
다양한 디자인 패턴의 목적은 변경을 감추는 것이다. “이러한 변경 사항이 있을 때, 특정 디자인 패턴을 사용해야 한다” 라는 관점으로 적용해야 한다.
응집도
코드를 수정할 때, 원인이 분산되지 않고 한 모듈에서만 해결하고 싶은 욕구가 있다. 그래서 응집도란, 동일한 이유로 코드가 변경 되는 정도를 나타낸다.
응집도가 낮은 경우는, 아래 예시 처럼 서로 다른 이유로 코드가 바뀌는 경우이다.

가장 쉽게 확인되는 응집도 낮은 코드는, 깃에서 머지할 때 컨플릭트가 자주 발생하는 코드는 서로 다른 개발자가 각각의 다른 이유로 같은 모듈을 수정하는 경우에 응집도가 낮은 것을 쉽게 파악할 수 있다. 이러한 경우는 그 코드의 응집도를 높이기 위해 객체를 다시 설계하도록 하는 것이 올바르다.
높은 응집도는, 아래 예시처럼 모듈에서 변경해야 하는 이유가 하나인 경우이다.

가격 할인 정책, 정률 할인 정책, 순번 할인 검증 등 클래스가 모두 각자의 책임에 따라 쪼개어져 있어, 변경해야 할 이유가 단 하나만 존재하게 된다. 즉, 높은 응집도는 각 클래스는 변경될 이유가 하나여야 하는 SRP 원칙을 따른다.
결합도
보라색 모듈이 변경될 때 파란색 모듈이 영향을 받아서 변경되면 의존서이 있다고 표현한다.
결합도는 강하거나, 느슨하다고 표현하며 보라색이 10번 변경될 동안 파란색 모듈이 10번 모두 같이 변경되면 결합도가 높다. 반대로, 보라색이 10번 변경될 동안 파란색 모듈이 5번만 바뀌면 결합도가 상대적으로 낮다고 표현한다.
즉, 결합도는 상대적으로 다른 모듈이 변경될 때 나 자신이 같이 변경되는 정도이다.

추상 클래스와 인터페이스를 추출했다고 해서 모두 추상화가 아니다. 추상화는 자주 변경되지 않는 부분만 남기고 자주 바뀌는 것을 모두 덜어내어 구현하도록 만드는 것이다.
인터페이스 구현 분리 원칙에서 말 하는 인터페이스는 언어가 제공해주는 인터페이스 클래스가 아닌, 객체가 호출하는 접점(주로 메서드에 해당)을 뜻한다. 이 때, 인터페이스와 구현을 잘 식별하여 구현에서는 변하지 않는 인터페이스를 의존하도록 설계 하는 것을 의미한다.
무엇이 자주 바뀌는 것이고, 무엇이 자주 바뀌지 않는 것일까? 어떻게 판단할까?
구현을 인터페이스로 잘 막고, 얼마나 인터페이스에만 의존하는지가 결합도의 척도이다.
클래스의 속성이 구현에 해당 한다. 이 속성을 자주 변경하기 때문에 이 부분이 자주 변경되는 불안정한 부분이 된다.
아래 예시는, 구현만 변경했을 뿐인데 인터페이스와 의존하고 있는 클라이언트 모두 변경이 되는 결합도가 높은 설계이다.

반대로, 아래 예시 처럼 느슨한 결합도의 설계는 구현을 변경하더라도 인터페이스와 의존하고 있는 클라이언트는 변경되지 않는다.

캡슐화
변경 가능성이 높은 부분을 내부로 숨기는 추상화 기법이다. 설계에서 변하는 것이 무엇인지 고민하고, 변하는 개념을 캡슐화 하는 것이 좋은 설계 방식이다.
즉, 캡슐화는 자주 변경되는 내부의 구현 부분을 인터페이스로 포장하여 감추는 것이다.
- 변경되는 것을 외부에서 알지 못하도록 감추는 기법이다.


타입 캡슐화는 타입을 숨기기 위해 캡슐화 하는 방식으로, 아래 예시 처럼 Movie 가 DiscoutPolicy 만을 의존하여 타입이 변경 되더라도 Movie 는 모른다.
그래서, 타입 캡슐화를 하게 되면 의존성 역전 원칙을 따르게 된다.
- DIP: 상위 모듈과 하위 모듈 모두 추상화에 의존하는 원칙
- Movie 는 DiscountPolicy 만 의존하기 때문에 런타임에서 타입에 영향을 받지 않는다.

- Movie 는 DiscountPolicy 만 의존하기 때문에 런타임에서 타입에 영향을 받지 않는다.
변경하기 쉬운 설계의 특징

- 높은 응집도: 클래스 안의 코드가 동일한 이유로 변경
- 낮은 결합도: 클래스의 구현 변경 시 부수효과 제한
- 캡슐화: 변하는 무엇이든 감추는 것
이 세가지 특성 중에서는 캡슐화가 가장 중요한 요소이다. 캡슐화를 잘 준수하여 설계하면 자연스레 응집도, 결합도는 좋은 설계 방향으로 흘러간다.
기능이라는 컨텍스트 안에서 설계 흐름
- 협력에 필요한 메시지를 먼저 결정
- 메시지에 적합한 객체를 나중에 결정
- 메시지를 처리할 코드 작성 -> 응집도, 결합도, 캡슐화가 이 코드 작성 시점에 결정 됨
- 특정한 협력 안에서 메시지를 처리할 코드 작성
- 메시지를 처리하는데 필요한 상태를 나중에 결정 -> 데이터 협력
- 두가지 협력 관계에서, 메시지 관점에서 추상화 결정 -> 타입 캡슐화
- 여러 종류의 객체와 협력 가능한 유연한 설계
도메인 정책 변경에 대응하기
영화 예매 시스템에서 할인 정책을 한 개만 적용이 가능 했었으나, 앞으로는 중복으로 할인이 가능하도록 변경된다고 한다.
절차적인 코드는 요구사항이 변경되는 것을 수용하면서 하나의 메서드에서 굉장히 많은 역할을 맡게 된다. 가장 문제는 이러한 구현을 수정하는 것이 아니라, 기존 코드를 수정하면서 발생할 수 있는 사이드 이펙트를 겉잡을 수 없어서 두려움을 느끼는 경우이다.
- 응집도가 낮은 코드의 대표적인 사례는 테스트 케이스가 굉장히 많다.
- 절차적 코드에서 데이터와 프로세스가 나뉘어지면서 발생한 알고리즘은 어떤 행위인지 알기 어렵다.
객체지향 코드에서 디자인 패턴을 활용한 방법

객체지향 설계에서 응집도가 높고 결합도가 낮은 설계를 통해 기존 코드를 수정하지 않고 새로운 클래스를 추가 하면서 변하는 요구사항에 대응할 수 있다. 이 것이 바로 OCP 원칙을 준수하게 되는 것이며 메시지 기반의 타입 캡슐화이고, 런타임 관점에서 생각하고 협력 관점에서 메시지를 계속 진화하는 설계를 구성할 수 있게 되는 것이다.
만약, “중복할인” 이라는 객체에서 수정이 필요하다면 아래 예시에 나와있는 OverlappedDiscountPolicy 만 수정하면 된다.

디자인 패턴은 변경되는 어떠한 것을 감추기 위한 목적으로 활용된다.
- 변경되는 요소에 따라 적용할 수 있는 디자인 패턴이 각 각 다른 것을 사용하는 것이기 때문에, 디자인 패턴이 좋다고 막 사용하는 것이 아니라 항상 디자인 패턴을 선택하기 위해 무엇이 변경 되는지 무엇을 캡슐화 하는지에 따라 선택해야 한다.
컴포지트(조합) 디자인 패턴
변경되는 객체의 개수를 감추어 적용하고자 한다면 컴포지트 패턴을 이용하여 객체를 조합하여 사용한다.
- 변하는 요구사항에 대응하는 방법 중, 우리가 처리하고자 하는 할인 정책이 1개 였지만 변경사항에 따르면 앞으로 N 개를 적용하고 싶었다. 이렇게 여러 개를 처리하도록 변경되었을 때 개수의 변화를 감추기 위해 컴포지트 디자인 패턴이 활용 되었다.
객체지향 설계가 적합할 때
복잡하고 끊임없이 변하는 비즈니스 규칙을 구현해야 한다면, 객체 모델을 사용해 비즈니스 규칙을 처리하는 것이 현명하다. - 마틴 파울러
객체지향은 끊임없이 변하는 요구사항을 보다 수월하게 변경하기 위해서 적용한다.
우리는 요구사항을 받았을 때, 처음 부터 위와 같은 객체지향 설계를 한 번에 설계할 수 있는 사람은 없다.
- 객체지향 설계를 마음 먹었다고 하더라도 해당 요구사항에서는 무엇이 변경될 요소인지 알아내기가 어렵기 때문이다.
- 그렇기 때문에 대부분 절차지향적 코드의 시작으로 프로젝트를 진행하다가, 자주 변경되는 요소를 찾고 추상화를 도출하고 그렇게 점진적으로 객체지향적으로 발전하게 된다.
요구사항이 어떻게 변경될지 모른다면

처음부터 우린 객체지향 설계를 완벽히 해낼 수 없기 때문에, 요구사항이 변경 될 때 마다 변경하기 쉬운 코드로 리팩터링을 지속적으로 시도하는 것이다.

절대 코드를 한 번에 이상적인 코드를 작성하려고 하지말고, 자주 리팩토링을 시도하며 점진적으로 발전시키자.
코드를 개선하기 위해 이런 지식이 뒷받침 되어야한다.
- 원칙의 이해
- 디자인 패턴의 이해
- 요구사항에서 변경되는 요소에 대한 이해
- 변경을 쉽게 만들기 위한 설계에 대한 이해
Q&A
객체지향에 조금 더 익숙해지고자 공부해보려고 하는데 책이나 추천하는 공부 방법이 있는가?
- 객체지향의 사실과 오해는 객체지향의 개론적인 내용
- 오브젝트는 4-5년차 정도 해당되는 개발자가 읽으면 좋은 내용
- 리팩터링과 켄트벡의 TDD 책 읽어보기