상속과 컴포지션 선택 기준

 

개체지향 설계 생각에 대한 배운점 정리

 

프로젝트를 진행하면서 생각을 하게 된 생각이 있다.

 

여러가지 프래그먼트들이 존재하지만 팀원이 만든 DialogFragment 클래스를 상속을 통해 다른 프래그먼트에서 재활용해야 할지 컴포지션을 사용해야 할지 고민이 되었기 때문이다. 

이럴때 상속을 사용해야 할지 컴포지션을 사용해야 할지 궁금하게 되었고, 개체지향을 배운것을 공유하고자 한다. 

 

 

첫번째 : 메모리 구조

 

상속에서의 메모리 구조

open class Parent(private val name: String, private val age: String) {
    fun print() {
        println(name + age)
    }
}

class Child(private val name: String, private val age:String, private val action: String): Parent(name, age) {
    fun printAction() {
        println(action)
    }
}

위와 같이 상속을 사용하면 메모리 구조는 어떻게 될까? 

상속받은 클래스의 메모리는 일렬로 정렬되어 있음을 알 수 있다. 즉, 상속을 받게 되면 한번에 메모리에 올라가게 되는것이다. 

 

Memory name: minsu (1027) age: 24 (1028) action: eating (1029)

컴포지션에서의 메모리 구조

class Delegation(
    private val title: String,
    private val body: String,
    private val derived: Derived = Derived("todo")
) {
    fun printf() {
        println(title + body)
    }
}

class Derived(private val initial: String) {
    fun print() {
        println(initial)
    }
}

컴포지션에서의 메모리 구조는 아래와 같다.

Class: Derived(1032) pointer (1033) ????? title: josh (1034) body: test (1035)
(1036) ????? initial: todo (1037) (1038) ????? (1039) ?????

위처럼 컴포지션으로 개체를 생성하게 되면 멤버 클래스가 생성되어있는 메모리 주소를 포인터로 들고 있게 되어 메모리가 여러곳에 혼재되는 상황이 발생한다.

 

 

 

두번째 : 다형성

다형성(polymorphism)이란?

다형성(polymorphism)이란 하나의 객체가 여러 가지 타입을 가질 수 있는 것을 의미합니다. 자바에서는 이러한 다형성을 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록 하여 구현하고 있습니다.

다형성은 상속, 추상화와 더불어 객체 지향 프로그래밍을 구성하는 중요한 특징 중 하나입니다.

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

fun main() {

val animal = listOf(Dog(), Cat())
    animal.forEach {
        it.shout()
    }
}

open class Animal() {
    open fun shout() {
        println()
    }
}

class Dog() : Animal() {
    override fun shout() {
        println("bark")
    }
}

class Cat() : Animal() {
    override fun shout() {
        println("meow")
    }
}

실행 결과

다형성을 이용하려면 상속을 이용할 수 밖에 없기 때문에, 상속을 강제 할 수 밖에 없게 된다.

 

 

세번째 : 유지보수

 

첫번째. 클래스 작성시

클래스 작성시 컴포지션 : 컴포지션 클래스의 함수를 실행시키기 위한 다른 함수가 필요함 (derived를 캡슐화 시킬경우)

여기에서는 Derived 클래스를 임시로 초기화해주었지만, Derived 클래스의 변수를 초기화 해주는 setter 등 여러가지 메서드가 Delegation 클래스에 필요하게 될것이다.

다른 클래스에게 책임을 위임시키고 있지만, 프래그래머에게 어떠한 멤버변수들을 set, get 함수를 작성할지 혹은 어떤 값을 받아야 할지 강제 할 수 없는 부분에 있어서 주의가 필요할 수 있다. 

 

클래스 작성시 상속 : 부모의 함수를 실행시키기 위해서 다시 작성해줄 필요가 없음

컴포지션과 다르게 확실히 코드가 줄어들었고 Delegation 클래스가 Derived 클래스의 인스턴스가 printOrigin코드를 실행시킬 수 있다.

 

또한 상속시 open키워드 혹은 abstract 키워드로 작성해야할 메서드를 강제할 수 있기 때문에 상속을 사용하면 실수를 줄일 수 있는 확률이 올라가게 된다. 

 

아키텍처 관점

 

중간에 ContextWrapper에서 필요한 메서드를 고쳐야 한다면? 아래 상속받은 클래스에서 다시 또 많은 수정을 해주어야 한다.

 

하지만 컴포지션을 사용한다면?

 

상속과 비슷한 문제가 존재하겠지만, 상속보다는 유지보수 문제가 덜할것이다. 상속보다 조립성을 더욱 강조했기 때문이다.

 

 

네번째 : 일반적인 경우

 

일반적으로 상속과 컴포지션의 관계를 배울때 사용하는 개념을 이용하기. 

 

IS-A HAS-A
상속의 개념이다. 구성의 개념이다.
클래스는 둘 이상의 클래스를 확장할 수 없습니다. 클래스는 여러 다른 클래스와 HAS-A 관계를 가질 수 있습니다.
상속은 정적 바인딩이며 런타임에 변경할 수 없습니다. 컴포지션은 동적 바인딩이며 변경 사항에 유연합니다.
   

 

 

결론

상황에 맞는 방법을 적용해 보는것이 좋을것 같고, 이를 적용하는 기준을 적절히 사용해보자.

728x90
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기