해리의 데브로그

Clean code - Architecture Use Case

|

백명석님의 클린코드 강의 를 듣고 정리한 포스팅 입니다.

1. Architecture

  • Web 기반 Accounting 시스템이 있다고 가정할 때, 아키텍처에서 주목해야할 부분은 Web 시스템이 아니라 Accounting 시스템이어야 함.
  • SW 아키텍처는 Accounting Issue를 잘 드러내야 하며, Web에 대한 Issue는 거의 언급되지 않아야 함.
  • 하지만 대개의 웹 시스템은 반대임. Web Issue에 대해 고함치며, 비즈니스 의도에 대해서는 거의 언급하지 않음.

1-1. Web System에 만연하는 MVC Architecture

  • View/Controller: 강하게 HTML과 연관
  • Model: 강하게 Controller에 연관
  • View/Controller: 강하게 모델에 coupling. 모델의 구조에 많은 영향을 미침.

1-2. Accounting System Architecture

  • 아키텍처 변경 없이 delivery 매커니즘을 변경할 수 있어야 함.
  • 동일한 시스템을 웹과 콘솔에 delivery 할 수 있어야 함.
  • 이 두 시스템의 아키텍처는 동일해야함.

2. Use Cases

“Object-Oriented Software Engineering - A Use Case Driven approach”, Ivar Jacobson

  • Delivery 문제를 우아한 아키텍처로 해결
  • Delivery와 무관한 방식으로 사용자가 시스템과 상호작용하는 방식을 이해하는 것
  • 링크, 버튼, 클릭 등의 용어를 사용하지 않고 표현
  • Delivery 매커니즘을 나타내지 않는 용어로 사용
  • 제이콥슨은 이런 상호작용을 use case라고 했음.
  • 어플리케이션 개발은 Delivery와 독립적인 Use Case에 의해 주도되어야 한다. 이를 통해 다양한 Delivery 매커니즘을 수행할 수 있음.
    • 다양한 Delivery 매커니즘을 쓰지 않는 경우(웹만 쓰는 경우)라도, 딜리버리 매커니즘과 잘 절연이 되어야, 웹에서 변경이 일어나더라도 어플리케이션이 보호가 됨.
  • use case가 시스템에서 가장 중요한 것

3. Use Case Driven Architecture

  • use case driven 시스템의 아키텍처를 보면 delivery 매커니즘이 아닌 use case를 보게 됨
    • 독자가 보게 되는 것은 시스템의 의도이다.
  • TimeCardTransaction : 유즈 케이스를 추상화한 클래스
  • TimeCardTransaction 라는 행위가 있음.
    • 직원이 출근하여 TimeCard를 찍었을 때 일어나는 행위.
    • 그 어플리케이션 로직을 구현한 것이 TimeCardTransaction
    • TimeCard & Employee 와 같은 Entity(도메인 모델)과도 상호작용을 함

4. Use Cases

  • 사용자가 특정 목적을 이루기 위해 시스템과 어떻게 상호작용하는지에 대한 형식적 기술
  • 목적: 주문 처리 시스템에서 새로운 주문을 생성하는것
Create Order
Data:
	<customer-id>, <customer-contact-info>, <shipment-destination>, 
	<shipment-mechanism>, <payment-information>
Primary Course:
1. 수주 담당자가 위의 데이터를 가지고 "create order" 커맨드를 실행시킨다.
2. 시스템은 모든 데이터를 검증한다.
3. 시스템은 주문을 생성하고 주문 아이디를 결정한다.
4. 시스템은 주문 아이디를 수주 담당자에게 전달한다.
  • screen, button, field등과 같은 웹(Delivery 매커니즘)과 같은 것들은 언급하지 않음
  • 시스템으로 들어가는 데이터, 커맨드와 시스템이 응답하는 것만 언급
  • Delivery 매커니즘과 무관한 아키텍처를 가지려면 Delivery 매커니즘과 무관한 use case로 시작해야 한다
  • Use case의 응답도 주목
    • Create order에서 Response가 order_id라고 가정했을 때,
    • order_id는 완전하게 Delivery 매커니즘과 무관, 수주 담당자는 order_id를 볼 필요가 없음
    • 반면 delivery 매커니즘은 팝업을 띄워서 사용자에게 항목을 주문에 추가하라고 요청할 수 있음
  • Use Case는 입력 데이터를 해석하여 출력 데이터를 생성하는 필수 알고리즘
  • Use Case를 구현하는 객체를 생성할 수 있다는 것을 의미함.
  • 이 예제는 primary course만 있는데 예외상황도 있을 수 있음.

