코드스피츠 1강 오브젝트 2회차
책 오브젝트를 기반으로 하는 코드스피츠 강의 오브젝트 - 2회차 를 정리한 내용입니다.
이 포스트는 Java 예제를 Swift 예제로 바꿔서 다루고 있습니다.
Type
데이터 타입은 메모리에서 얼만큼 차지하는 길이이다.
(Bool, Int, Long ...)
하지만 객체지향의 타입 다르다!
객체지향의 Types
Role: Type을 통해 역할을 묘사함
Responsibility: 알고리즘을 Type으로 표현
Message: 형을 통해 메시지를 공유함.
(메시지 함수의 인자는 형으로 표현되어야 함)
Protocol: 객체 간 계약을 형을 통해 공유함.
(이펙티브 자바 Marker interface 는 계약을 형으로 표현하도록 설명되어 있음)
✅Marker interface 는 아무런 메서드도 선언하지 않은 인터페이스를 말한다.
대표적으로 Java의 Serializable 이 있다.
Serializable 할 수 있다는 것을 형으로 표현하고 있다.
public interface Serializable {
}
Supported types
static: 단 한개의 인스턴스가 존재 (동시성 문제가 있음)
enum: 제한된 수의 인스턴스가 존재 (제네릭 불가, 동시성 안정성이 확보)
class: 무제한의 인스턴스가 존재
유틸리티 함수와 메소드를 구분하는 방법
utility 함수는 클래스에 있는 메소드 내용에 this 가 존재하지 않으면 utility 이다.
해당 클래스 메소드에서 제거하고 utility로 나와야 한다.
Condition
1. 조건 분기는 결코 제거할 수 없다.
사람의 머리로는 2단계 조건 분기부터는 판별하기 어렵다.
따라서 1단계 조건 분기만 가져야 한다.
모든 조건의 진리표를 작성하면 여러 if 문을 커버할 수 있다.
if 문의 TestCase를 150개를 만들어도 151번째에서는 깨지게된다.
2. 조건 분기에 대한 전략은 두가지 뿐이다.
1. 내부에서 응집성있게 모아두는 방식
장점: 모든 경우의 수를 한 곳에서 파악할 수 있다.
단점: 분기가 늘어날 때마다 코드가 변경된다.
케이스가 새로 추가되면 모든 경우에 대해서 회귀 테스트가 일어난다.
데이터 주도 설계 방식이다.
2. 외부에 분기를 위임하고 경우의 수 만큼 처리기를 만드는 방식
장점: 분기가 늘어날 때마다 처리기만 추가하면 된다.
단점: 모든 경우의 수를 파악할 수 없다.
책임 주도 설계 방식이다.
두번째를 사용하면 processCondition 함수 내부는 변경이 일어나지 않는다. (OCP 원칙)
그러므로 우리는 두번째 방식을 사용하여야 한다.
value = responsibility
시스템의 존재 가치는 사용자에게 제공되는 기능
- ex) 정보가 있으면 무언가를 할 수 있다는 것이 그 시스템의 가치
사용자가 사용할 기능 = 시스템의 책임
사용자가 사용하고 싶은 기능이 그 시스템이 책임지고 수행해야 될 일
- ex) 영화 예매 사이트에 접속했는데 예매가 안되면 가치는 전혀 없다. 사이트의 가치를 보존하려면 예매가 잘 되야 한다.
시스템 차원의 책임을 더 작은 단위의 책임으로 분할
- 우리는 머리가 나쁘니까 방대한 책임을 이해할 수 없다. 방대한 책임을 다룰수 있는 작은 책임으로 계속 분할한다.
- 역할 모델의 핵심은 큰 책임을 응집성이 높으면서도 결합도가 낮은 부분 으로 쪼갤 수 있는지가 실력이고 이걸 달성하는데 굉장히 오래 걸린다.
해당 책임의 추상화하여 역할을 정의함
- 역할 이란, 책임은 기능이지만 책임간의 공통점을 모아서 보다 높은 단계로 이해하는 과정을 의미
- 책임과 역할을 따로 생각하는 것이 어렵다.
- 다양한 책임으로부터 공통된 점을 뽑아서 역할로 정의하는 것이 어렵다.
- 책임으로부터 역할을 인식하고, 반대로 역할로부터 책임을 양산할 수 있다.
- 처리기를 역할로 묶을 수 있기 때문에 유리해질 수 있다.
역할에 따라 협력이 정의됨
- 협력 단계를 섣불리 책임 단계에서 정리하면 안된다.(책임 단계에서 협력을 구상하면 책임이 추가될 때, 협력 관계가 깨지게 됨)
코딩!
전체 코드는 여기에서 확인할 수 있습니다.
주요 설명 정리
DiscountCondition
DiscountCondition 는 능동적 개체가 아니고 수동적 개체이다.
Iterator 패턴은 능동적 개체가 아니고 수동적 개체이다.
수동적 개체로 사용되는 이유
* Lazy 하게 사용하기 위함 (Client가 원할 때 hasNext 와 Next 를 호출하기 위함)
protocol DiscountCondition {
// 두 개의 책임 (발동 트리거, 외부에서 조건을 확인)을 가지고 있다.
// isSatisfiedBy 외부에서 발동 트리거 조건을 확인
func isSatisfiedBy(screening: Screening, audienceCount: Int) -> Bool
// calculateFee 발동 트리거이다.
func calculateFee(fee: Money) -> Money
}
가장 좋은 인자
인자를 하나 받는 함수가 가장 좋은 함수이다.
모든 인자를 추상화해서 객체화 시키면 하나의 인자로 만들 수 있다.
어떤 메소드에게 전달되는 메시지 인자가 2개 이상이라면
* 메시지가 충분히 추상화되지 않음
* 메시지가 충분히 형(Type) 이 되지 않음
가장 좋은 Interface
메소드가 아무것도 없는 Interface(Marker Interface) 가 가장 좋은 Interface 이다.
메소드 하나를 갖는 Interface 가 그 다음으로 좋은 Interface 이다.
DiscountPolicy
DiscountPolicy 는 Marker interface 이다.
protocol DiscountPolicy {
}
protocol AMOUNT: DiscountPolicy {
}
protocol PERCENT: DiscountPolicy {
}
protocol COUNT: DiscountPolicy {
}
protocol NONE: DiscountPolicy {
}
SequenceAmountDiscount, SequencePercentDiscount
상속의 한계는 병합되어 있는 경우의 수의 모든 코드 중복을 제거할 방식이 없다.
코드 중복을 제거하기 위해서는 전략 개체로 사용하는 방법밖에 없다.
class SequenceAmountDiscount: AmountDiscount {
override func isSatisfiedBy(screening: Screening, audienceCount: Int) -> Bool {
return screening.sequence == sequence
}
}
class SequencePercentDiscount: AmountDiscount {
override func isSatisfiedBy(screening: Screening, audienceCount: Int) -> Bool {
return screening.sequence == sequence
}
}
Movie
if 를 제거하는 유일한 방법중 하나는 외부에 if를 위임하고 외부에서 생성된 객체를 주입받는 것이다.
이름에서 if 조건을 기술하는 것을 알수있다.
SequencePercentDiscount
SequenceAmountDiscount
2중 if를 외부에 위임하면서 이름이 기술될 수 있다.
A: Sequence B: Percent
우리는 for <-> 재귀 변환하는 것처럼
Generic <-> if 변환하는 것에 익숙해야한다.
class Movie: MemoryHashable {
private let title: String
private let runningTime: TimeInterval
private let fee: Money
private let discountConditions: [DiscountPolicy & DiscountCondition]
public init(title: String, runningTime: TimeInterval, fee: Money, discountConditions: [DiscountPolicy & DiscountCondition]) {
self.title = title
self.runningTime = runningTime
self.fee = fee
self.discountConditions = discountConditions
}
용어 정리
정보전문가(Information Expert)패턴
책임을 수행하는 데 필요한 정보를 가지고 있는 객체에게 할당해야만 캡슐화를 유지시킬 수 있다.
이 책에서는 Screening 이 예매를 담당하고 있다.
Screening이 예매를 하는 것은 현실세계의 모델링과는 다른 양상으로 나타난다.
하지만 캡슐화를 유지하기 위해서는 Screening이 Movie 를 가지고 있기 때문에 예매를 하기 위한 필요한 정보를 가지고 있다고 판단한다.
이 것은 정보전문가 패턴의 단점이다.
참조: