디자인 패턴을 kotlin으로 써 보았다 Template 편

객체 지향으로 소중한 Interface의 사고방식과 객체의 재이용성을 배우기 위해 「Java 언어로 배우는 디자인 패턴 입문」 에 대해 배우고, 자바와 함께 kotlin으로 써 보기로 했습니다.
이번에는 Template에 대해 정리합니다.

Template란?



슈퍼 클래스에서 처리의 틀(닮은 공유의 처리)을 정해, 서브 클래스로 구체적인 내용을 정하는 디자인 패턴.
하기는 「문자나 문자열을 5회 반복해 표시한다」라고 하는 공통의 처리를 프레임워크로서 서브 클래스에 각각 구체적인 처리로서 구현해 갑니다.

AbstractDisplay 클래스



형태가 되는 4개의 추상 메소드가 정의되고 있어, open, print, close의 구체적인 처리는 서브 클래스에 구현하게 되어 있다.

AbstractDisplay.java
abstract class AbstractDisplay {
    public abstract void open();
    public abstract void print();
    public abstract void close();
    public final void display() {
        open();
        for(int i = 0; i < 5; i++) {
            print();
        }
        close();
    }
}

AbstractDisplay.kt
abstract class AbstractDisplay {
    abstract fun open(): Unit
    abstract fun print(): Unit
    abstract fun close(): Unit
    fun display(): Unit {
        open()
        for (i: Int in 1..5) print()
        close()
    }
}

CharDisplay 클래스



템플릿이 되는 AbstractDisplay를 상속한 서브 클래스로, 이 클래스에서는 생성자로 건네받은 한 문자를 정형해 표시하는 구체적인 처리를 구현해 간다.
Kotlin에서 인터페이스를 상속하는 경우 ()는 필요하지 않지만 추상 클래스라고 상속 할 때 AbstractDisplay ()와 같이 ()를 붙여야합니다. 인터페이스는 생성자가 없지만 추상 클래스 는 생성자의 구현이 가능하기 때문에 디폴트로 붙인다.

CharDisplay.java
class CharDisplay extends AbstractDisplay {
    private char ch;
    public CharDisplay(char ch) {
        this.ch = ch;
    }
    @Override
    public void open() {
        System.out.print("<<");
    }
    @Override
    public void print() {
        System.out.print(ch);
    }
    @Override
    public void close() {
        System.out.println(">>");
    }
}

CharDisplay.kt
class CharDisplay(private val ch: Char): AbstractDisplay() {
    override fun open() = print("<<")
    override fun print() = print(ch)
    override fun close() = println(">>")
}

StringDisplay 클래스



템플릿이 되는 AbstractDisplay를 상속한 서브 클래스로, 이 클래스에서는 생성자로 건네받은 캐릭터 라인을 정형해 표시하는 것 같은 구체적인 처리를 구현해 간다.
Kotlin에서 for문으로 임의의 숫자를 지정해 돌리는 경우는 for i..width 라고 지정하면 된다.

StringDisplay.java
class StringDisplay extends AbstractDisplay {
    private String str;
    private int width;
    public StringDisplay(String str) {
        this.str = str;
        this.width = str.getBytes().length;
    }
    @Override
    public void open() {
        printLine();
    }
    @Override
    public void print() {
        System.out.println("|" + str + "|");
    }
    @Override
    public void close() {
        printLine();
    }
    private void printLine() {
        System.out.print("+");
        for(int i = 0; i < width; i++) {
            System.out.print("-");
        }
        System.out.println("+");
    }
}

StringDisplay.kt
class StringDisplay(private val str: String, private  val width: Int = str.length): AbstractDisplay() {
    override fun open()= printLine()
    override fun print() = println("|" + this.str + "|")
    override fun close() = printLine()
    private fun printLine() {
        print("+")
        for (i: Int in 1..width) print("-")
        println("+")
    }
}

Main 클래스



AbstractDisplay를 상속한 서브 클래스이므로 display 메소드를 호출할 수 있으며, 출력은 구현한 각 서브 클래스에서 정의한 내용으로 출력된다.

TemplateSample.java
public class TemplateSample {
    public static void main(String[] args) {
        AbstractDisplay d1 = new CharDisplay('H');
        AbstractDisplay d2 = new StringDisplay("Hello, World");
        AbstractDisplay d3 = new StringDisplay("Hello World!!");

        d1.display();
        d2.display();
        d3.display();
    }
}

Template.kt
fun main(args: Array<String>){
    val d1: AbstractDisplay = CharDisplay('H')
    val d2: AbstractDisplay = StringDisplay("Hello, World")
    val d3: AbstractDisplay = StringDisplay("Hello World!!")

    d1.display()
    d2.display()
    d3.display()
}
<<HHHHH>>
+------------+
|Hello, World|
|Hello, World|
|Hello, World|
|Hello, World|
|Hello, World|
+------------+
+-------------+
|Hello World!!|
|Hello World!!|
|Hello World!!|
|Hello World!!|
|Hello World!!|
+-------------+

클래스 다이어그램





소감


  • 로직을 공통화할 수 있는 이점이 2개, 추상 클래스의 템플릿 메소드로 알고리즘이 정의되고 있으므로, 서브 클래스로 기술을 하지 않아도 되는 점과 만약 템플릿 메소드에 버그가 있는 경우는 템플릿 메소드 만의 수정으로 끝나므로 영향 범위가 한정되는 점을 배웠다.
  • 예를 들면 display 템플릿 메소드(execute 메소드)를 개개의 클래스로 copipe로 기술한 경우라면 모든 개개의 클래스에서 변경이 필요하게 된다고 예에서는 들었다.
  • 또한, 슈퍼 클래스형의 변수에 서브 클래스의 인스턴스의 어느 것을 대입해도 올바르게 동작한다고 하는 원칙을 The Liskov Substitution Principle(LSP)이라고 불리는 것을 배웠다.
  • Kotlin에서 for문을 기술하는 경우, for i..width 라고 지정하거나, 추상 클래스를 상속하는 경우는 ()를 붙이고, Interface를 상속하는 경우는 붙지 않는 것을 배웠다.

  • 참고



    아래를 참고로 하겠습니다. 매우 읽기 쉽고 이해하기 쉬웠습니다.

    Java 프로그래머를위한 Kotlin 입문
    역방향 Kotlin

    좋은 웹페이지 즐겨찾기