4-1. Use Case Alogrithm

  • Use Case Alogrithm은 다른 비즈니스 객체들(Customer, Order)을 언급(내포)함
  • 알고리즘: use case 정의. 비즈니스 규칙을 내포
  • 하지만 이런 비즈니스 규칙은 customer나 order 객체에 속하지 않는다.
    • create order 하기 위해서는 customer_id가 있어야 함.
    • order을 하기 위한 상품의 코드, 갯수를 알아야 함.
    • 그런데 고객은 이러한 것을 다 할 수 있는가? customer은 상품에 대해 모름. order은 상품에 대해서는 알지만 customer에 대해서는 모름. 알수 없음. 한 객체에게 줄 수 없는 책임.
  • 여러 객체들을 coordination 하는 규칙을 어디에 위치할 것인가(어디에 비즈니스 규칙을 내포시킬 것인가)?
    • Use Case 객체에 비즈니스 규칙을 위치시켜야한다.
    • 이렇게 partitioning을 하게 되면 아키텍쳐에서 use case가 가장 핵심적인 역할을 하게 될 것이다.

5. Partitioning - 어떻게 나눌 것인가?

Ivar Jacobson: 아키텍처는 3개의 fundamental kinds of objects를 갖는다

  • Business Objects - Entities
    • Entity, VO
  • UI Objects - Boundaries
    • DTO, VO
  • Use Case Objects - Interactors
    • Controller라고 야콥슨이 부름. 그러나 MVC와 혼동을 피하기 위해 Interactors라고 부름

5-1. Entities

  • 주문이라는 도메인을 만든다고 하면, 주문이라는 도메인에는 어디에나 “Order”, “Product”라는 클래스가 있을 것임. 그런데, A라는 회사의 주문시스템과 B라는 회사의 주문시스템은 다를 것임. (로직의 처리가 다름)
    • 같은 도메인인데 어떠한 시스템이냐에 따라 달라지는 부분을 어플리케이션 로직이라고 할 수 있음.
    • 도메인이 같으면 무조건 같은 로직을 비즈니스 로직, 도메인 로직이라고 할 수 있음.
  • 엔티티는 어플리케이션 로직을 갖지 않으며, 독립적인 비즈니스 로직을 갖는다.
  • 다른 어플리케이션에서도 그 엔티티를 사용할 수 있음.
  • 어플리케이션의 특화된 메소드는 엔티티가 가지고 있으면 안됨. 이런 메소드는 Use Case Object(Interactor)로 옮겨야 한다.
  • Book, Library, Employee

5-2. Interactor (Service, UseCase)

  • 어플리케이션에 종속적인 업무 규칙 (비즈니스 로직)을 갖고 있음
  • 특정 어플리케이션에 특화된 메소드들은 Interactor 객체에 구현
  • Interactor는 application에 특화된 로직을 통해 목적을 달성
    • Application과 무관한 entity 로직을 호출

    • 예) CreateOrderInteractor: OrderEntity에 create 하고, GetId를 호출. 이러한 로직은 이 어플리케이션에서만 수행하며 다른데서는 안할 수 도 있음(이 두 메소드는 application 로직과 무관)

    • Use Case의 목적을 달성하기 위해 이러한 메소드들을 어떻게 호출하는지 아는 것이 Interactor의 책임이다.

5-3. Boundary Object (DTO)

