본문 바로가기

2편 객체지향 설계 5원칙 [SOLID] 『개방-폐쇄 원칙』

by Recstasy 2020. 3. 28.

SOLID의 2원칙은 '개방-폐쇄'원칙이다. 

좀더 구체적으로 이름을 붙이자면 "확장은 개방, 수정은 폐쇄"원칙이다. 언뜻 와닿지 않은 내용인데, 왜 객체지향 프로그램은 확장에 개방적이어야 하고, 수정에는 폐쇄적이어야 할까?


효율성


객체지향 5원칙과 20개가 넘는 디자인패턴과 같은 방법을 사용하는 궁극적인 목표는 결국 '효율성'이다. 프로그래밍은 무엇보다도 효율적이어야 한다. 만일 s/w가 효율적이지 않다면 존재할 이유가 없다. 소프트웨어가 있는 이유는 비효율적인 일을 효율적으로 처리하기 위해서다. 그래서 개방-폐쇄 원칙이 필요하다.


아웃소싱

개방-폐쇄 원칙을 한 마디로 표현하자면 '아웃소싱' 개념에 가깝다. 

에어비앤비와 힐튼 호텔을 생각해보자. 어떠한 사업이 더 효율적일까? 일단 에어비앤비, 힐튼 호텔 모두 숙박업에 속한다. 하지만 이들의 사업방식은 극과극이다. 에어비앤비는 자체적인 숙박시설을 갖추지 않고, 개인소유의 주거공간을 아웃소싱한다. 반면, 힐튼 호텔은 토지를 확보하고, 관광 및 숙박시설 허가를 받아서, 숙박시설을 건립한다. 


  



힐튼, 에어비앤비 중에서 확장이 더 쉬운 쪽은 어디일까?


사업의 성장속도를 비교해보면, 에어비앤비 쪽이 압도적으로 확장에 유리하다는 사실을 알 수 있다. 에어비앤비는 땅을 구입해서 대규모 시공 및 운영권을 확보할 필요가 없다. 그들은 주거공간을 갖고 있는 개인(호스트)과 계약서만으로도 사업을 확장할 수 있다. 이 때문에 에어비앤비는 엄청난 속도로 전통적인 숙박업의 성장속도를 따라잡았다. 


효율성

결국 또 효율성이다. 

에어비앤비만 보더라도 확장에 개방적이며, 수정에 폐쇄적인(에어비앤비는 호스트의 집을 마음대로 수정할 수 없다)' 방침이 얼마나 강력한 효율성을 발휘하는지 알 수 있다. 사실 효율성 이슈는 비단 에어비앤비의 것만이 아니다. 스포츠업계의 대표적인 아웃소싱 기업, 나이키와 의류업계의 유니클로와 같은 기업은 에어비앤비보다 훨씬 앞서 '효율성'을 극대화했다. 월마트, 다이소, 아웃소싱 AS센터 등... 주변을 둘러보면 '아웃소싱'을 통해 효율성을 이끌어 낸 사례가 즐비하다


고수와 하수의 차이, 노력하는데도 안 풀리는 경우, 부자와 빈자, 경쟁의 원리, 합격-불합격 등... 따지고보면 경쟁 원리의 본질은 결국 '효율성'으로 연결된다.



개방-폐쇄 원칙

태극권

뜬금없는 이야기일 수 있는데 개방-폐쇄 원칙은 태극권의 원리와 비슷하다. 태극권의 자세한 원리는 잘 모르지만 대략적으로 '상대의 힘을 이용한다'라는 의미에서 볼 때. 상당히 효율적인 무술이 아닐까한다. 상대의 힘을 그대로 이용한다는 의미는 효율성과 맞닿아 있기 때문이다



객체지향 2원칙은 상대의 힘을 역이용하는 느낌과 비슷하다.

자바스크립트로 작성한 아래의 예제를 살펴보자. Hilton클래스는 개방-폐쇄 원칙을 적용하지 않은 상태다.


