티스토리 뷰

Android/Kotlin

클래스 위임

취뽀가자!! 2019. 4. 28. 23:11

객체 지향에서 위임이란 클래스의 특정 기능들을 대신 처리해 주는 것을 말합니다. 코틀린은 기본적으로 상속이 허용되지 않는데, 개발을 하다 보면 상속이 불가능한 클래스도 새로운 동작을 확장하거나 수정해 활용해야 하는 경우가 있습니다. 

 

클래스를 상속하지 않고 기존 클래스의 일부 메서드를 변경하거나 새로운 기능을 확장하는 방법이 위임 입니다. 위임을 사용하는 대표적인 패턴에는 데코레이터(Decorator) 패턴이 있습니다.

 

데코레이터 패턴은 그 이름처럼 특정 클래스의 기능에 추가 기능을 덧붙이는 방법입니다. 데코레이터 패턴은 특정 클래스에 기능을 덧붙이고자 위임을 사용하는데, 자바로 이해해 봅시다.

public final class Sword{
    String name;
    
    public Sword(String name) {
        this.name = name;
    }

    public void equip(){
        System.out.println(name + " 이 장착되었습니다.");
    }
}

그런데 위 코드에 마법검 기능이 추가하고 싶어져 상속을 받아 메서드 오버라이딩을 하려고 합니다. 하지만 final 키워드 때문에 상속이 불가능하죠. 이럴 때 데코레이터 패턴을 사용할 수 있습니다. 먼저 Sword 클래스의 메서드를 전부 포함하는 인터페이스를 만듭니다.

public interface ISword {
    // 장착메세지를 출력하는 메소드
    void equip();
}

기존의 Sword 클래스가 ISword 인터페이스를 상속받도록 합니다. 여기서 ISword 인터페이스는 Sword 클래스와 메서드가 동일하기 때문에 내부 코드를 변경할 필요가 없습니다.

public final class Sword implements ISword {
    String name;

    public Sword(String name) {
        this.name = name;
    }
    
    public void equip(){
        System.out.println(name + " 이 장착되었습니다.");
    }
}

이제 Sword 클래스는 상속을 하지 않고도 기능을 확장할 수 있습니다. Interface를 이용해서 말이죠. 이제 마법검 기능을 사용할 수 있는 클래스를 만들어 보겠습니다.

// ISword 인터페이스를 상속받는다
public class MagicSwordDelegate implements ISword {
    ISword iSword;

    public MagicSwordDelegate(ISword iSword) {
        this.iSword = iSword;
    }

    @Override
    public void equip() {
        playWonderfulSound();

        // 기존 기능은 iSword 에 위임한다.
        iSword.equip();
    }

    // 확장기능
    public void playWonderfulSound() {
        System.out.println("짜잔");
    }
}

위 클래스는 ISword 인터페이스를 필드로 가지고 있습니다. 단지 Sword 클래스의 기능에 추가 기능을 덧붙이는 경우라면 Sword 타입을 필드로 가져도 상관없지만, 인터페이스를 필드로 가지면 이후 인터페이스를 상속받은 모든 클래스에 대하여 확장 기능을 사용할 수 있습니다.

 

equip() 메서드에 중요한 특징은 확장 기능을 실행한 뒤 필드로 가지고 있는 iSword 클래스의 equip()함수를 호출한다는 것입니다. 이렇게 기존에 설계된 객체에게 책임을 전달하는 것이 위임입니다. 확장 기능은 자신이 실행하고, 기존 Sword의 기능은 그대로 기존 객체의 메소드에 전달하는 방식이죠.

 

데코레이터 패턴은 이런 위임을 활용한 패턴 중 하나로, 보통 기존 기응에 추가 기능을 덧붙이는 패턴입니다. 데코레이터 패턴을 활용하면 기존 클래스를 상속받지 않은 상태로 새로운 추가 기증을 덧붙이거나 확장할 수 있습니다.

 

문제는 이런 데코레이터 패턴의 경우 단순한 경우에도 길어진다는 것입니다.

 

확장 기능 없이 ArrayList를 데코레이터 패턴으로 확장한 코드를 봐 보죠.

import android.support.annotation.NonNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class DelegatingCollection<T> implements Collection<T> {
    private ArrayList<T> innerList = new ArrayList<>();


    @Override
    public int size() {
        return 0;
    }

    @Override
    public boolean isEmpty() {
        return false;
    }

    @Override
    public boolean contains(Object o) {
        return false;
    }

    @NonNull
    @Override
    public Iterator<T> iterator() {
        return null;
    }

    @NonNull
    @Override
    public Object[] toArray() {
        return new Object[0];
    }

    @NonNull
    @Override
    public <T1> T1[] toArray(@NonNull T1[] t1s) {
        return null;
    }

    @Override
    public boolean add(T t) {
        return false;
    }

    @Override
    public boolean remove(Object o) {
        return false;
    }

    @Override
    public boolean containsAll(@NonNull Collection<?> collection) {
        return false;
    }

    @Override
    public boolean addAll(@NonNull Collection<? extends T> collection) {
        return false;
    }

    @Override
    public boolean removeAll(@NonNull Collection<?> collection) {
        return false;
    }

    @Override
    public boolean retainAll(@NonNull Collection<?> collection) {
        return false;
    }

    @Override
    public void clear() {

    }
}

단순히 확장 기능 없이 ArrayList를 위임했을 뿐인데도 코드가 굉장히 길어졌습니다. 이제 코틀린으로 ArrayList를 위임한 코드를 봐 보죠.

package com.akj.kotlinsample

class DelegatingArrayList<T>(val innerList: MutableCollection<T> = mutableListOf()) : MutableCollection<T> by innerList {

    // add 메소드는 기존의 기능에 전달받은 아이템을 로그로 출력하는 기능을 추가한다.
    override fun add(element: T): Boolean {
        // 확장 기능을 실행
        printItem(element)
        // innerList 에 기능을 위임한다.
        return innerList.add(element)
    }

    // 아이템을 프린트 하는 함수
    fun printItem(item:T){
        println(item.toString())
    }
}

위의 경우 ArrayList로 모두 위임한 것이기 때문에 코틀린은 한 줄로 코드를 작성할 수 있는데, 이게 가능한 이유는 코틀린의 by라는 키워드 때문입니다. 위의 코틀린 코드는 innerList라는 ArrayList 타입의 프로퍼티가 "Collection 인터페이스를 상속받는데, Collection 인터페이스의 기능을 innerList에 위임하겠다."는 의미입니다.

 

Java처럼 똑같이 데코레이터 패턴을 구현했지만. 코틀린 코드는 자바에 비해 훨씬 간결 명료해진 것은 코틀린이 언어적 차원에서 위임을 제공하기 때문입니다.

'Android > Kotlin' 카테고리의 다른 글

클래스 상속  (0) 2019.04.27
Getter 와 Setter  (0) 2019.04.27
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함