Use Case의 책임 중 하나는 사용자로부터 입력을 받고, 결과를 다시 사용자에게 반환하는 것. 이를 위해 또다른 객체가 존재하며 이것이 바로 Boundary Object

  • Use Case를 딜리버리 매커니즘으로부터 분리할 때 사용
  • Use Case를 Delivery 매커니즘 간의 통신 수단 제공
  • 여러가지 클라이언트(MVC / Console / Thick Client Boundary) 등은 Boundary의 반대편에 존재
  • Use Case는 이러한 Delivery 매커니즘에 대해 모른 채 Boundary의 또다른 반대편에 존재

5-4. Flow

  • Delivery 매커니즘
    • 사용자 요청 수입
  • 요청을 표준적인 형식(Request Model)으로 표현. RequestModel은 Object가 아니며 Data Structure 임. (Getter, setter만 있으면 됨)
    • Boundary를 통해 RequestModel을 Interactor에 전달
  • Interactors(Use case)
    • Applications에 특화된 비즈니스 로직을 수행
    • Entity를 조작하여 Application에 독립적인 비즈니스 로직을 수행(Validate, calculate, update)
  • Interactor(Use Case)
    • 결과를 수집
    • 표준적인 형식(Response Model)로 생성
    • Boundary를 통해 다시 Delivery 매커니즘으로 전달

“정해진 Flow는 없으며 단지 관계만 있을 뿐임.”

  1. Request & Response Model은 순수한 데이터 구조이다.
  2. Boundary(Interface, DTO)를 통해서 Request Model이 Interactor(Use Case)로 전달됨.
    • 1 Interactor per a Use Case ( 1 Interactor == CreateOrderUseCase)
  3. Interactor은 정해진 Use Case에 따라 기능을 수행함
  4. Interactor은 관련된 Entity를 사용해 무언가를 수행하고 결과를 수집
  5. 수행 결과를 Response Model로 변환해 Boudary를 통해 외부로 전달함

5-5. Use Cases and Partitioning

  • 우리는 시스템의 행위를 use case로 기술한다.
    • use case에서 application에 특화된 행위를 interactor 객체로 캡쳐한다.
    • application에 무관한 행위를 entity 객체로 캡쳐하고 interactor로 제어한다.
    • UI 종속적인 행위는 boundary 객체로 캡쳐하여 interactor와 커뮤니케이션한다.

6. Isolataion

  • 소스 코드 의존성은 하나의 방향만 유지해야함(Delivery 매커니즘을 Decouple하기 위해)

7. Database

  • 비즈니스 객체가 아니라 Data Structure를 포함
  • 데이터가 저장되는 방식은 Interactor나 Entity가 원하는 방식이 아님
  • 그러므로 DB와 Entity간의 Boundary Layer를 제공해야 함 (Dependency Inversion Principle)

8. Conclusion

  • 아키텍쳐는 툴이나 프레임워크에 기반하지 않는다
  • 좋은 아키텍쳐
    • 툴이나 프레임워크에 대한 결정을 아주 오랫동안 미룰 수 있음
    • 이행되지 않은 결정의 갯수를 최대화
    • 딜리버리 매커니즘에 의존하지 않고 노출시키지 않고 숨김
  • 시스템의 모양을 보면 web 시스템인지 여부를 알 수 없어야 함
  • 시스템의 use case는 주요한 추상화(primary abstraction)이고 시스템 아키텍처를 구성하는 핵심적인 원칙
  • 아키텍처를 보면 UI가 아니라 시스템의 의도를 볼수 있어야함.
  • Interactor는 use case를 캡슐화(구현)하고 entity는 비즈니스 객체를 캡슐화하며 boundary는 UI와 격리를 제공
  • 격리를 얻기 위해 application을 delivery 측과 분리하는 boundary 인터페이스 생성

9. Web Server의 Reuqest 처리 과정

JustWrite 님의 블로그 포스팅의 이미지를 참조하였습니다.

기본 구조

