한 줄 요약
객체지향 코드 품질은 어떻게 향상시킬 수 있을 것인가?
정리
•
코드를 작성함에 있어서 두 가지 요소, 가독성과 확장성을 고려하라. (문제 없이 동작하는 것은 기본이다)
•
객체를 설계할 때는 역할, 책임, 협력을 고려하라.
•
class 가 아니라 객체에 초점을 맞춰라! 해당 객체가 어떤 행동을 할 것이고 책임을 가질 것인지를 고민하는 것이 중요.
•
객체를 내부 데이터의 저장소로 인식하면 책임과 협력이라는 요소를 등한시하게 될 것이고 결과적으로 낮은 응집도, 높은 결합도를 가진 코드, 캡슐화가 제대로 이루어지지 않은 코드가 만들어질 것이다.
•
중요한 것은 책임!
•
책임(행동)에 신경을 쓰면 구현은 숨겨지고, 인터페이스(추상화)만 외부로 드러날 것이다. 이는 곧 높은 응집력, 낮은 결합도를 가진 고품질의 코드로 이어질 것.
캡슐화
•
캡슐화는 설계의 제 1원리다. 캡슐화의 원칙을 위반하면 낮은 응집도, 높은 결합도라는 문제로 몸살을 앓게 됨.
•
캡슐화를 통해 객체가 자율적인 존재가 되도록 하라!
•
객체는 단순한 데이터 제공자가 아니다. 내부에 저장되는 데이터보다 객체가 협력에 참여하면서 수행할 책임을 정의하는 오퍼레이션이 더 중요하다!
•
캡슐화는 변할 수 있는 어떤 것이라도 감추는 것을 의미한다! (그렇기에 인터페이스(책임)가 남는 것이다!)
•
중요한 것은 객체가 내부에 어떤 상태를 가지고 어떻게 관리를 하는 것이 아니다. 중요한 것은 객체가 다른 객체와 협력하는 방법이다.
책임 할당하기
•
책임에 초점을 맞춰 설계할 때 가장 큰 어려움은 어떤 객체에게 어떤 책임을 할장하는지를 결정하는 것이다.
•
두 가지 원칙
◦
데이터보다 행동을 먼저 결정하라
◦
협력이라는 문맥 안에서 책임을 결정하라
협력에 적함한 책임을 수확하기 위해서는 객체를 결정한 후에 메시지를 선택하는 것이 아니라, 메시지를 결정한 후에 객체를 선택해야 한다.
GRASP 패턴(General Responsibility Assingment Software Pattern)
Information Expert 패턴
책임을 수행할 정보를 알고 있는 객체에게 책임을 할당하는 것.
여기서 정보란 데이터와 다르다는 사실에 주의! 객체가 정보를 알고 있다고 해서 그 정보를 저장하고 있을 필요는 없다.
위 패턴을 따르는 것만으로도 자율성이 높은 객체들로 구성된 협력 공동체를 구축할 가능성이 높아짐.
Low Coupling(낮은 결합도) 패턴
책임할당을 검토하거나 여러 설계 대안들이 있을 때 낮은 결합도를 유지할 수 있는 설계를 선택하라.
High Cohesion(높은 응집도) 패턴
책임할당을 검토하거나 여러 설계 대안들이 있을 때 높은 응집도를 유지할 수 있는 설계를 선택하라.
Creator 패턴
객체 A 를 생성해야 할 때 어떤 객체에게 객체 생성 책임을 할당해야 하는가? 아래 조건을 최대한 많이 만족하는 B 에게 객체 생성 책임을 할당
•
B 가 A 객체를 포함하거나 참조한다.
•
B 가 A 객체를 기록한다.
•
B 가 A 객체를 긴밀히 사용한다.
•
B 가 A 객체를 초기화하는 데 필요한 데이터를 가지고 있다 (B 가 A 에 대한 정보 전문가)
일단 구현 후 책임을 분배한다
•
일단 구현을 해본다
•
메서드 단위로 최대한 한가지 역할을 하도록 구현체를 쪼개본다
•
쪼개진 메서드들의 책임을 어떻게 할당할 것인지 결정한다.
객체를 자율적으로 만들자
다른 객체가 데이터를 사용하도록 하지 말고 행동을 하게 만들자.
메시지와 인터페이스
서버-클라이언트 모델
두 객체 사이에서는 메시지를 통해서 협력 관계를 구축한다. 협력 안에서 메시지를 전송하는 객체를 클라이언트, 수신하는 객체가 서버다. 협력은 클라이언트가 서버의 서비스를 요청하는 단방향 상호작용이다.
서버는 내부 구현인 메서드를 드러내는 것이 아니라 퍼블릭 인터페이스만을 노출시킨다. 외부의 어떤 객체가 이를 호출하는지는 관심이 없고 요청이 온 메시지를 올바르게 응답하는데 초점을 맞춘다. 때문에 결합도가 낮아진다.
퍼블릭 인터페이스 품질에 영향을 미치는 원칙, 기법
•
디미터 법칙
낯선 자에게 말하지 말라, 오직 인접한 이웃하고만 말하라. ⇒ 오직 하나의 dot 만 사용하라? (좀 극단적이긴 하지만)
객체의 협력 경로를 제한하라는 원칙 (내부에 이 객체 저 객체 모두 결합되어서 결합도가 높은 코드가 생성되지 않도록) ⇒ 캡슐화를 다른 관점에서 표현한 것이라 볼 수 있다.
// wrong
screening.getMovie().getDiscountConditions();
// right
screening.calculateFee(audienceCount);
JavaScript
복사
•
묻지 말고 시켜라
객체의 내부 구조를 묻지 말고 시켜라
•
의도를 드러내는 인터페이스
메서드의 이름을 어떻게 하는지가 아니라 무엇을 하는지를 드러내자. ⇒ 클라이언트가 내부를 이해해야할 필요성이 줄어든다.
•
명령 - 쿼리 분리
객체의 상태를 수정하는 오퍼레이션을 명령이라고 부르고, 객체와 관련된 정보를 반환하는 오퍼레이션을 쿼리라고 부른다.
명령 = 프로시저, 쿼리 = 함수.
명령이거나 쿼리 중 하나여야한다. 명령인 동시에 쿼리여서는 안된다.
만약 둘 다 같이 있으면 부수효과를 제어하기 힘들고, 문제가 생겼을 시 찾기도 힘들어진다. 그리고 쿼리의 결과 또한 예측하기 어려워진다. (쿼리만 두번 실행했는데 결과가 다르게 나온다?? 문제. 쿼리는 함수형 프로그래밍으로? 함수형 프로그래밍이라기 보다는 불변성을 지켜서 구현한다.)
원칙의 함정에 빠지지 말라!
모든 설계는 결국 트레이드 오프의 결과물.
객체분해
객체지향 ⇒ 데이터를 중심으로 데이터 추상화와 프로시저 추상화를 통합한 객체를 이용해 시스템을 분해하는 것이다.
상위 기능은 하나 이상의 더 간단하고 더 구체적인, 덜 추상적인 하위 기능의 집합으로 분해된다.
하향식 기능 분해의 문제점
•
시스템은 하나의 메인 함수로 구성돼 있지 않다.
•
기능 추가나 요구사항 변경으로 인해 메인 함수를 빈번하게 수정해야 한다.
•
비즈니스 로직이 사용자 인터페이스와 강하게 결합된다.
•
하향식 분해는 너무 이른 시기에 함수들의 실행 순서를 고정시키기 때문에 유연성과 재사용성이 저하된다.
•
데이터 형식이 변경될 경우 파급효과를 예측할 수 없다.
하향식 접근법은 비즈니스 로직과 사용자 인터페이스 로직이 밀접하게 결합되게 된다. 문제는 비즈니스 로직과 사용자 인터페이스가 변경되는 빈도가 다르다는 것!
하향식 기능 분해하는 과정은 하나의 함수를 더 작음 함수로 분해하고, 분해된 함수들의 실행 순서를 결정하는 작업으로 요약할 수 있다. ⇒ 설계 시점부터 시스템이 무엇을 해야 하는지가 아니라 어떻게 동작해야 하는지에 집중하도록 만든다.
하향식 설계와 관련된 모든 문제의 원인은 결국 결합도다. 함수는 상위 함수가 강요하는 문맥에 강하게 결합된다.
객체지향은 함수 간의 호출 순서가 아니라 객체 사이의 논리적인 관계를 중심으로 설계를 이끌어 나간다
언제 하향식 분해가 유용할까
작은 프로그램과 개별 알고리즘을 위해서는 유용한 패러다임. 이미 해결된 알고리즘을 문서화하고 서술하는 데는 훌륭한 기법이다. 그러나 실제 동작하는 커다란 소프트웨어를 설계하는 데 적합한 방법은 아니다.
⇒ 시스템의 변경을 관리하는 기본적인 전략은 함께 변경되는 부분을 하나의 구현 단위로 묶고 퍼블릭 인터페이스를 통해서만 접근하도록 만드는 것. 기능 기반으로 시스템 분해하는 것이 아니라 변경의 방향에 맞춰 시스템 분해.
추상 데이터
변경의 주된 압력이 오퍼레이션을 추가하는 것이라면 추상 데이터 타입이 좋을 수 있다. 그러나 새로운 타입을 빈번하게 추가해야 한다면 객체지향의 클래스 구조가 더 유용하다.
의존성 관리하기
협력은 필수적이지만 과도한 협력은 설계를 곤경에 빠트릴 수 있다. 협력은 다른 객체에 대해서 알 것을 강요한다. ⇒ 의존성 생김
의존성 전이
뜻 ⇒ A 가 B 에 의존하면 A 는 B 에 의존하는 것들에 대해서도 의존성이 생김.
의존성은 함께 변경될 수 있는 가능성을 의미하기 때문에 모든 경우에 의존성이 전이되는 것은 아님. ⇒ 변경의 방향과 캡슐화의 정도에 따라서 결정됨.
의존성 해결
컴파일 타임 의존성 ⇒ 런타임 의존성을 해결을 해야함. 일반적으로 아래의 세 가지 방법을 사용
1.
객체를 생성하는 시점에 생성자를 통해 의존성 해결
2.
객체 생성 후 setter 메서드를 통해 의존성 해결
3.
메서드 실행 시 인자를 이용해 의존성 해결vv
1, 2 번을 혼용하는 경우도 있음.
개방 폐쇄 원칙(로버트 마틴)
소프트웨어 개체는 확장에 대해 열려있어야 하고, 수정에 대해서는 닫혀있어야 한다. ⇒ 추상화에 의존한다.
생성 사용 분리
동일한 클래스 내에서 생성과 사용이라는 두 가지 이질적인 목적을 가진 코드가 공존하는 것은 문제
소프트웨어 시스템은 시작 단계와 실행 단계를 분리해야 한다 (By Martin)
Fatory
생성과 사용을 분리하기 위해 객체 생성에 특화된 객체