🌆
Sunset Archive
GitHubLinkedInBlog
  • litsynp
  • Archives
    • Projects
      • Let's Parking!
      • 맛집몬스터 (JMT Monster)
      • spring-x-demo Projects
    • Articles
      • My Thoughts on GitBook
      • Wake-on-LAN & RDP Setup
    • Learning
      • Languages
        • Rust
          • Useful Links
          • Syntax
          • Pointers
          • Dependencies
          • Logging
          • Phantom Types
          • Iterable
            • Optional Skip
        • Go
          • Useful Links
          • Structures
          • Zero Values
          • Error Handling
          • Dependency Injection
          • Project Layout
        • JavaScript/TypeScript
          • Basics
            • Types
              • [JS] Falsy Values
              • [TS] Types - unknown, any, never
            • Rest Parameters (...)
            • Spread Syntax (...)
            • Destructuing Assignment
            • CJS, AMD, UMD, ESM
          • Advanced
            • Conditional Destructuring Assignment
            • Type Guards
          • Miscellaneous
            • Dependency Injection in JS?
            • ESLint, Prettier, TypeScript
          • Node
            • Useful Links
            • General
              • V8 Engine
              • Version Management: NVM
              • Environment Variables
            • Database
              • Knex
        • C
          • Dynamic Multi-dimensional Arrays
        • Spring
          • General
            • @Import v.s. @ContextConfiguration
            • MessageSource
          • Kotlin+Spring
            • Kotlin Annotations (Use-Site Targets)
            • Handling Null in Request DTO
            • Handling Null in URL
          • Reactive Stack
            • Reactive API with Spring WebFlux
          • Spring Security
            • Google OAuth Setup
          • Spring Batch
            • Bulk Insert
        • Kotlin
          • val/var in Constructor
          • Initializer Blocks
          • Inheritance Modifiers (final, open, abstract)
          • Delegate Pattern
        • Java
          • Serialization
          • Random Number Generation
            • (1) Math.random() v.s. Random
            • (2) Random v.s. ThreadLocalRandom
        • Python
          • Version Management
        • Ruby
          • Installation
          • Getters & Setters
        • Elixir
        • Erlang
        • Flutter
        • AWS
          • AWS CLI
        • Terraform
          • Installation
          • Basics
      • Code
        • OOP
          • The 4 Basic Concepts of OOP
          • The SOLID Principles
          • GRASP
          • Spring POJO
          • Others
        • Functional Programming
          • Currying
          • Higher-Order Function
          • Closure
          • Monad
        • 공변성, 반공변성, 무공변성
        • Others
          • UUID
          • GraphQL
          • Multimedia
            • Streaming
          • Geography
            • 위도 (Latitude), 경도 (Longitude)
      • Tools
        • Nix
        • Mermaid
          • Flowchart
          • Sequence Diagram
          • Class Diagram
          • Entity Relationship Diagrams
        • VSCode
          • VSCode CLI
          • VSCode Extensions
        • JetBrains
          • IntelliJ - Open Projects in Tabs
          • Delete Leftover IDE Directories
        • vim
          • Commands
      • Books
        • 다재다능 코틀린 프로그래밍 (Programming Kotlin)
          • Pt 01. 코틀린으로 스크립팅 하기
            • Ch 01. 코틀린 시작하기
            • Ch 02. Java 개발자를 위한 코틀린 필수 사항
            • Ch 03. 함수를 사용하자
            • Ch 05. 콜렉션 사용하기
        • 오브젝트 (Object)
          • Ch.0 들어가며 - 프로그래밍 패러다임
          • Ch.1 객체, 설계
          • Ch.2 객체지향 프로그래밍
          • Ch.3 역할, 책임, 협력
          • Ch.4 설계 품질과 트레이드오프
          • Ch.5 책임 할당하기
          • Ch.6 메시지와 인터페이스
          • Ch.7 유연한 설계
          • Ch.8 의존성 관리하기
          • Ch.9 유연한 설계
          • Ch.10 상속과 코드 재사용
          • Ch.11 합성과 유연한 설계
          • Ch.12 다형성
          • Ch.13 서브클래싱과 서브타이핑
          • Ch.14 일관성 있는 협력
          • Ch.15 디자인 패턴과 프레임워크
          • End. 마치며 - 나아가기
          • 후기
        • 헤드 퍼스트 디자인 패턴 (Head First Design Patterns)
          • Ch.1 디자인 패턴 소개와 전략 패턴
          • Ch.2 옵저버 패턴
          • Ch.3 데코레이터 패턴
          • Ch.4 팩토리 패턴
          • Ch.5 싱글턴 패턴
          • Ch.6 커맨드 패턴
          • Ch.7 어댑터 패턴과 퍼사드 패턴
          • Ch.8 템플릿 메소드 패턴
          • Ch.9 반복자 패턴과 컴포지트 패턴
          • Ch.10 상태 패턴
          • Ch.11 프록시 패턴
          • Ch.12 복합 패턴
        • 이펙티브 타입스크립트 (Effective TypeScript)
          • Ch.1 타입스크립트 알아보기
            • Item 1 타입스크립트와 자바스크립트의 관계 이해하기
            • Item 2 타입스크립트 설정 이해하기
            • Item 3 코드 생성과 타입이 관계없음을 이해하기
            • Item 4 구조적 타이핑에 익숙해지기
            • Item 5 any 타입 지양하기
          • Ch.2 타입스크립트의 타입 시스템
            • Item 6 편집기를 사용하여 타입 시스템 탐색하기
            • Item 7 타입이 값들의 집합이라고 생각하기
            • Item 1-7 Study Summary
            • Item 8 타입 공간과 값 공간의 심벌 구분하기
            • Item 9 타입 단언보다는 타입 선언을 사용하기
            • Item 10 객체 래퍼 타입 피하기
            • Item 11 잉여 속성 체크의 한계 인지하기
            • Item 12 함수 표현식에 타입 적용하기
            • Item 13 타입과 인터페이스의 차이점 알기
            • Item 14 타입 연산과 제너릭 사용으로 반복 줄이기
            • Item 15 동적 데이터에 인덱스 시그니처 사용하기
            • Item 16 number 인덱스 시그니처보다는 Array, 튜플, ArrayLike를 사용하기
            • Item 17 변경 관련된 오류 방지를 위해 readonly 사용하기
            • Item 18 매핑된 타입을 사용하여 값을 동기화하기
          • Ch.3 타입 추론
            • Item 19 추론 가능한 타입을 사용해 장황한 코드 방지하기
            • Item 20 다른 타입에는 다른 변수 사용하기
            • Item 21 타입 넓히기
            • Item 22 타입 좁히기
            • Item 23 한꺼번에 객체 생성하기
            • Item 24 일관성 있는 별칭 사용하기
            • Item 25 비동기 코드에는 콜백 대신 async 함수 사용하기
            • Item 26 타입 추론에 문맥이 어떻게 사용되는지 이해하기
            • Item 27 함수형 기법과 라이브러리로 타입 흐름 유지하기
          • Ch.4 타입 설계
            • Item 28 유효한 상태만 표현하는 타입을 지향하기
            • Item 29 사용할 때는 너그럽게, 생성할 때는 엄격하게
            • Item 30 문서에 타입 정보를 쓰지 않기
            • Item 31 타입 주변에 null 값 배치하기
            • Item 32 유니온의 인터페이스보다는 인터페이스의 유니온을 사용하기
            • Item 33 string 타입보다 더 구체적인 타입 사용하기
            • Item 34 부정확한 타입보다는 미완성 타입을 사용하기
            • Item 35 데이터가 아닌, API와 명세를 보고 타입 만들기
            • Item 36 해당 분야의 용어로 타입 이름 짓기
            • Item 37 공식 명칭에는 상표를 붙이기
          • Ch.5 any 다루기
            • Item 38 any 타입은 가능한 한 좁은 범위에서만 사용하기
            • Item 39 any를 구체적으로 변형해서 사용하기
            • Item 40 함수 안으로 타입 단언문 감추기
            • Item 41 any의 진화를 이해하기
            • Item 42 모르는 타입의 값에는 any 대신 unknown을 사용하기
            • Item 43 몽키 패치보다는 안전한 타입을 사용하기
            • Item 44 타입 커버리지를 추적하여 타입 안전성 유지하기
          • Ch.6 타입 선언과 @types
            • Item 45 devDependencies에 typescript와 @types 추가하기
            • Item 46 타입 선언과 관련된 세 가지 버전 이해하기
            • Item 47 공개 API에 등장하는 모든 타입을 익스포트하기
            • Item 48 API 주석에 TSDoc 사용하기
            • Item 49 콜백에서 this에 대한 타입 제공하기
            • Item 50 오버로딩 타입보다는 조건부 타입을 사용하기
            • Item 51 의존성 분리를 위해 미러 타입 사용하기
            • Item 52 테스팅 타입의 함정에 주의하기
          • Ch.7 코드를 작성하고 실행하기
            • Item 53 타입스크립트 기능보다는 ECMAScript 기능을 사용하기
            • Item 54 객체를 순회하는 노하우
            • Item 55 DOM 계층 구조 이해하기
            • Item 56 정보를 감추는 목적으로 private 사용하지 않기
            • Item 57 소스맵을 사용하여 타입스크립트 디버깅하기
          • Ch.8 타입스크립트로 마이그레이션하기
            • Item 58 모던 자바스크립트로 작성하기
            • Item 59 타입스크립트 도입 전에 @ts-check와 JSDoc으로 시험해 보기
            • Item 60 allowJS로 타입스크립트와 자바스크립트 같이 사용하기
            • Item 61 의존성 관계에 따라 모듈 단위로 전환하기
            • Item 62 마이그레이션의 완성을 위해 noImplicitAny 설정하기
        • Dive Into Design Patterns
          • 디자인 패턴 소개
          • 소프트웨어 디자인 원칙들
          • 디자인 패턴 목록
          • 유용한 링크
        • 가상 면접 사례로 배우는 대규모 시스템 설계 기초 (System Design Interview)
          • Key Points
          • Real Life Systems
          • Engineering Blogs
        • Node.js 디자인 패턴 바이블 (Node.js Design Patterns 3rd Edition)
        • 리팩터링 2판 (Refactoring: 2nd Ed.)
          • 1장 리팩터링: 첫 번째 예시
          • 2장 리팩터링 원칙
          • 3장 코드에서 나는 악취
          • 4장 테스트 구축하기