사용자가 Delivery Mechanism을 통해 요청을 전달

컨트롤러가 Request Model을 Interactor(Use Case)에 전달

Interactor가 Boundary 인터페이스를 구현함. Delivery Mechanism에서는 Boundary 인터페이스를 통해 Interactor를 호출함.

Interactor가 수행 결과를 Delivery Mechanism에 반환

Delivery Mechanism의 어떤 객체가 Boundary 인터페이스를 구현함. Interactor는 이 Boundary 인터페이스를 통해 결과를 전달함.

결과적으로 Delivery Mechanism에 해당하는 Web Server는 어플리케이션 핵심인 Interactor를 알지 못함. 이로 인해 얻을 수 있는 점은 Delivery Mechanism이 Easy to Plug In/Out 될 수 있다는 것임.

Clean code - Domain Model 방식으로 개발하는 순서

|

백명석님의 클린코드 강의 를 듣고 정리한 포스팅 입니다.

1. 데이터(정적) 모델링

클래스, 속성, 관계 식별

  • 도메인 영역의 주요 개념(명사)를 식별. 요구사항에 있는 명사를 식별한다.
  • “Applying UML and Patterns” 참조
  • 초기 도메인 모델의 예시 - 클래스, 멤버변수, 관계만 명시되어 있음
    • 정적 모델링 - 모든 사람들이 공유 할 수있게 청사진을 그림

2. 행위 모델링

도메인 모델에 행위 추가하기

  • 도메인 모델에 행위를 추가함으로써 도메인 모델에 생명력을 부여
  • 도메인 모델의 행위를 결정하기 위해 도메인 모델의 책임(Responsibility)와 상호작용(Collaboration)을 식별
    • 클래스의 책임 - 클래스가 아는것(속성, 관계), 하는 것, 결정 하는 것 등
    • 클래스의 상호작용 - 책임 수행을 위해 호출하는 다른 클래스들
  • 책임과 상호작용을 식별하는 절차
    • 요구사항(유스케이스, 유저스토리, UI 디자인) 분석을 통해 어플리케이션이 처리해야 하는 요구사항 식별
    • 도메인 모델의 클라이언트(Presentation layer 등)에게 도메인 모델을 노출하기 위한 도메인 모델의 인터페이스(타입, 메소드) 결정 - 서비스의 인터페이스
    • 해당 인터페이스를 각각의 요구사항을 고려하여 TDD 접근법으로 구현

2-1. 요구사항 식별하기

  • 처리해야할 요구사항을 식별/어떻게 응답할 지 결정
  • UI 디자인, 유스 케이스, 유저 스토리 등을 분석한다
  • 요구사항은 2가지 부분으로 구성
    • 사용자 행위, 요청 ex) 고객이 배송 정보를 입력한다.
    • 사용자 행위, 요청에 대한 어플리케이션의 응답 - 유스케이스의 책임 ex) 배송 시간을 검증하고 배송 정보를 갱신
  • 어플리케이션의 책임은 2가지로 그룹핑
    • 사용자 입력 검증(validate), 값 계산(calculate), 데이터베이스 갱신(update)
    • 값 출력 (화면에 뿌릴 값을 제공하는 기능)
    • Command Query Seperation

2-2. 메소드 식별하기

각 요청에 대해 2가지 메소드 들이 존재

  • 서비스 메소드
    • 사용자 요청 검증
    • 계산 수행
    • 데이터 베이스 갱신
  • 리파지토리 메소드

    • 출력을 위한 데이터 반환
  • 도메인 모델의 클라이언트는 도메인 티어를 2번 호출
    • 서비스 메소드 + 리파지토리 메소드

2-3. TDD로 메소드 구현하기

  • 대상 서비스 메소드에 대해 하나 이상의 테스트 케이스(다양한 argument에 대해 서비스가 잘도는지)를 작성하는 것으로 시작. 각 테스트 케이스는 서로 다른 상황을 재현하기 위해 다른 인자로 구성된다.
  • Mock 객체를 이용해서 ➔ 서비스 메소드 ➔ 리파지토리 메소드 순으로 top-down 방식으로 구현
  • 구현을 하다가 발견되는 collaborator를 구현하기 위해 머릿속에서 context-switching이 일어날 필요가 없어서 집중하면서 구현

