Kotlin - by로 Delegate Pattern 쉽게 구현하기

디자인 패턴에서 Delegate Pattern은 어떤 기능을 자신이 처리하지 않고 다른 객체에 위임을 시켜 그 객체가 일을 처리하도록 하는 것입니다. Delegate Pattern을 설명할 때, 상속(Inheritance)과 구성(Composition)에 대해서 이야기를 합니다.

상속은 클래스의 변수와 메소드를 모두 받기 때문에 재구현할 필요가 없어서 편리합니다. 하지만 올바르지 않은 상속은 많은 문제를 발생시킵니다. 그 문제점 중 하나는 객체의 유연성이 떨어진다는 것인데요.. 이런 해결방법으로 Composition(또는 Aggregation)관계로 구현하는 것을 권장하고 있습니다. Composition(또는 Aggregation)은 상속이 아닌, 클래스 안에 객체를 소유하고 있는 관계를 말합니다.(흔히 has-a라는 관계라고 하고, 상속은 is-a라고 말합니다)

Delegate Pattern은 Composition을 이용하는 일반적인 패턴입니다. Composition 객체의 함수가 많아지면 형식적인 코드(boilerplate code)를 많이 작성해야 할 수 있는데요. 코틀린은 by라는 키워드를 이용하여 적은 코드로 적용할 수 있도록 지원하고 있습니다.

먼저 Delegate Pattern에 대해서 간단히 예제로 알아보고, by를 이용하여 쉽게 적용하는 방법을 알아보겠습니다.

Delegate Pattern 예제

아래 코드를 보시면 인터페이스 IWindow, 클래스 TransparentWindow, UI가 있습니다. 클래스들은 모두 IWindow 인터페이스를 상속받았습니다. UI는 TransparentWindow를 상속받지 않고, mWindow를 클래스 내부에 갖고 있습니다. 그리고 UI는 mWindow.getWidth()처럼 mWindow의 함수를 호출해주고 있습니다. 이 구조에서, UI 클래스는 TransparentWindow의 기능을 내부 변수 mWindow에 위임하였습니다. 이 구조를 유지하기 위해 UI는 IWindow의 인터페이스를 상속받았고 오버라이드 메소드 안에서 mWindow의 메소드를 호출해주었습니다. 여기서 형식적인 코드(boilerplate code)는 UI.getWidth()와 UI.getHeight()입니다. 만약 IWindow의 메소드가 20개라면, 20개에 대한 wrapper 메소드를 모두 작성해주어야 합니다.

interface IWindow {
    fun getWidth() : Int
    fun getHeight() : Int
}

open class TransparentWindow : IWindow {
    override fun getWidth(): Int {
        return 100
    }

    override fun getHeight() : Int{
        return 150
    }
}

class UI(window: IWindow) : IWindow {
    val mWindow: IWindow = window

    override fun getWidth(): Int {
        return mWindow.getWidth()
    }

    override fun getHeight(): Int {
        return mWindow.getHeight()
    }
}

위 클래스는 아래 코드처럼 사용될 수 있습니다. UI 객체에 인자로 TransparentWindow를 넘겨주고, ui.getWidth()가 호출되면 UI 클래스는 내부의 TransparentWindow에 모든 일을 위임합니다.

fun main(args: Array<String>) {
    val window: IWindow = TransparentWindow()
    val ui = UI(window)
    System.out.println("Width : ${ui.getWidth()}, height: ${ui.getHeight()}")
}

// 결과
// Width : 100, height: 150

by를 사용하여 작성한 Delegate Pattern 예제

위에서 직접 Delegate Pattern을 구현하였습니다. 그래서 형식적인(boilerplate) 코드를 직접 작성하였습니다. 코틀린은 by 키워드로 Delegate Pattern을 쉽게 구현하도록 도와주며 이 과정에서 boilerplate code를 생략할 수 있습니다.

아래 코드를 보시면 UI 클래스는 위와 거의 동일하지만 IWindow by window 부분이 다르다는 것을 알 수 있습니다. 이렇게 코드를 작성하면 IWindow 인터페이스에 전달받은 window객체를 이용하여 Delegate Pattern 코드를 자동으로 작성해줍니다. 그렇기 때문에 UI가 상속받은 IWindow의 인터페이스를 구현하지 않아도 됩니다.

interface IWindow {
    fun getWidth() : Int
    fun getHeight() : Int
}

open class TransparentWindow() : IWindow {
    override fun getWidth(): Int {
        return 100
    }

    override fun getHeight() : Int{
        return 150
    }
}

class UI(window: IWindow) : IWindow by window {

}

main은 위와 동일합니다. UI의 IWindow 인터페이스는 구현되었기 때문에 ui.getWidth를 호출하면 TransparentWindow에 모든 역할을 위임합니다.

fun main(args: Array<String>) {
    val window: IWindow = TransparentWindow()
    val ui = UI(window)
    System.out.println("Width : ${ui.getWidth()}, height: ${ui.getHeight()}")
}
// 결과
// Width : 100, height: 150

아래 코드는 위 코드를 자바로 변환한 것입니다. UI 클래스를 보시면 상속받은 IWindow에 대한 메소드들이 모두 구현되어있습니다. 메소드에는 인자로 전달받은 TransparentWindow 객체의 동일한 함수 이름을 호출하도록 구현되어있습니다.

public interface IWindow {
   int getWidth();

   int getHeight();
}

public class TransparentWindow implements IWindow {
   public int getWidth() {
      return 100;
   }

   public int getHeight() {
      return 150;
   }
}

public final class UI implements IWindow {
   private final IWindow $$delegate_0;

   public UI(@NotNull IWindow window) {
      Intrinsics.checkParameterIsNotNull(window, "window");
      super();
      this.$$delegate_0 = window;
   }

   public int getHeight() {
      return this.$$delegate_0.getHeight();
   }

   public int getWidth() {
      return this.$$delegate_0.getWidth();
   }
}
public final class Kotlin20Kt {
   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      IWindow window = (IWindow)(new TransparentWindow());
      UI ui = new UI(window);
      System.out.println("Width : " + ui.getWidth() + ", height: " + ui.getHeight());
   }
}

정리

by 키워드를 이용하여 Delegate Pattern을 구현할 수 있고, 많은 boilerplate code들을 생략할 수 있습니다.

참고

Loading script...
codechachaCopyright ©2019 codechacha