Composition over inheritance

우선 동영상 하나 보고 가실게요.

What's wrong with inheritance?

Object Oriented Programming (이하 OOP) 에서 polymorphic (다형성) 을 구현하기 위해서 사용되는 방법 중 가장 대표적인 것이 아마 inheritance 일 것이다. 하지만, 지속적으로 inheritance 의 문제점이 지적되어 왔다. 대표적인 것이 이른바 고릴라 바나나 역설 이다.

the entire jungle 이 아주 찰지다

I think the lack of reusability comes in object-oriented languages, not functional languages. Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.

If you have referentially transparent code, if you have pure functions — all the data comes in its input arguments and everything goes out and leave no state behind — it’s incredibly reusable.

Joe Armstrong from Coders at Work

Erlang 을 만든 Joe Armstrong 의 이 문장은 상속으로 인해서 불필요한 메소드의 복제(?)가 많이 일어나고, 이에 따른 비용 혹은 부작용이 발생한다고 정리할 수 있을 것 같다. 다음 예제를 보자.

class Animal {  
  constructor(name) {
    this.name = name
  }

  walk() {
    console.log('I can walk')
  }
}

class Human extends Animal {  
  this.talk = () => console.log('I can even talk! My name is', this.name)
}

이런 샘플 코드만 봤을때는 상당히 직관적이고 당연한 것이라 볼 수 있지만, 어디 실제 코딩 상황에서 이런가?

class Title extends Component {  
  render() {
    <h1>{this.props.label}</h1>
  }
}

const Title = (props) => {  
  const { label } = props

  return (
    <h1>{label}</h1>
  )
}

React 의 경우 일반적으로는 컴포넌트를 두가지 방식1으로 선언하게 된다. 위의 Component 를 extends 하면 componentDidMount 같은 life cycle method 나 this.setState 와 같은 상태 함수를 사용할 수 있다. 두번째는 보통 Stateless Component 라고 불리는데 컴포넌트 내부에서 상태 변화를 감지해야 할 필요가 없을때 주로 쓰인다.2

상태 변화를 체크해야할 필요가 없는 DOM object 에 굳이 추가적인 메소드를 넣어 메모리를 낭비해야할 이유가 별로 없지 않은가? 특히나 여러 부모를 가진 자식 class 인 경우 실제 구조가 어떻게 되어있는지 트래킹 하는 것 조차 큰 일이 될 것이다.

Dawn of Composition

최근 그래서 대두되고 있는 개념이 composition 이다. 특히나 Composition over inheritance 라는 위키피디아 페이지까지 생길 정도로 최근 디자인 패턴의 주류 라고 생각해도 좋을 것 같다.

지난번 포스트에서 작성했듯이 Javascript (이하 JS) 는 prototype 이라는 독특한 방식으로 OOP 를 구현했기 때문에, object 생성 방식에서 부터 다른 언어에 비해 복잡한 것이 사실이다. 또한 JS 는 객체 내부에 private property 를 가질 수가 없기 때문에, 부작용이 발생할 여지도 커 inheritance 를 추천하지 않는다.

위에서 설명한 Animal 과 Human 을 composition 형태로 다시 작성해보자.

const walker = () => {  
  return {
    walk: () => console.log('I can walk')
  }
}

const talker = (state) => {  
  return {
    talk: () => console.log('I can even talk! My name is', state.name),
  }
}

const animal = (name) => {  
  const state = {
    name,
  }

  return Object.assign(
    {},
    walker(),
  )
}

const human = (name) => {  
  const state = {
    name,
  }

  return Object.assign(
    {},
    walker(),
    talker(state),
  )
}

human('Seokjun').talk()

> I can even talk! My name is – "Seokjun"

코드가 좀 길어진 것 같지만 Object.assign 이 어떻게 동작한다는지 안다는 가정 하에 상당히 가독성이 좋고 추가적으로 JS의 핵심적인 특성인 Closure 를 사용해서 Functional Programming 을 효과적으로 구현할 수 있다.

매번 작성하기가 번거로우니, Array.reduce 를 활용해서 composer 함수를 만들 수도 있다.

const composer = (state, ...funcs) => {  
  return funcs.reduce((obj, func) => Object.assign(obj, func(state)), {})  
}

const animal = (name) => composer({ name }, walker)  
const human = (name) => composer({ name }, walker, talker)

human('Seokjun').talk()

> I can even talk! My name is – "Seokjun"

Composition over inheritance

Do not use inheritance

이쯤 읽었으면 Composition 의 장점이 확실하게 드러났을 것이라고 생각한다. 그렇다면 언제 Composition 을 사용해야 할까? 많은 개발자들이 이 질문에 항상 이라고 대답하고 있다. Pure Function 으로 작성한 walker, talker 는 부작용도 없고, 테스트 코드를 작성하기에도 좋다. 모든 상속된 객체가 같은 특성을 공유할 이유도 그럴 필요도 없다. javascript 에서 구현이 안되는 private property 를 객체 지향 하에 손쉽게 구현할 수 있다.

뭔가 더 글을 남겨야 될것 같은 기분이 들지만 여기서 줄인다. composition 이라는게 그만큼 간단한 것이니까.


  1. 실제로는 PureComponent 나 React.createClass 등 몇가지 방법이 더 있다.

  2. 하지만 Stateless Component 역시 실제로는 똑같이 React.Component 를 extends 한 것으로 트랜스파일링 된다.

Seokjun Kim

Read more posts by this author.

Subscribe to Make It Yourself

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!
comments powered by Disqus