[Book] Object
요즘 오브젝트(Object)를 읽는 중인데, 챕터별로 간단하게 기억해볼만한 부분들을 남기고자 함. 모든 내용은 책1의 해당 챕터에서 모두 발췌함.
Chapter 1 객체, 설계
좋은 코드란 1)잘 작동해야함 2)변경이 편해야함 3)보기 편해야함. 이걸 할 수 있기 위해서는 객체화가 중요함. 객체화는 데이터와 프로세스를 하나로 때려박는게 아님. 설계(코드를 배치하는 행위)를 잘 해서 객체들 사이의 의존성을 적절하게 조절하여 좋은 코드를 작성하도록 함.
Chapter 2 객체지향 프로그래밍
객체지향 패러다임의 전환은 클래스가 아닌 객체에 초점을 맞추는것. 설계할 때에는 모든 객체들이 살아움직이도록 어떤 상태와 행동(합쳐서 캡슐화)을 가지는지를 결정해야함. 접근 제어는 자율화에 유리. 이를 위해 인터페이스와 구현은 분리되어야함. 또 구현은닉을 도모할 수 있음. 따라서 인터페이스와 구현을 깔끔하게 분리시켜 변경을 쉽게할 것. 객체는 독립된 것이 아니라 협력된 공동체이다.
설계가 유연해지면 코드를 이해하고 디버깅하는게 어렵고 유연성을 억제하면 재사용과 확장가능성은 낮아짐. 재사용 가능한 설계의 기본을 이루는 디자인패턴/프레임워크 모두 추상화를 이용해 상위 정책을 정의. 추상화를 이용해 상위정책을 표현하면 기존 구조를 수정하지 않고도 새로운 기능을 쉽게 추가 확장 가능 (유연한 설계). 코드를 재사용하는 경우에는 상속보다 합성(wrapper같은 개념인듯)을 선호하는 것이 옳지만 다형성을 위해 인터페이스를 재사용하는 경우에는 상속과 합성을 조합해서 사용할 수 밖에 없다.
Chapter 3 역할, 책임, 협력
객체지향의 본질=협력하는 객체들의 공동체를 창조하는 것. 핵심은:
- 협력= 시나리오. 모델링과 유사. 협력은 책임을 결정할 문맥.
- 책임= Doing. Knowing. 가장 중요함! 책임을 결정하고 구현을 고민할 것. 책임은 divide and conqure처럼. 메세지로 객체를 결정한 후, 항상 인터페이스 중심으로 설계할 것. Do not data driven design. 행동=메세지=책임.
- 역할= a set of 책임. 책임을 특정 객체에 할당하기 보다는 역할에 할당한다고 생각하라. 역할을 통해 유연하고 재사용이 가능한 협력을 얻을 수 있다. 객체에게 중요한 것은 행동이며, 역할은 객체를 추상화해서 객체 자체가 아닌 협력에 초점을 맞출 수 있게 한다.
(IMO: 결국 전체 시스템을 크게 바라본 경험이 많은 사람일수록 좋은 개발자가 되겠구나, 좋은 oop 코드를 짜겠구나 싶음)
Chapter 4 설계 품질과 트레이드오프
객체지향 설계란
- 올바른 객체에게 올바른 책임을 할당하면서
- 낮은 결합도와 높은 응집도를 가진 구조를 창조하는 활동
결합도와 응집도를 합리적인 수준으로 유지할 수 있는 중요한 원칙은 객체의 행동에 초점을 맞추는 것. 구현=상태=데이터를 객체 분할의 중심축으로 삼으면 구현에 관한 세부사항이 인터페이스에 스며들고, 상태의 변경이 곧 인터페이스의 변경을 초래, 인터페이스에 의존하는 모든 객체에 변경의 영향이 퍼짐.
유지보수성이 목표다. 여기서 유지보수성이란 두려움 없이, 주저함 없이, 저항감 없이 코드를 변경할 수 있는 능력을 말한다.
캡슐화: 변경될 수 있는 어떤 것(구현)이라도 캡슐화해야만 한다.
응집도: 하나의 변경을 수용하기 위해 모듈 전체가 함께 변경된다면 높은 응집도를 가진 것
결합도: 결합도가 높을수록 함께 변경해야 하는 모듈의 수가 늘어남
# Rectangle 을 변경하는 주체를 외부의 객체에서 Rectangle 자체로 이동
class Rectangle{
public void enlarge(int multiple){
right *= multiple;
bottom *= multiple;
}
}
# 캡슐화! 스스로 자신의 데이터를 책임지는 객체
# 내부 속성을 외부로 감추는 것은 '데이터 캡슐화'
# 그러나 캡슐화란 변할 수 있는 어떤 것이라도 감추어야만 한다.
- 데이터 중심의 설계는 본질적으로 너무 이른 시기에 데이터에 관해 결정하도록 강요
- 또한, 협력이라는 문맥을 고려하지 않고 객체를 고립시킨 채 오퍼레이션을 결정
Chapter 5 책임 할당하기
- 데이터보다 행동을 먼저 결정하라
- 협력이라는 문맥 안에서 책임을 결정하라
이 객체가 수행해야 하는 책임은 무엇인가? 이 책임을 수행하는데 필요한 데이터는 무엇인가
책임은 객체의 입장이 아니라 객체가 참여하는 협력에 적합해야 한다. 협력에 적합한 책임이란 메시지 수신자가 아니라 메시지 전송자에게 적합한 책임을 의미한다. 메시지를 결정한 후에 객체를 선택해야 한다. 메시지가 존재하기 때문에 그 메시지를 처리할 객체가 필요한 것이다.
“메시지를 전송해야 하는데 누구에게 전송해야 하지?”라고 질문하는 것. 설계의 핵심 질문을 이렇게 바꾸는 것이 메시지 기반 설계로 향하는 첫걸음이다. 객체를 가지고 있기 때문에 메시지를 보내는 것이 아니다. 메시지를 전송하기 때문에 객체를 갖게 된 것이다.
메시지를 먼저 결하기 때문에 메시지 송신자는 메시지 수신자에 대한 어떠한 가정도 할 수 없다. 메시지 전송자의 관점에서 메시지 수신자가 깔끔하게 캡슐화되는 것이다.
- 메시지를 전송할 객체는 무엇을 원하는가?
- 메시지를 수시할 적합한 객체는 누구인가? (수신하는 메시지를 인터페이스로 만들자)
- 객체에서 필요한 내부 작업은 무엇인가? (스스로 처리할 수 없는 작업은 무엇이 있는가?)
GRASP(General Responsibility Assignment Software Pattern)2
- 설계를 시작하기 전에 도메인에 대한 개략적인 모습을 그려 보는 것이 유용함. 정확하거나 완벽할 필요 없음.
- Information Expert: 책임을 수행하는 데 필요한 정보를 가지고 있는 객체에게 할당하라. (정보를 가지고 있다고 해서 반드시 데이터=상태=구현을 지니고 있다는 것은 아니다)
- Low Coupling/High Cohesion
- Creator: 최대한 많이 만족하는 B에게 A를 생성하도록 해야함
- B가 A 객체를 포함하거나 참조한다.
- B가 A 객체를 기록한다.
- B가 A 객체를 긴밀하게 사용한다.
-
B가 A 객체를 초기화하는 데 필요한 데이터를 가지고 있다.
(B는 A에 대한 정보 전문가이다)
- Polymorphism
- Protected Variations
클래스 응집도 판단하기 응집도가 낮은 클래스의 징후:
- 클래스가 하나 이상의 이유로 변경돼야 한다면
- 클래스의 인스턴스를 초기화하는 시점이 경우데 따라 서로 다른 속성을 초기화하고 있다면
- 메서드 그룹이 속성 그룹을 사용하는지 여부로 나뉜다면
동일한 책임을 수행한다는 것은 동일한 역할을 수행한다는 것을 의미한다. 역할의 개념을 적용하면 구체적인 클래스는 알지 못한 채 오직 역할에 대해서만 결합되도록 의존성을 제한할 수 있다. 역할을 대체할 클래스들 사이에서 구현을 공유해야 할 필요가 있다면 추상 클래스를 사용하면 된다. 구현을 공유할 필요 없이 역할을 대체하는 객체들의 책임만 정의하고 싶다면 인터페이스를 사용하면 된다. 클래스는 작고 오직 한 가지 일만 수행한다. 책임은 적절하게 분배돼 있다.
설계를 주도하는 것은 변경. 변경에 대비할 수 있는 방법
- 코드를 이해하고 수정하기 쉽도록 최대한 단순하게 설계
- 코드를 수정하지 않고도 변경을 수용할 수 있도록 코드를 더 유연하게 만드는 것
여전히 책임을 할당하는데 어려움을 느끼고 있다면, 일종의 리팩터링Refactoring을 수행할 것
일단 절차형 코드로 실행되는 프로그램을 빠르게 작성한 후 완성된 코드를 객체지향적인 코드로 변경하는 것이다.
Reference
-
https://en.wikipedia.org/wiki/GRASP_(object-oriented_design) ↩
Leave a comment