Clean code - Architecture

|

백명석님의 클린코드 강의 를 듣고 정리한 포스팅 입니다.

1. What is Architecture

전체적인 시스템 개발에 기반을 제공하는 변경 불가능한 초기 결정 사항의 집합(Set of irrevocable early decisions that raised the foundation for the entire system development)

  • Java, Eclipse, Spring, MySQL, MVC 등은 아키텍처가 아니다.
    • 건물을 지을 때 해머, 못, 기와장 벽돌 등이 아키텍처가 아니듯이, 이 또한 도구이며 뭔가를 만들기 위한 재료에 불과하다.
  • 아키텍처는 사용법(Usage)여야 한다. (이것이 무엇인지 사용법을 드러내는 것)

2. What is Use Case

A list of steps, typically defining interactions between a role(actor) and a system, to achieve a goal

  • 시스템의 사용법(Usage)를 보여주는 것. 시스템이 무엇을 하는지를 나타낼 수 있는 가장 좋은 방법.
  • 객체 지향 분석 설계 단계
    1. Actor를 찾음 (Actor: 시스템을 사용하는 목적이 같은 사용자들의 집합) Ex) 글쓰기 사용자, 읽기 사용자, 운영자
    2. Actor들이 사용하는 시스템의 기능을 찾음 (=Use Case)
  • 게시글 작성하기
    • 아래와 같은 일련의 단계가 Use Case가 됨.
    • 게시글을 작성하기 위해 게시글 작성자는 시스템과 아래와 같이 상호작용을 한다.
사용자 시스템
1. 제목을 입력한다. 2. 제목의 유효성을 조사한다.
3. 본문을 입력한다. 4. 본문의 유효성을 조사한다.
5. 글 작성하기를 요청한다. 6. 글을 저장한다.

3. Architecture Exposes Usage

  • 아키텍처는 사용법에 대해서 설명
  • 일반적으로 아키텍처라고 일컫는 “MVC 구조만 있는 웹 시스템”은 아키텍처가 아님.
    • Use case를 숨기고 Delivering 매커니즘(MVC)만 노출
    • 중요한 것은 Delivering 매커니즘이 아니라 Use case. 시스템이 무엇을 하는 건지를 나타 내어야 함.
    • Use Case는 Delivering 매커니즘에 decoupled되어야 한다.
    • UI, DB, Framework, Tools 등에 대한 결정들이 Use case와는 완전히 decouple되어야 한다.
  • Use case should stand alone

4. Deferring Decisions - 결정 미루기

  • 좋은 아키텍처는 FW, WAS, UI 등과 같은 stuff들에 대한 결정을 연기하는 것을 허용.
    • stuff에 대한 결정은 연기 될 수 있어야 하고, 연기 되어야만 한다.
    • 좋은 아키텍처의 주요한 목적 중 하나
  • 시간이 지날 수록 결정을 위한 정보가 풍부해짐

5-1. Deferring Decisions examples - FitNesse

개발을 시작할 때 일반적으로 DB부터 생각한다. MySQL을 선정하고, 스키마를 짜고, 테이블을 만들고.. 이러한 방식은 지양되어야 한다. 개발자 입장에서 data부터 생각하고 시작하면 꼬인다. 반드시 절차지향적으로 가게 된다. 아키텍처를 Usecase에 집중해야 한다. (시스템의 의도)

FitNesse 는 WIKI 이며, acceptance test tool 로 개발자가 아닌 비지니스 피플들이 위키 스타일로 테스트를 작성할 수 있게 해줌.

  • 위키 저장을 위해 MySQL을 생각
  • 무언가를 하기 전에 DB를 먼저 기동하고 스키마를 개발해야한다고 생각
  • 그러나 사실 이게 바로 필요하진 않았다.
  • 위키 텍스트를 html로 변환하는 것에 초점
    • 파싱, 번역하는 코드는 DB 없이도 개발할 수 있다.
  • WikiPage 라는 중요한 추상화에 집중
    • wiki text를 가짐
    • toHtml: wiki text를 html로 parse
    • Save: wiki text를 DB에 저장
  • mocking
    • override save (DB 사용없이 use case에 집중)
    • do nothing
  • 하나 이상의 페이지를 사용해야 하는 경우 발생
    • DB가 실제로 필요한 상황
  • InMemory Page - Test Double
  • 결국에는 persistence 없이는 구현할 것이 없어졌다.
    • 생각해 낼 수 있는 새로운 테스트는 항상 DB가 필요.
    • DB를 기동하고 스키마를 만들고 DB에 위키 페이지를 저장하고 읽는 기능을 구현.
  • FileSystemPage Test Double

5-2. Summary

  • Flat File system이 충분히 좋았기 때문에 MySQL은 사용하지 않았음.
  • 주요한 아키텍처 결정을 연기했다 (Deffering Decision)

    • 실제 DB는 사용 하지 않았음 (더 간단하고 충분히 좋은 해결책이 있었기에)
  • 최종적으로 고객사에서 모든 데이터는 실제 DB에 넣어야 한다는 정책이 있어서 그렇게 했다.
    • MySqlPage를 추가.

6. Central Abstraction - 핵심 추상화

  • 많은 아키텍트는 DB를 core abstraction 이라고 생각함.
  • DB가 동작하고 스키마가 준비되기 전에는 어떠한 생각도, 작업도 시작하지 않음.
  • 핵심적인 추상화는 Fitness 프로젝트에서 WikiPage이다.
    • DB는 연기할 수 있는 Detail로 간주.
    • 좋은 아키텍처는 Tool, FW로 구성되는 것이 아니다.
    • 좋은 아키텍처는 UI, WAS, DB, FW 등과 같은 디테일에 대한 결정을 연기 할 수 있도록 해준다.
  • 어떻게 하면 이렇게 연기할 수 있나?

    • 연기하고 싶은 것(도메인 로직)에서 구조를 decouple, irrelevant하게 설계.
  • 어떻게 하면 Tool, FWs, DB에서 decouple하나?
    • 아키텍처 측면에서 SW 환경이 아닌 Use Case에 집중하라.

7. Conclusion

  • 아키텍처를 use case 에 집중
  • UI, WAS, DB, FW 등과 같은 다른 시스템 컴포넌트에 대한 결정을 미룰 수 있음.
  • 이러한 연기는 우리의 선택을 최대한 오래 열어 둘 수 있음.
  • 이말은 필요에 따라 결정을 변경할 수 있다는 것이다.
  • 프로젝트 진행 중에 충분한 정보가 생김에 따라, undo에 대한 비용 없이 여러 번 변경 할 수 도 있다.

SOLID Principles in PHP 05 - Dependency Inversion Principle (DIP)

|

Laracasts - SOLID Principles in PHP 강의를 듣고 정리한 포스팅 입니다.

1. Depedency Inversion(의존성 역전)

  • High level modules should not depend upon low level modules. Instead, they should depend upon abstractions(All of this is about decoupling code)
  • Depend on abstractions, not on concretions.
  • Dependency Inversion does not equal Dependency Injection

2. High level & Low Level

  • High Level Code - isn’t as concerned with detail. is not concerned with specific details.
  • Low Level Code - is more concerned with details and specifics.
  • Higher level code should never have to depend upon low level code. One class should never be forced to depend upon a specific implementation. Instead, it should depend upon a contract or abstractions.

전등, 텔레비전 등의 장치들을 갖고 있다고 가정할 때, 우선적으로 전력을 공급해야만 함. (plug for). 대신에 멀티탭(인터페이스)으로 각 장치들을 묶어서 전력을 공급한다고 해보자. 장치들은 사실 어떻게 전력을 공급받는지는 관심이 없으며, 멀티탭에 연결만 하면 그들이 원하는(전력 공급) 것만 받으면 되는 것임.

좀 더 개념을 확장하면, 집은 멀티탭(인터페이스 = 추상화, abstraction)를 소유하고 있다고 볼 수 있음. 그리고 전력 공급이 필요한 어떠한 장치도 멀티탭(인터페이스)의 규칙을 따라야만 함. 개별 장치(전등, 텔레비젼, 닌텐도)는 인터페이스를 갖고 있는 것이 아니라 인터페이스를 따르고 있음. 이 경우 하이 레벨 코드와 로우 레벨 코드 둘다 abstraction에 의존하고 있음. 이제, 어떤 파트가 abstraction을 갖고 있냐는 것이 흥미로운 질문이 될 수 있음.

3-1. Practices

PasswordReminder 클래스를 구현하고 생성자 메소드로, 데이터베이스에 연결하는 클래스를 파라미터로 받는다고 가정해보자. 이때 Typehint로 데이터베이스 연결 클래스는 MySQLConnection 라고 정의하자. 이러한 디자인 설계(코드 작성)는 적절하지 못한 방법이다.

  • PasswordReminder 클래스는 $dbconnection 가 무엇인지 왜 신경써야하는가? Depedency Injection의 관점에서는 코드상의 문제가 없음 (클래스를 파라미터로 의존성 주입하고 있음). 그러나 Dependency Inversion의 관점에서 볼 땐 DIP를 위반하고 있음.
  • 하이 레벨 모듈 PasswordReminder 은 로우 레벨 모듈 MySQLConnection 에 의존하면 안됨. abstraction에 의존해야하며 concrete implementation에 의존하면 안됨.
  • PasswordReminder 는 어떻게 데이터베이스에 연결하는지에 대한 지식이 필요 없음. 그저 데이터베이스가 연결되고 작동만 하면 됨 → 인터페이스 생성
class PasswordReminder {

    /**
     * @var MySQLConnection
     */

    private $dbConnection;

    public function __construct(MySQLConnection $dbconnection)
    {
        $this->dbConnection = $dbconnection;
    }
} 

3-2. Depends on abstraction

abstraction에 의존하도록 코드를 아래와 같이 변경시킬 수 있음.

  • 하이 레벨 모듈( PasswordReminder ) : \ConnectionInterface 인터페이스를 생성하고 connect 메서드 구현. 이후, PasswordReminder 의 파라미터로는 인터페이스를 넘김(abstraction에 의존)
  • 로우 레벨 모듈(DbConnection ) : abstraction에 의존하도록 클래스 생성 후 인터페이스 구현
interface ConnectionInterface {
    public function connect();
}

class DbConnection implements ConnectionInterface {    
  public function connect()    
  {          
  }
}
class PasswordReminder {

    /**
     * @var MySQLConnection
     */

    private $dbConnection;

    public function __construct(ConnectionInterface $dbconnection)
    {
        $this->dbConnection = $dbconnection;
    }
}

Dependency Injection과 Dependency Inversion는 동일한 개념이 아님. 그러나 Dependency Injection는 Dependency Inversion을 준수 할 수 있는 방법론을 제공함.

4. IoC(Inversion of Control) - Who owns Abstraction?

하이 레벨 모듈과 로우 레벨 모듈 중 어떤 것이 abstraction( ConnectionInterface ) 을 소유하고 있는지를 확인해보면, 결과론적으로는 둘다 소유하고 있지 않음.

SOLID Principles in PHP 04 - Interface Segregation Principle (ISP)

|

Laracasts - SOLID Principles in PHP 강의를 듣고 정리한 포스팅 입니다.

1. GET STARTED

A client should not be forced to implement an interface that it doesn’t use.
knowledge that one object has another object.