Powered by GitBook
On this page
  • About
  • 델리게이션(Delegation)이란?
  • 상속 대신 델리게이션을 써야 하는 상황
  • 델리게이션을 사용한 디자인
  • 어려운 델리게이션
  • 코틀린의 by 키워드를 사용한 델리게이션
  • 파라미터에 위임하기
  • 메소드 충돌 관리
  • REF

Was this helpful?

Edit on GitHub
  1. Archives
  2. Learning
  3. Languages
  4. Kotlin

Delegate Pattern

PreviousInheritance Modifiers (final, open, abstract)NextJava

Last updated 9 months ago

Was this helpful?

About

객체지향 프로그래밍의 디자인 방식인 상속과 델리게이션 중 델리게이션을 알아본다.

델리게이션(Delegation)이란?

델리게이션은 상속처럼 객체지향 프로그래밍의 디자인 방식이다. 두 방식 모두 클래스를 다른 클래스로부터 확장시킨다.

상속은 강력하고 자주 사용되기 때문에 강력하게 묶이고 수정하기가 어렵다. 상속이 가능한 대부분의 언어는 일단 상속하게 되면 클래스가 다른 베이스(부모) 클래스들 사이에서 선택할 권한을 주지 않는다.

델리게이션은 상속보다 유연하다. 객체가 처리할 일을 다른 클래스의 인스턴스에게 위임하거나 넘겨버릴 수 있다.

같은 설계론 책들은 가능한 경우 상속보다 델리게이션을 사용하라고 조언한다. 에서도 Java 프로그래머들에게 상속보다는 델리게이션을 사용할 것을 강하게 추천했다.

하지만 Java에서 델리게이션보다는 상속을 통한 재사용을 많이 봤을 것이다. 왜냐하면 Java는 상속에 대한 많은 지원을 해줬지만 델리게이션에 대한 지원은 약하기 때문이다. 반면 코틀린은 델리게이션을 위한 기능을 제공한다.

상속 대신 델리게이션을 써야 하는 상황

상속은 객체지향 언어에서 흔하고, 많이 사용되는 기능이다. 델리게이션은 더 유연하지만, 많은 객체지향 언어들은 따로 지원하지 않는다. 델리게이션을 사용하려면 상속보다 더 많은 노력이 들어가기 때문에 꺼려지기도 한다. 코틀린은 상속, 델리게이션 둘 다 지원하기 때문에 문제를 기반으로 적절한 해법을 선택하면 된다.

  • 클래스의 객체가 다른 클래스의 객체가 들어갈 자리에 쓰여야 한다면 상속을 사용해라.

  • 클래스의 객체가 단순히 다른 클래스의 객체를 사용만 해야 한다면 델리게이션을 사용해라.

Candidate 클래스는 왼쪽에서는 상속을 사용했고, 오른쪽에서는 델리게이션을 사용했다.

상속

왼쪽 그림에서 Candidate 클래스가 BaseClass를 상속 받을 때 Candidate 클래스의 인스턴스는 내부에 BaseClass의 인스턴스를 같이 갖고 다닌다고 볼 수 있다.

  • 베이스 인스턴스는(BaseClass)는 자식클래스(Candidate)와 분리시킬 수 없을 뿐 아니라 변경할 수도 없다.

  • 컴파일 시간에 BaseClass의 인스턴스를 참조하고 있는 Caller는 실행 시간에는 Candidate의 인스턴스로 사용될 수도 있다.

짧게 말해, 상속을 사용해 자식 클래스를 설계하면 많은 제약사항이 따른다.

델리게이션

오른쪽 그림에서 Candidate 클래스가 DelegateObject로 델리게이션을 하면 Candidate 클래스의 인스턴스는 델리게이션의 참조를 갖는다.

실제 델리게이션 클래스는 다양할 수 있다. Groovy, JavaScript 등 델리게이션을 지원하는 몇몇 언어에서는 런타임에서 Candidate 인스턴스에서 위임되는 객체를 변경할 수도 있다. 상속과는 다르게 인스턴스들은 분리가 가능하고, 덕분에 엄청난 유연성을 갖게 된다. Caller는 Candidate에게 요청을 보내고, Candidate는 적절하게 해당 요청을 한다.

선택

  • 클래스의 구현을 새로 하거나 한 클래스의 인스턴스를 '개는 동물이다'와 같이 포함 관계(IS-A)에 있는 다른 클래스로 대체할 때 상속을 사용하라.

  • 오직 다른 객체의 구현을 재사용하는 경우라면, 델리게이션을 사용하라.

델리게이션이 적절한 설계일 때 Java 같은 언어를 선택하면 많은 코드를 중복해서 작성해야 한다. 중복코드를 작성하게 되면 관리가 어려워진다. 코틀린은 델리게이션을 사용할 때 더 좋은 선언적인 접근 방식을 사용한다.

델리게이션을 사용한 디자인

상속이 방해 요소가 되는 시점을 알아보고, 문제 해결을 위해 델리게이션을 사용해본다.

디자인적 문제점

기업의 소프트웨어 프로젝트를 시뮬레이션하는 어플리케이션을 만들어 본다. 먼저 일을 할 작업자이다.

interface Worker {
    fun work()
    fun takeVacation()
}

작업자는 두 개의 일을 할 수 있다:

  1. 일을 한다.

  2. 가끔 휴가를 떠난다.

이제 Worker 중에서 각각의 언어에 특화된 두 개의 클래스를 구현해본다.

class JavaProgrammer : Worker {
    override fun work() = println("...write Java...")
    override fun takeVacation() = println("...code at the beach...")
}

class CSharpProgrammer : Worker {
    override fun work() = println("...write C#...")
    override fun takeVacation() = println("...branch at the ranch...")
}

회사에는 팀을 관리하기 위한 소프트웨어 개발 매니저가 필요하다.

class Manager

Manager 클래스는 아주 작고 효율적이며 코드에 쓰여있듯 아무 일도 하지 않는다. 매니저 인스턴스에서 work()를 호출해봐야 아무 의미가 없다. Manager에 로직을 넣기 위해 디자인을 해야 하는데, 이것은 쉬운 일이 아니다.

잘못된 경로로의 상속

회사는 프로젝트를 실행하고 전달하기 위해 Manager에 의존하게 된다. Manager는 프로그래머에게 일을 시킬 것이다. 가장 단순한 형태로 만들어보자면 Manager가 work()를 호출하기 위해서는 Worker 내의 구현(work())을 실행하면 된다.

이를 위한 방법 중 하나는 상속을 사용하는 것이며, Java에서는 주로 상속을 사용한다. Manager를 JavaProgrammer에서 상속받으면 Manager 클래스에서 구현을 다시 작성할 필요가 없다. 이런 점에서 상속이 매력적이다.

open class JavaProgrammer : Worker {
    // ...
}

그리고 Manager 클래스로 JavaProgrammer로부터 상속받는다.

class Manager : JavaProgrammer()

이제 새 Manager 인스턴스에서 work()를 사용할 수 있다.

val doe = Manager()
doe.work() // ...write Java...

잘 동작하지만 결함이 존재한다. Manager 클래스는 JavaProgrammer 클래스에 갇혀버려 CSharpProgrammer가 제공하는 구현을 사용할 수 없다.

이번에는 상속의 또 다른 예상치 못한 결과인 대체 가능성에 대해서 살펴보도록 한다.

우리는 Manager가 JavaProgrammer나 다른 특정 언어의 프로그래머라고 단정지은 적 없다. 하지만 상속이 그렇게 만들었다.

val coder: JavaProgrammer = doe

의도된 디자인은 아니지만, 막을 방도가 없다. 우리가 의도하는 바는 JavaProgrammer 뿐 아니라 작업을 맡길 수 있는 모든 Worker에게 Manager가 의존하는 것이다.

어려운 델리게이션

Java 같은 언어는 상속을 위한 문법은 갖고 있지만 델리게이션에 대해서는 그렇지 않다.

델리게이션의 예제를 살펴본다. 아래의 코드는 Java에서 Manager가 Worker에게 델리게이션을 사용하는 방식을 코틀린 코드로 나타낸 것이다.

class Manager(val worker: Worker) {
    fun work() = worker.work()
    fun takeVacation() = worker.work() // 쉬는게 쉬는게 아니다.
}

이제 사용해보자.

val doe = Manager(JavaProgrammer())
doe.work() // ...write Java...

이 방식이 일단 상속보다 좋은 점은:

  • Manager가 JavaProgrammer의 클래스에 강하게 묶이지 않는다는 점이다. CSharpProgrammer는 물론 Worker를 구현하는 어떤 클래스의 인스턴스든 넘길 수 있다.

  • JavaProgrammer 클래스가 더 이상 상속을 해주지 않기 때문에 open을 입력할 필요가 없다.

