객체지향설계

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

iOS_Assin 2019. 10. 25. 01:45

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

 

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

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

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

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

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

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

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

 

 

들어가기전 책의 6장에서 배운 내용을 정리해보자.

 

디미터의 법칙

협력이라는 컨텍스트 안에서 객체보다 메시지를 먼저 결정하면 두 객체 사이의 구조적인 결합도를 낮출 수 있다. 

a().b().c()

 

헐리우드 원칙(묻지 말고 시켜라)

클라이언트 관점에서 메시지를 선택하기 때문에 필요한 정보를 물을 필요 없이 원하는 것을 표현한 메시지를 전송하면 된다. 

if a.상태 {
  a.액션()
}

 

의도를 드러내는 인터페이스:

메시지를 전송하는 클라이언트의 관점에서 메시지의 이름을 정한다는 것이다. 

TickerSeller.sellTo(audient:)

Audient.buy(ticket:)

Bag.hold(ticket:)

 

명령-쿼리 분리 원칙

객체에게 시키는 것 (묻지 말고 시켜라)이 항상 가능한 것은 아니다.

설계는 트레이드오프의 산물이므로 원칙을 맹신하지 마라. 

 

객체가 단순히 어떤 일을 해야 하는지 뿐만 아니라 협력속에서 객체의 상태를 예측하고 이해하기 쉽게 만들기 위한 방법이다. 

func isSatisfied(schedule:) // 쿼리
func reschedule(schedule:) // 명령

 

 

분해 (Decomposition)

Assemble vs Composition 

 

Assemble 은 레고와 같이 하나의 블럭이 가치가 있는 것이 아니라 조립한 결과가 가치가 있다. 

Compsition 은 자동차와 같이 하나하나의 부속들이 가치가 있고 그것들이 모인것을 말한다. 

 

소프트웨어 격리, 모듈화가 잘되어있다면 Composition 이라고 부르고 

서로 얽혀있다면 Assemble 되어있다고 말할 수 있다.

 

Functional decomposition (기능적 분해)

Flow chart 기법

: 하향식 접근법으로 최상위 (Top) 부터 시작해서 좀 더 작은 단위의 하위 기능으로 분해한다. 

코드스피츠: 실행해야하는 일련의 작업을 시작 - 끝이 있는 하나의 Flow 로 바라본다. 

 

Flow 에 따라서 프로그램을 쪼갤 수 있다고 생각하기 때문에 Functional decomposition 이 일어난다.

그러나 인간은 학습하기 때문에 변화가 일어나고 변화에 대응할 때 Flow 는 취약해진다. 

 

Flow 사각형 / 상태

 

Flow 는 원의 상태를 처리하기 위한 Flow 이다. 

 

하지만 시간이 지남에 따라 하늘색 상태가 필요하다는 것을 알게된다.

 

처음부터 존재해야 하는 상태라는 걸 깨닫고 하늘색 상태를 첫 Flow 에 추가하게 된다.

 

하늘색 상태를 추가하면서 첫 Flow 는 오염되기 때문에 처음부터 다시 만들어야 한다.

 

 

2번째 Flow 를 처리하고 나니까 3번째부터는 분기가 필요하다는 사실을 알게된다. 

 

개별 처리를 위한 분기점에만 사용되고 다른 곳에서는 영향을 미치지 않기 때문에 변수의 Scope 를 정할 수 있게 된다.

초록색 박스는 초록색 변수의 Scope를 나타낸다.

변수의 Scope 범위를 짧게 가져가고 있기 때문에 1,2 Flow 영향을 미치지 않으므로 1,2 Flow를 수정할 필요가 없다.

 

하지만 재수없게도 다시 시간이 지남에 따라 빨간색 원이 필요하다는 것을 깨닫게 된다. 😭

 

다시 처음부터 빨간색을 추가하면 결과적으로 모든 곳에서 불이난다...

 

빨간색 원이 생김으로써

매일 밤을 세운다.

회귀 테스트를 일으킨다

 

Abstract Data Type (추상 데이터 타입)

기능적 분해에서는 무슨 행위를 할지를 보고 해당되는 데이터를 떠올리기 때문에 마지막에 빨간색 원같은 것이 나타나는 것이다.

 

추상 데이터 타입은 데이터에 따라서 할 일들이 만들어 진다는 것을 사실을 알 수 있다고 한다.

 

 

if 를 넣어서 공통적인 Flow 를 발견할 수 있다.

모든 상태가 한곳에 모이기 때문에 코드의 응집성이 굉장히 높아진다.

 

def calulatePay(taxRate)
  if (hourly) then 
    // 시간 근무 급여 계산
    return calculateHourlyPay(taxRate)
  else
    // 정규 근무 급여 계산
    return calculateSalriedPay(taxRate)

 

 

외부에서는 하나의 형으로 인식하고 내부 로직은 복잡한 로직이 감춰진다.

 

ADT(Abstract Data Type) 장점

내부에서 등장하는 모든 상태를 수용하고 있으므로 새로운 메소드(Flow) 를 추가하면 외부에 노출되지 않고 내부에서 다 해결할 수 있다.

 

ADT(Abstract Data Type) 단점

새로운 유형의 데이터가 추가되면 모든 메서드(Flow) 의 코드가 변경되어야 한다.

 

책에서도 기능이 주로 추가되고 데이터의 변경이 없다면 ADT 는 나쁘지 않다고 설명하고 있다.

 

하지만 초록색 상태에서만 해당되는 메소드 (Flow)가 추가되면 

초록색이 아닌 경우는 임의에 예외값을 발생시킨다.

 

ADT 는 사용해야 하는 상황

1. 모든 메소드가 모두 가용할 수 있는 상태를 만족해야 한다.

2. 상태가 확정되어 있어야 한다.

 

Object Oriented

객체 지향의 사고방식에서는 상태가 없는 수준에서 추상화를 하고 상태에 따라 상속 구조를 변경하면서 형을 더 만든다. 

ADT 는 형을 줄여가는 과정이지만 객체지향은 형이 늘어난다.

 

ADT 에서는 초록색이 추가되면서 모든 메소드들이 불타올랐지만

객체지향에서는 아무런 영향을 주지않고 정확한 필요한 초록색 코드만 생성된다.

 

ADT vs 객체지향

ADT는함수 내부에서 if 를 사용하지만

객체지향은 사용하는 측(Client) 로 if 를 밀어낸다.

 

객체지향의 단점

 

메소드를 추가하면 구현하고 있는 모든 곳에서 변경이 일어나게 된다.

 

그러므로 객체지향 설계에서 주의할 점은 "성급한 추상화" 이다.

 

객체지향에서는 추상화가 제대로 되어 있지 않은 동안 함부로 상태를 늘려가면 안된다.

 

성급한 추상화란

메소드를 추가하고 삭제하는 일은 초반에 자주 일어나고 깨달음에 이르기 전에는 구상 클래스를 함부로 늘리면 안된다는 뜻이다.

 

리스코프 치환 원칙에 의해서 이전 장 4회차 D 의 문제와 유사하다.

 

초록색에서만 다루는 메소드가 하나 추가되면 Generic 을 이용하는 방법으로 해소할 수 있다.

Generic 뿐만 아니라 6가지 이상의 방법이 존재한다. (어떤 방법이 있지,..?)

 

개발자의 세계 ADT

이번 장에서는 4회차 Programmer 예제를 사용한다.

 

 

class Paper {
    private let isClient: Bool

    public init(isClient: Bool) {
        self.isClient = isClient
    }
    
    let library = Library(name: "vueJS")
    let language = Language(name: "kotlinJS")
    var programmer: HasProgrammer?
    let server = Server(name: "test")
    let backEndLanguage = Language(name: "java")
    let frontEndLanguage = Language(name: "kotlinJS")
    
    private var backEndProgrammer: HasProgrammer?
    private var frontEndProgrammer: HasProgrammer?
    
    func setBackEndProgrammer(_ p: HasProgrammer) {
        if isClient {
            self.backEndProgrammer = p
        }
    }
    func setFrontEndProgrammer(_ p: HasProgrammer) {
        if isClient {
            self.frontEndProgrammer = p
        }
    }
    func setProgrammer(_ p: HasProgrammer) {
        if isClient {
            self.programmer = p
        }
    }
}

 

flag 변수(isClient) 에 대한 여파로 코드는 내부의 모든 상태를 커버할 수 있도록 변경해야 한다.