- 객체지향 시스템의 근간을 이루는 것은 메시지이지만 가장 눈에 띄는 구조는 클래스
- 무엇이 클래스에 속하는지, 어떻게 알 수 있는지에 대해서 설명
설계할 때 고민하는 것
- 어떤 클래스를 만들어야 할까
- 몇 개나 만들어야 할까
- 어떤 행동을 구현해야 할까
- 하나의 클래스는 다른 클래스에 대해서 얼마나 알고 있어야 할까
- 다른 클래스에게는 어느 정도까지 열려있어야 할까
클래스를 만들 때 명심해야 하는 것
- 클래스는 단순해야 한다
-
클래스를 이용해서 애플리케이션을 모델링하는 것이 우리의 목표
- 지금 당장 해야 할 일을 할 줄 알고, 나중에도 쉽게 수정할 수 있는 클래스를 가지고 모델링 해야 한다.
무엇을 클래스에 넣을지 결정하기
- 문제는 기술에 대한 지식이 아니라 코드를 구성하고 배치하는 일이다. 우리는 코드를 짤 줄은 알지만 그것들을 어디에 어떻게 놓아야 하는지를 모르고 있다.
-
클래스를 만드는 것
- 주어진 상상의 범위 밖에서는 사고하기 힘든 하나의 박스를 만드는 것
- 메서드를 제대로 묶어 클래스를 만드는 일을 중요하지만, 프로젝트 초기 단계에서 메서드를 제대로 묶어내기는 어렵다. -> 당장 주어진 정보가 너무 부족하기 때문
- 디자인이란 완벽함을 추구하는 행위라기보다 코드의 수정 가능성을 보존하는 기술
- 수정하기 쉬움이라는 가치 속에서 프로그래밍의 기술이 드러난다.
-
수정하기 쉽다의 정의
- 수정이 예상치 못한 부작용을 낳지 않는다.
- 요구사항이 조금 변했을 때 연관된 코드들을 조금만 수정하면 된다.
- 현재 코드를 다시 사용하기 쉽다.
- 코드를 수정하는 가장 쉬운 방법은 이미 수정하기 쉬운 코드에서 새로운 코드를 추가하는 것이다.
-
수정하기 쉬운 코드의 특징 (TRUE)
- 투명하다 : 수정된 코드, 이 코드와 연관된 코드 속에서 수정의 결과가 뚜렷하게 드러나야 한다.
- 적절하다 : 모든 수정 비용은 수정 결과를 통해 얻은 이득에 비례해야 한다.
- 사용 가능하다 : 예상치 못한 새로운 상황에서도 현재 코드를 사용할 수 있어야 한다.
- 모범이 된다 : 코드 자체가 나중에 수정하는 사람이 위의 특징을 이어갈 수 있게 도와줘야 한다.
- 이런 코드를 짜기 위한 첫 단추는 모든 클래스들이 하나의 잘 정의된 책임을 갖도록 하는 것
하나의 책임만을 지는 클래스
- SOLID
SOLID 원칙은 함수와 데이터 구조를 클래스로 배치하는 방법, 그리고 이들 클래스를 서로 결합하는 방법을 설명해준다. SOLID 원칙의 목적은 중간 수준의 소프트웨어 구조가 아래와 같도록 만드는 데 있다. - 변경에 유연하다. - 이해하기 쉽다. - 많은 소프트웨어 시스템에 사용될 수 있는 컴포넌트의 기반이 된다. 클린 아키텍처 참고
* SRP : 단일 책임 원칙
* OCP : 개방 폐쇄 원칙
* LSP : 리스코프 치환 원칙
* ISP : 인터페이스 분리 원칙
* DIP : 의존성 역전 원칙
-
단일 책임 원칙
- 하나의 클래스는 최대한 작으면서도 유용한 것만 해야 한다. = 하나의 책임만 있어야 한다.
-
응집성이 높은 모듈을 만들도록 하는 원칙
- 클래스 안의 모든 것들이 하나의 핵심 목표와 연관되어 있을 때 이 클래스는 강하게 응집되어 있다.
- 단일 책임 원칙은 클래스가 매우 협소한 한 가지 역할만 해야 한다고 말하는 것이 아니다. 또는 사소한 수정만 해야 한다고 말하는 것도 아니다.
- 클래스가 응집되어 있어야 한다고 말하는 것이다.
- 클래스의 모든 활동은 그 존재 이유와 밀접히 연관되어 있어야 한다.
- 모듈의 변경의 이유가 하나의 이해관계자 되는 사용자에 의해서만 변경되어야 하는 것을 의미
-
왜 중요한가?
- 쉽게 수정할 수 있는 애플리케이션은 재사용하기 쉬운 클래스로 구성되어 있다.
- 재사용할 수 있는 클래스란 쉽게 가져다 쓸 수 있는 코드(pluggable unit)다.
- 한 개 이상의 책임 있는 클래스는 재사용이 어렵다.
- 애플리케이션이 너무 많은 것을 하는 클래스에 기대고 있으면 예상치 못한 오류가 발행할 가능성도 높아진다.
-
클래스에게 하나의 책임만 있는지 알아보기
- 클래스를 인격이 있는 존재처럼 가정하고 질문을 던져보는 방법
-
클래스의 책임을 한 문장으로 만들어 보는 것
- 그리고 -> 이 클래스는 하나 이상의 책임을 가지고 있을 가능성이 높다.
- 또는 -> 클래스는 서로 연관되지도 않은 둘 이상의 책임을 가지고 있다.
디자인 결정 시기
- 우리가 미래에 어떤 기능이 필요할지 이미 알고 있다면 지금 당장 완벽한 디자인을 선택할 수 있다. 불행히도 우리에게는 그런 정보가 없다. 어떤 일이 생길지 모른다. 그럴듯해 보이는 대안들 사이에서 저울질하다가 시간만 허비하고 결국 아무 근거 없이 하나의 디자인을 선택하지만 결국 잘못된 선택을 할 뿐이다. 28p
- 더 많은 정보를 얻을 때까지 일단은 그냥 기다리고 있는 쪽이 가장 비용-효율적인 접근일지도 모른다.
- 지금 당장 개선하기 와 나중에 개선하기 사이에는 언제나 긴장이 흐른다. 좋은 디자이너는 이 긴장을 이해하고 있고 당장의 필요와 미래의 가능성 사이에서 심사숙고하여 개선비용을 최소화한다.
변화를 받아들일 수 있는 코드 작성하기
-
데이터가 아니라 행동에 기반한 코드 작성
-
인스턴스 변수를 감싸는 래퍼 메서드의 사용
- 데이터를 마치 메시지를 이해하는 객체처럼 취급하는 것.
- 좀 더 추상적이다. -> 모든 변수를 래퍼 메서드로 감싸고 변수를 마치 객체처럼 사용할 수 있기 때문에 데이터와 객체 사이의 구분이 무의미해진다.
-
복잡한 데이터 구조에 의존하는 것을 가리기
- 복잡한 구조를 직접 참조하면 진짜 데이터가 무엇인지 드러내지 않기 때문에 우리를 헷갈리게 한다.
-
-
모든 곳에 단일 책임 원칙 강제
- 메서드도 클래스처럼 하나의 책임을 지도록 쪼개기
-
리팩터링
- 리팩터링은 디자인이 명확하기 때문에 필요한 것이 아니라 오히려 디자인이 불명확하기 때문에 필요한 것이다.
-
메서드가 단일 책임을 질 때 얻을 수 있는 이점
- 리패터링을 통해 클래스의 모든 메서드가 하나의 책임만 지게 되면 클래스 자체가 명확하게 드러난다.
- 주석을 넣어야 할 필요가 없어진다.
- 재사용을 유도한다.
- 다른 클래스로 옮기기 쉽다.
-
클래스 내부에서 책임 격리하기
- 우리의 목표는 최대한 특정 디자인에 종속되지 않으면서, 클래스가 하나의 책임만을 지도록 하는 것
- 수정하기 쉬운 코드를 작성하려면 진짜 어쩔 수 없는 순간이 올 때까지 최대한 디자인 결정을 미루는 것이 좋다.
- 충분한 정보가 모아질 때까지 기다렸다가 분리한다. 그전에는 클래스 내부에서 최대한 분리될 책임을 격리해보자.
- 너무 많은 책임을 지고 있는 클래스가 있다면 다른 클래스로 책임을 분리한다.
- 핵심 클래스에 집중한다.
- 클래스의 책임이 무엇인지 결정하고 우리의 결정을 꼼꼼히 체크
- 아직 제거하기 어려운 책임을 발견했다면, 그 책임을 내부에서 격리시킬 것