하지만 이런 디자인은 바람직하지 못하다. 소프트웨어 디자인의 기본사항 몇 가지도 어기고 있다.

이런 문제로 인해 Java 개발자들은 델리게이션보다는 상속을 사용하려는 경향이 있다. 델리게이션에 이런 문제가 생긴 이유는 언어의 지원이 부족하기 때문이다. 반대로 상속은 지원을 많이 받고 있지만, Manager는 JavaProgrammer가 아니다(IS-A가 아니다). 상속을 사용한 모델링도 LSP를 위반한다.

코틀린은 이런 문제를 해결하기 위해 언어 수준의 델리게이션을 지원한다.

코틀린의 by 키워드를 사용한 델리게이션

위의 예제에서 델리게이션을 Java 방식으로 구현했다. Manager의 바디는 중복된 메소드 호출과 DRY, OCP 원칙 위반으로 지저분해졌다. 코틀린에서는 컴파일러에게 코드를 요청(라우팅)할 수 있다. 그래서 Manager는 보스답게 번거로운 작업 없이 일을 맡길 수 있다.

class Manager() : Worker by JavaProgrammer()

위의 예제에서 Manager는 어떤 메소드도 갖고 있지 않다. Manager는 JavaProgrammer를 이용해 Worker 인터페이스를 구현하고 있다. 코틀린 컴파일러는 Worker에 속하는 Manager 클래스의 메소드를 바이트코드 수준에서 구현하고, by 키워드 뒤에 나오는 JavaProgrammer 클래스의 인스턴스로 호출을 요청한다. 즉, by 키워드를 통해 우리가 시간을 들여 수동으로 구현한 델리게이션을 대신 해준다.

val doe = Manager()
doe.work() // ...write Java...

언뜻 보기엔 상속을 이용한 방식과 아주 비슷해 보이지만, 주요한 차이점이 있다.

첫째, Manager 클래스는 JavaProgrammer를 상속받지 않았다. 이전에 상속을 이용한 예제에서는 Manager의 인스턴스를 JavaProgrammer 타입의 참조가 필요한 곳에 사용할 수 있었는데, 더 이상 그런 일은 발생하지 않고 오류를 발생시킨다.

val coder: JavaProgrammer = doe // ERROR: type mismatch

둘째, 상속을 사용한 솔루션에서 work() 메소드를 호출하는 것이 Manager 클래스에서는 구현되지 않았다. 대신 베이스 클래스로 요청을 넘겼다. 컴파일러가 내부적으로 Manager 클래스에 메소드를 생성하고 요청(라우팅)을 한다. 사실상 우리가 doe.work()를 호출하면 Manager 클래스의 보이지 않는 메소드인 work()를 호출하는 격이다. 이 메소드는 코틀린 컴파일러에 의해 합성되었고, 델리게이션에게 호출을 요청한다. 이 경우에는 클래스 선언 시 주어진 JavaProgrammer의 인스턴스에게 요청하게 된다.

위의 솔루션은 델리게이션의 가장 간단한 형태이다.

파라미터에 위임하기

worker by JavaProgrammer()라는 코드를 작성했다. 이런 구조로 작성하면 이슈가 존재한다.

  1. Manager 클래스는 오직 JavaProgrammer 인스턴스에만 요청할 수 있다.

  2. Manager의 인스턴스는 델리게이션에 접근할 수 없다. 즉, Manager 클래스 안에 다른 메소드를 작성하더라도 해당 메소드에서는 델리게이션에 접근할 수 없다는 뜻이다.

이런 제약은 인스턴스를 생성하면서 델리게이션을 지정하지 않고 생성자에 델리게이션 파라미터를 전달함으로써 해결할 수 있다.