class Hilton{     

     reservate(user){ }

     userRequest(){ }

     roomService(){ }

     interior(){ }

     realEstate(){ }

     entertain(){ }

     ....추가

}


let user = Object.freeze({

   name : '김철수',

   id : 'webdoli',

   etc : 'etc...'

})

let Hilton = new Hilton(user)


뭔가 점차 비대해지는 것이 느껴진다.


고객서비스부터 건물 인테리어 및 토지 활용방안까지 모두 Hilton클래스의 책임 범위에 있기 때문에 추가되는 기능(확장)이 증가할 때마다 메서드가 늘어나고 있다. 위의 방식을 사용하면, 기능이 추가될 때마다 Hilton클래스가 점차 무거워진다. 만일 Hilton클래스를 상속받는 클래스가 있다면, 해당 클래스는 시작부터 무거운 짐을 떠안아야 한다.


반면, 에어비앤비 방식(개방-폐쇄 원칙)은 기능이 늘어나는 것과 크게 상관없다.


class Airbnb{

    contract(host,user){

         host.service(user);

    }

}


class Host{

    service(user){

      this.userRequest(){}

      this.roomService(){}

        ...

   }

    userRequest(){ }

    roomService(){ }

       ...

}




let user = Object.freeze({

   name : '김철수',

   id : 'webdoli',

   etc : 'etc...'

})


let host1 = new Host(user);

let bnb = new Airbnb();

bnb.contract(host1,user); 


Hilton클래스에 비해 Airbnb클래스는 간단하다. contract()메서드의 인자값에 Host클래스만 계속 추가하기 때문이다. 해당 코드에서 방의 구조 변경과 같은 구체적인 사항은 Host클래스가 자체적으로 수행한다. Host클래스는 권한과 자유 그리고 책임을 갖고 있으며, 상황은 유동적이다. 자체 service()내용은 Host클래스가 알아서 관리하면 되기 때문이다.


그 결과 Airbnb클래스는 Host클래스를 추가하는 것만으로도 확장이 가능하다. 


let host2 = new Host(user);

let host3 = new Host(user);

....


bnb.contract(host2,user1);

bnb.contract(host3,user2);

....


활용

Airbnb클래스는 Host클래스를 받기만 할뿐 자신이 뭔가 기능을 구사하지 않는다. 인자로 받은 하위 클래스의 기능을 오직 이용만 한다. 그래서 서두에 태극권을 소개했다. Airbnb클래스는 똑같은 명령(.service())을 지시할 뿐, 하위 Host클래스는 각자 다른 기능을 구사하기 때문이다. 이는 마치 상대의 힘을 역이용하는 것과 유사하다. 그리고 반드시 실행해야되는 명령이 있다면, 다음과 같은 형태로 Airbnb클래스에 추가할 수 있다. 


class Airbnb{

     contract(host,user){

           host.service(user);

     }

     checkUser(){

           throw new Error('사용자 체크인아웃 기능을 반드시 넣으시오.')

     }

}


위의 Airbnb클래스를 '추상클래스'라 한다. 

추상클래스의 경우, 자바스크립트 외의 대부분 언어에서 Abstract 클래스(추상클래스)를 지원한다. 하지만 자바스크립트는 클래스 자체도 함수이며, 굳이 추상 클래스를 사용할 필요는 없을 것 같다. 따라서 빈껍데기의 Abstract클래스를 만들기보다 위와 같이 사용하더라도 상관없다. 


결론

설계를 마치고, 도메인을 정리하면서 클래스를 만들면서 기능이 늘어난다면 일단 의심해보자. 만일 공통된 개념의 기능이 있다면 하나로 엮자. 그리고 하위 클래스에서 구체적인 내용을 구현한다. 마치 아웃소싱을 한다는 개념으로 클래스를 작성하다보면, 최대한 효율적인 움직임을 만들어낼 수 있을 것이다. 




댓글

최신글 전체

이미지
제목
글쓴이
등록일