[Kotlin] Inheritance(상속) vs Delagation(위임) (feat. Kotlin Delegation, Delagation Pattern)

Cropo - 박영훈·2022년 3월 27일
0

Delegation Pattern

Delegation Pattern은 객체 지향 기반의 디자인 패턴으로 상속과 같이 코드의 재사용성을 향상 시켜주기 위한 패턴입니다. Delegation은 한국어로는 ‘위임'으로 번역됩니다. 이를 통해 의미를 해석해보면 객체 A가 특정 기능을 직접 처리하지 않고 다를 객체 B에 위임하여 처리하도록 하는 것입니다.

Delegation(위임) vs Inheritance(상속)

Delegation과 Inheritance는 객체 지향에서 객체의 관계를 구성하는 대표적인 방법입니다. 각 상황에 맞춰 적절한 방법을 선택한다면 객체 지향적인 설계를 하는데 있어 많은 도움이 됩니다.

Inheritance(상속)

상속은 is a 관계를 가지며 부모 클래스의 속성들을 자식 클래스가 상속 받아서 사용할 수 있게 해줍니다.

부모 클래스에서 작성된 속성들을 물려 받기 때문에 자식 클래스에서는 해당 속성들을 정의하지 않고 사용함으로서 불필요한 중복 코드를 방지할 수 있습니다.

class Parent {
		open fun print() {
				...
		}
}

class Child : Parent() {
}

val child = Child()
child.print() // 부모 클래스에 정의된 print 메소드의 사용이 가능하다.

위 코드와 같이 중복 코드 작성 없이 사용이 가능하지만 대신 Child 클래스는 Parent 클래스와 강한 결합을 가지게 됩니다. 부모 클래스에 변경 사항이 생기게 된다면 자식 클래스에도 변경 사항에 대한 대응을 해주어야 합니다.

간단한 예시를 들면, Parent 클래스의 print() 메소드가 삭제되어야 한다면 Child 클래스에서도 print 메소드를 사용하는 모든 코드들을 수정 해주어야 하는 상황이 발생합니다.

Delegation(위임)

위임은 has a 관계를 가지며 A 클래스 내에 위임 관계를 가지고 있는 B 클래스의 인스턴스를 가지고 있는 구조입니다.

interface Sample {
    fun print()
}

class A(private val sample: Sample) : Sample {
    override fun print() {
        sample.print()
    }
}

class B : Sample {
    override fun print() {
        ...
    }
}

val b = B()
val a = A(b).print()

Delegation은 위 코드와 같이 작성 가능하다. 객체 지향의 다형성을 활용하여 Sample이라는 Interface를 생성하고 그 구현체에 print() 메소드의 동작을 정의합니다. 이를 통해 A 클래스는 print 메소드를 직접 구현한 것이 아닌 B, C 클래스에 위임하게 됩니다.

여기서 B 클래스의 타입을 인터페이스로 구현한다면 런타임 시점에서도 인터페이스에 맞는 다른 구현체로 바꿔 사용이 가능합니다. 때문에 상속에서는 컴파일 시점에 이미 결정된 관계이지만 위임은 런타임 시점에서도 변경 가능한 유동적인 관계를 가지고 있다고 볼 수 있습니다.

이러한 장점 덕분에 상속보다 위임이 더 적합한 경우가 많이 있습니다. 하지만 단점으로는 많은 언어에서 Delegation을 언어 차원에서 지원하지는 않습니다. 때문에 메소드가 추가되는 등의 변경 사항이 발생할 경우 A와 B 클래스 모두 수정되어야 하기 때문에 객체 지향 설계 SOLID 원칙 중 OCP(개방-폐쇄 원칙)을 위반하게 됩니다.

이러한 문제를 해결하기 위해 언어적 차원에서 Delegation을 지원하는 대표적인 언어로 Kotlin이 있습니다. Kotlin에서는 by 키워드를 통한 delegation을 지원합니다.

Kotlin Delegation

Kotlin에서는 by 키워드를 통해 Delegation을 지원합니다. 기존 Delegation 예제 코드를 보면 A 클래스에 override를 통해 위임한 대상의 메소드를 전부 호출하는 코드를 작성해야 했습니다. 이러한 보일러 플레이트 코드를 제거하기 위해 by 키워드를 이용할 수 있습니다.

interface Sample {
    fun print()
}

class A(sample: Sample) : Sample by sample

class B : Sample {
    override fun print() {
				...
    }
}

val b = B()
val a = A(b).print()

위 코드와 같이 by 키워드를 이용한다면 override를 통한 불필요한 코드 작성을 없앨 수 있고 또한 Sample 인터페이스의 기능이 추가되거나 변경되어도 A 클래스에 추가적인 코드 작성 없이 사용이 가능합니다. 이를 통해 OCP 원칙을 지킬 수 있습니다.

참고 링크

Kotlin Delegation

Kotlin Inheritance

Delegation Pattern

profile
Junior Developer

0개의 댓글