class Manager(val staff: Worker) : Worker by staff {
    fun meeting() = println("organizing meeting with ${staff.javaClass.simpleName}")
}

val doe = Manager(CSharpProgrammer())
val roe = Manager(JavaProgrammer())
doe.work() // ...write C#...
doe.meeting() // organizing meeting with CSharpProgrammer
roe.work() // ...write Java...
roe.meeting() // organizing meeting with JavaProgrammer

Manager 클래스의 생성자는 staff라는 파라미터를 받고, val로 정의했기 때문에 속성이 된다. val이 제거된다면 파라미터로 남을 것이다. val이 사용되든 안되든 상관없이 클래스는 staff 파라미터를 델리게이션으로 사용한다.

Manager 클래스의 meeting() 메소드에서 staff에 접근할 수 있다. 왜냐면 staff가 Manager 객체의 속성이기 때문이다. work() 같은 메소드를 호출하면 staff가 델리게이션이기 때문에 staff로 요청이 전달된다.

메소드 충돌 관리

코틀린 컴파일러는 델리게이션에 사용되는 클래스마다 델리게이션 메소드를 위한 랩퍼를 만든다. 사용하는 클래스와 델리게이션 클래스에 동일한 이름과 시그니처가 있는 메소드가 있더라도 코틀린이 해결하기 때문에 충돌이 일어나지 않는다.

이전 예제에서 Worker 인터페이스는 takeVacation() 메소드를 갖고 있고 Manager 클래스는 해당 메소드를 델리게이션인 Worker에게 위임했다. 비록 이것이

WIP

REF

거의 모든 내용은 다음 책에서 나왔다.

이것이 객체지향 언어에서는 상속을 통한 다형성의 매력이라고 생각하고 사용한다. 하지만 베이스 클래스에서 상속받은 인스턴스를 자식 클래스에서 마음대로 바꾸려는 행동은 오류를 일으킬 수 있다. 에서 설명된 리스코프 치환의 원칙(LSP)이 경고하듯 말이다. 문제는 자식 클래스에서 부모 클래스의 메소드를 오버라이드할 때 베이스 클래스의 외부 동작을 유지해야 한다는 것이다.

Manager 클래스에 work() 메소드를 구현했다. 해당 work() 메소드는 Manager 인스턴스가 참조로 가지고 있는 Worker의 인스턴스를 호출하는 기능만 갖고 있다. takeVacation() 메소드도 동일하게 worker의 참조를 호출한다. Worker에게 더 많은 메소드가 생긴다고 가정해보자. Manager에는 더 많은 호출 코드가 들어가야 할 것이다. 모든 호출 코드는 호출할 메소드명을 제외하고는 거의 비슷하다. 이는 에서 설명한 "DRY(Don't Repeat Yourself)" 원칙을 위반한다.

그 외에도 에서 논의된 바 있는 개방-폐쇄 원칙(OCP)도 지키지 못했다. OCP란 소프트웨어의 모듈(클래스, 메소드 등)은 확장에는 열려있어야 하고 변경에는 닫혀있어야 한다는 원칙이다. 클래스를 확장하기 위해서 클래스를 변경하면 안 된다는 뜻이다. 지금 구현된 디자인은 Worker 인터페이스에 deploy() 메소드를 추가하면 Manager가 해당 메소드를 위임하는 호출을 하기 위해 Manager 클래스도 변경하여 해당 메소드를 호출하는 메소드를 추가해야 한다. OCP의 위반이다.

<클린 소프트웨어>(제이펍, 2017)
<실용주의 프로그래머>(인사이트, 2014)
<클린 소프트웨어>(제이펍, 2017)
Yes24 - <다재다능 코틀린 프로그래밍>(영진닷컴, 2021)
<GoF의 디자인 패턴>(프로텍미디어, 2015)
<이펙티브 자바 3/E>(인사이트, 2018)
Inheritance v.s. Delegation