본문 바로가기

프로그래밍/Scala

1101 Inheritance vs. Composition

 

 

 

코드 reuse 관점에서 상속은 좀 비효율적임 + 상속을 받게 되면 가독성도 떨어지고, 코드를 잘못 이해할 가능성이 높아짐.

상속 말고 코드를 reuse하게 해주는 방법 => Composition 

 

abstract class Iter[A] {
    def getValue: Option[A] 
    def getNext: Iter[A]
}

class ListIter[A](val list: List[A]) extends Iter[A] { 
    def getValue = list.headOption
    def getNext = new ListIter(list.tail)
}

abstract class Dict[K,V] {
    def add(k: K, v: V): Dict[K,V] 
    def find(k: K): Option[V]
}

 

Q: How can we extend ListIter and implement Dict?

자바면 multiple inheretence를 하면 됨.

하지만 스칼라는 class 2개를 상속받을 수 없음 -> 둘 중 하나는 trait로 바꿔야 됨. (trait는 제약 없이 상속 가능)

 

 

// abstract class Dict[K,V] {
// def add(k: K, v: V): Dict[K,V] 
// def find(k:K):Option[V]}

trait Dict[K,V] {
def add(k: K, v: V): Dict[K,V] def find(k: K): Option[V]
}

 

class ListIterDict[K,V](eq: (K,K)=>Boolean, list: List[(K,V)]) extends ListIter[(K,V)](list) with Dict[K,V]
      
{
    def add(k:K,v:V) = new ListIterDict(eq,(k,v)::list) def find(k: K) : Option[V] = {
    def go(l: List[(K, V)]): Option[V] =  
    	l match { 
        	case Nil => None
    		case (k1, v1) :: tl =>
   			if (eq(k, k1)) Some(v1) else go(tl) 
              }
    go(list) } 
}

 

 

Muliple interface를 구현하는 기술은 매우 중요하다.

 

def sumElements[A](f: A=>Int)(xs: Iter[A]) : Int = xs.getValue match {
case None => 0
case Some(n) => f(n) + sumElements(f)(xs.getNext) }
def find3(d: Dict[Int,String]) = {
  d.find(3)
}
val d0 = new ListIterDict[Int,String]((x,y)=>x==y,Nil)
val d = d0.add(4,"four").add(3,"three")
sumElements[(Int,String)](x=>x._1)(d)
find3(d)

 

extends를 통해서 구현을 상속 받음. 

문제점은 override 등이 발생하면 가독성이 떨어짐. 

 

 

 

 

 

 

Without Using Inheritance (a.k.a  Composition)

 

class ListIterDict[K,V]
(eq: (K,K)=>Boolean, list: List[(K,V)]) extends Iter[(K,V)] with Dict[K,V]
{

val listIter = new ListIter(list)
//ListIter는 field 3개짜리 class (list, getnext, getvalue)
//상속을 받지 않고 코드 복사하고 싶은 class의 instance를 생성한다.
def getValue = listIter.getValue 
def getNext = listIter.getNext
//코드가 간결해짐. 상속받을 때의 문제점도 사라짐.
def add(k:K,v:V) = new ListIterDict(eq,(k,v)::list) 
def find(k: K) : Option[V] = {
def go(l: List[(K, V)]): Option[V] = l match { case Nil => None
case (k1, v1) :: tl =>
if (eq(k, k1)) Some(v1) else go(tl) } go(list) }
}

 

 

내가 여러 개를 상속받는데 interface만 상속 받음. 코드를 재활용하고 싶으면 상속으로 재활용하는 게 아니라 Composition으로 재활용하는 것이 합리적임.

 

 

Programming Principle 요약

: Do not repeat yourself.

: trait 중에서 구현이 전혀 없는 건 여러 개 상속받고, 나머지는 composition으로 재활용해라. 가능하면 구현을 상속받는 건 피해라.

 

 

내가 사용하고 싶은 class의 instance를 new를 이용해서 불러온다.

OOP에서 가장 이상적인 방법!!

 

굳이 상속을 받지 않아도 걔의 instance를 가지고 있으면 이용할 수 있음!

 

 

 

class A {

  a

  b

  f

  g = ... f ...

}

 

class B(arg) extends A(arg+1) {

  x

  y

  override f

}

 

보단 

---------------------------------

class A(_of) {

  a

  b

  f = _of match Some_f => _f   None => ((x) => ...)

// 들어오는 인자에 따라 f의 값을 바꿔줄 수 있음

  g = ... f ...

  h = ... f ...

}

 

class B(arg) {

  p = new A(arg+1)

  x

  y

}

 

 

Q. composion에서 override?

class A를 수정함으로써 가능함. f를 t=애초에 parameter로 받던가 이렇게

 

 

 

상속은 abstract와 코드 reuse가 동시에.

abstract를 위한 상속은 써도 좋음.

코드를 재활용하는 상속은 지양.

 

 

Composition은 abstract와 reuse를 분리.

상속이 불가피하게 필요하면 사용은 하되, composition을 하면 explicit한 코드를 구현할 수 있게 됨.

 

interface는 클라이언트가 필요한 것들만. 

 

+++ 이부분 새로 슬라이드 추가될 것. (나중에 확인하기)

 

 

 

 

 

Mixin with Traits (**중요)

-> Mixin의 대표적인 예 : Stacking

 

 

 

 

 

 

 

 

 

 

 

 

intersection Types

 

traitA{val a:Int=0} 
traitB{val b:Int=0} 

class C extends A with B {
 override val a = 10 
 verride val b = 20 
 val c = 30
}

val x = newC 
val y: A with B = x 

y.a // 10
y.b // 20
y.c // type error

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'프로그래밍 > Scala' 카테고리의 다른 글

Scala String 문법  (0) 2022.11.22
1103 Mixin, Stacking with Traits  (0) 2022.11.03
Structure Type  (0) 2022.10.21
Currying & Closure 완전 이해하기  (0) 2022.10.21
PART 1 - Currying (0926)  (0) 2022.10.20