스타트렉의 세계관에 있다고 가정할 때 캡틴이라는 클래스가 있을 것이다. 캡틴의 주요 역할 중 하나는 선원들을 고용하는 것이며 코드로 작성하면 다음과 같을 것이다.

  • Captain has knowledge of the worker. Captain is depend upon worker, it means that it is all depend up anything that worker is depend upon.
  • it is not ideal. too many knowledge looking out here.
class Captain {
    public function hire(Worker $worker)
    {

    }
}

Worker 클래스 구성 후 Captain 클래스에서 호출

class Worker {
    public function work(){}
    public function sleep(){}
}

class Captain {
    public function manage(Worker $worker)
    {
        $worker->work();
        $worker->sleep();
    }
}

2. A client should not be forced to implement an interface that it doesn’t use.

만약에 이제 캡틴이 안드로이드 선원을 뽑는다고 하면, 인터페이스 WorkerInterface 를 만들어 안드로이드 클래스 AndroidWorker 가 인터페이스를 구현하도록 할 것이다. 여기서, 안드로이드는 잠을 자지 않으므로 sleep 메소드는 null 을 반환하게 코드를 작성할 것임.

  • 여기서 ISP을 위반하게 된다. (client should not be forced to implement an interface that it doesn’t use)
  • AndroidWorker 클래스는 sleep 메소드를 강제 받고 있음.
interface WorkerInterface {
    public function work();
    public function sleep();
}

class HumanWorker implements WorkerInterface {
    public function work(){}
    public function sleep()
    {
        return 'human sleeping';
    }
}

class AndroidWorker implements WorkerInterface {
    public function work(){}
    public function sleep()
    {
        return null; // violates the ISP
    }
}

WorkerInterface 를 다음과 같이 재 분류

  • WorkerableInterface
  • SleepableInterface

HumanWorker 클래스는 위 2개의 인터페이스를 모두 받으며, AndroidWorker 클래스는 WorkerableInterface 만 호출하는 걸로 ISP를 준수 할 수 있게 됨.

interface WorkerableInterface {
    public function work();
}

interface SleepableInterface {
    public function sleep();
}

class HumanWorker implements WorkerableInterface, SleepableInterface {
    public function work(){}

    public function sleep()
    {
        return 'human sleeping';
    }
}

class AndroidWorker implements WorkerableInterface {
    public function work(){}
}

3. Open Closed Principle 적용

캡틴 클래스로 돌아와 코드를 다시 다듬고나면 이제 sleep 메소드에 대한 고민을 해야함. 아래처럼 조건문을 걸어주는 방식은 적절한 방법이 아닐 것이다. (OCP 위반)

class Captain {
    public function manage(WorkerableInterface $worker)
    {
        $worker->work();
        if ($worker instanceof AndroidWorker) return;

        $worker->sleep();
    }
}

이제 생각할 수 있는 방법은 ManagableInterface를 만드는 것.

  • ManageInterface has a method called beManaged
  • HumanWorker & AndroidWorker class implements ManagableInterface
  • Captain class now calls beManaged method from ManagableInterface only

이 방법을 통해 ManagableInterface 는 더이상 $worker 오브젝트에 의존하지 않게 되어, 디자인을 향상시키며 결합도를 낮출 수 있게 됨. 또다른 말로는, $worker 가 갖고 있는 어떠한 implementation에 의존하지 않으면서 클라이언트 (manage 메소드) 는 ManagableInterface 를 준수하는 어떠한 오브젝트 사용할 수 있게됨.

interface ManagableInterface {
    public function beManaged();
}

class HumanWorker implements WorkerableInterface, SleepableInterface, ManagableInterface {
    public function work(){}
    public function sleep()
    {
        return 'human sleeping';
    }

    public function beManaged()
    {
        $this->work();
        $this->sleep();
    }
}

class AndroidWorker implements WorkerableInterface, ManagableInterface {
    public function work(){}
    public function beManaged()
    {
        $this->work();
    }
}

class Captain {
    public function manage(ManagableInterface $worker)
    {
        $worker->beManaged();
    }
}