객체지향설계

코드스피츠 1강 오브젝트 2회차

iOS_Assin 2019. 10. 21. 14:12

책 오브젝트를 기반으로 하는 코드스피츠 강의 오브젝트 - 2회차 를 정리한 내용입니다.

 

 

1.코드스피츠 1강 오브젝트 1회차 (1)

2.코드스피츠 1강 오브젝트 1회차 (2)

3.코드스피츠 1강 오브젝트 2회차 

4.코드스피츠 1강 오브젝트 3회차 

5. 코드스피츠 1강 오브젝트 4회차 

6. 코드스피츠 1강 오브젝트 5회차

7. 코드스피츠 1강 오브젝트 6회차

 

이 포스트는 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 를 가지고 있기 때문에 예매를 하기 위한 필요한 정보를 가지고 있다고 판단한다.

이 것은 정보전문가 패턴의 단점이다. 

 

 

참조:

https://www.youtube.com/watch?v=ex6kP_b7Ypk&t=2s