[C++] 누구나 쉽게, 리팩토링(클린코드)-② 인터페이스

반응형

 

리팩토링

 

리팩토링은 내부의 코드를 개선하는 하나의 방법론이자 솔루션을 뜻합니다. 

 

리팩토링을 한마디로 말하자면, 나쁜 냄새가 나는 코드를 최적화하는 것입니다. 여기서 주의할 점은 런타임 시의 최적화가 아니라 프로그래밍을 할 때 버그 없는 프로그램을 만드는 것입니다외부 인터페이스는 그대로 두고 내부의 코드만을 개선하여 재사용과 가독성을 높이는 데 목적이 있습니다.

 

 

 

 

이번시간에는 리팩토링을 진행하면서 인터페이스까지 함께 알아보겠습니다

 


2. 인터페이스

 

문제발생

인터페이스 설명을 위해 이번에는 핸드폰 예제를 이용합니다.

 

Anycall 클래스의 call 함수, Person 클래스의 use_phone 함수 구현

 

삼성의 자랑 애니콜(Anycall) 핸드폰의 통화(call)기능을 구현합니다. 사람(Person)은 use_phone()을 통해 통화 기능을 사용합니다. 

 

 

cyon 클래스

 

싸이언(Cyon) 폰을 하나 더 구매했다고 합시다. 싸이언은 통화 기능을 애니콜과 달리 send 함수를 사용합니다. 역시 통화를 하기위해서 Person 클래스에 싸이언용 use_phone()을 구현합니다.

 

이때 애니콜과 싸이언은 다른 타입으로, 다른 타입의 포인터끼리는 호환이 되지 않기 때문에 void use_phone(Cyon *p, const char* n) { p -> send(n); } 을 하나 더 만들어주어야 합니다.

 

싸이언 폰을 하나 더 사용하므로 메인함수는 아래와 같이 싸이언 클래스를 정의하고 함수를 호출하는 코드가 추가됩니다. 

 

int main() 
{
    Person p;

    Anycall a;
    p.use_phone(&a, "000-0000-0000");

    // 새롭게 추가된 Cyon 클래스 사용
    Cyon c;
    p.use_phone(&c, "000-1111-1111");
}

 

그러나 이렇게 구현하게 되면 OCP에 위배됩니다.

 

 

♥ OCP(Open Close Principle, 개방폐쇄원칙)

개방-폐쇄 원칙은 시스템의 구조를 올바르게 재조직(리팩토링)하여 나중에 이와 같은 유형의 변경이 더 이상의 수정을 유발하지 않도록 하는 것이다. 개방-폐쇄 원칙이 잘 적용되면, 기능을 추가하거나 변경해야 할 때 이미 제대로 동작하고 있던 원래 코드를 변경하지 않아도, 기존의 코드에 새로운 코드를 추가함으로써 기능의 추가나 변경이 가능하다.  [출처-위키피디아]

 

 

이는 강한 결합Tightly Coupling 되어 있기 때문입니다. 다른 기종의 핸드폰을 사용한다면 핸드폰 클래스를 또 추가해주고, Person 클래스에는 타입에 맞게 use_phone을 구현해주어야하는 번거로움이 생기게 됩니다. 

 

 

문제 해결

그렇다면 OCP원칙에 위배되지 않고, 핸드폰 하나가 더 추가되더라도 내부 코드는 변화 없이 구현할 수 있을까요?

정답은, 느슨한 결합Loosely Coupling을 만들면 됩니다! 

 

모든 기종의 핸드폰을 같은 이름의 통화 기능(call)으로 반드시 구현하도록 강제화합니다.

이전에 배웠듯이 추상 클래스를 도입하면 되겠지요?

 

 

우선 애니콜과 사이언 모두 "Phone 클래스"라는 상위 클래스를 상속받아 call 기능을 무조건적으로 구현하도록 합니다.

 

class Phone 
{ 
public :
   // 순수 가상 함수는 자식에게 특정 기능을 강제화하기 위해 사용
   virtual void call(const char *n) = 0;
};

 

 여기서 인터페이스 개념이 등장합니다. 인터페이스는 서로 다른 클래스를 연결하는 연결장치라고 볼 수 있습니다. 여기서는 Anycall, Cyon 클래스를 Person 클래스와 연결하는 Phone 클래스를 인터페이스라고 할 수 있습니다.

 

 

 

그러나 97번째 줄의 Person클래스에서 보면 use_phone 함수의 첫 번째 파라미터가 다르기 때문에 (Anycall, Cyon 포인터 타입이 다르기 때문에) 2개의 함수를 재정의하게 됩니다.

 

 

♥ 상속을 받았으니 부모타입 * = &자식객체; 가 가능하게 됩니다. 내부적으로 부모의 코드를 가지고 있기 때문에 업캐스팅Upcasting이 가능합니다.

 

따라서 애니콜과 사이언의 부모 클래스인 Phone 클래스를 이용하여 void use_phone(Phone* p, const char* n) { p -> call(n); } 라고 구현할 수 있게 됩니다. 와우!

 

 

 

 

내부의 코드는 변화 없이  스카이(Sky) 핸드폰도 추가 할 수 있습니다.

 

 

 

172~176줄에  SKY 클래스를 추가했지만 다른 코드는 변함이 없이 사용될 수 있습니다. 이렇게 인터페이스를 사용하면 코드 수정을 국지화할 수 있고 폰클래스와 사람클래스 모두 인터페이스에만 맞춰서 개발하면 되므로 개발 향상성이 2배 빨라진다고 할 수 있습니다.

 

 

 

정리

o 인터페이스는 서로 다른 클래스를 연결하는 연결장치라고 볼 수 있습니다. 
o 인터페이스를 사용하면 코드 수정을 국지화할 수 있고, 개발 향상성을 높일 수 있습니다.

 

 

 

* 자바에서의 인터페이스

자바에서는 인터페이스를 알아보기 위해 interface 키워드를 사용하는데, interface 키워드가 없는 C++에서는 관례적으로 심볼 I를 클래스 이름 앞에 붙여줍니다. 

 

class IPhone 
{
public :
    virtual void call(const char *n) = 0;
};

아이폰이 되어버렸네요(ㅋㅋㅋㅋㅋㅋ)

 

추가로 인터페이스를 나타내기 위해 I를  그냥 쓰기보다 define을 사용하여 클래스를 인터페이스로 정의해주고 사용하면 가독성 높은 클린 코드가 됩니다.

#define interface class 	// 클래스를 인터페이스처럼 사용. 가독성을 위해서

interface Phone
{
public :
    virtual void call(const char *n) = 0;
};

  끝.

반응형
그리드형

댓글

❤️김세인트가 사랑으로 키웁니다❤️