- 멤버 함수보다는 비멤버 비프렌드 함수를 자주 사용해라.
캡슐화 정도가 높아지고, 패키징 유연성도 커지며, 기능적인 확장성도 늘어난다.
멤버 함수보다는 비멤버 비프렌드 함수를 써라
멤버 함수를 사용하는 것보다 비멤버 비프렌드 함수를 사용하면 캡슐화, 패키징 유연성, 기능 확장에 대한 이점을 얻을 수 있다.
웹브라우저를 나타내는 클래스가 있다고 가정하자. 웹브라우저 클래스는 다양한 기능을 제공하는 함수들이 많을 것이다.
다운로드한 파일, 캐시를 지우는 함수, 방문 URL 기록을 없애는 함수, 시스템이 갖고 있는 쿠키를 제거하는 함수 등 이 있을 수 있다.
class WebBrowser {
public:
...
void clearCache();
void clearHistory();
void removeCookies();
...
};
이러한 기능을 한 번에 사용하고 싶은 사용자도 있을 수 있어 관련 함수를 모두 실행해주는 함수를 만들 수 있다.
class WebBrowser {
public:
...
void clearEverything(); // clearCache, clearHistory, removeCookies 호출
...
};
이러한 기능을 비멤버 함수로 제공한다면 다음과 같다.
void clearBrowser(WebBrowser& wb)
{
wb.clearCache();
wb.clearHistory();
wb.removeCookies();
}
객체 지향 법칙에 관련하여 얘기하면 데이터와 그 데이터를 기반으로 동작하는 함수는 한 데 묶여 있어야 한다고 하지만 멤버함수가 더 좋다고는 할 수 없다.
객체 지향 법칙의 핵심은 캡슐화이다.
멤버 버전인 clearEverything은 비멤버 버전인 clearBrowser보다 캡슐화 정도가 형편없다.
또한, 패키징 유연성도 낮고, 컴파일 의존도가 높아 WebBrowser도 확장하기 어렵다.
이점과 이유
어떤 것을 캡슐화하면, 외부에서 이것을 볼 수 없게 된다.
캡슐화하는 것이 늘어나면 그만큼 밖에서 볼수 있는 것들이 줄어든다.
밖에서 볼 수 있는 것들이 줄어들면, 그것들을 유연하게 변경할 수 있다. 변경이 영향을 줄 수 있는 범위가 변경된 것을 볼 수 있는 것들로 한정되기 때문이다.
즉, 어떠한 것을 보지 못하면 그것이 변경되어도 알 수 없고 문제가 되지 않는다.
그러니 보이지 않는 것들에 대한 변경이 자유롭다.
어떤 객체의 모습은 그 객체의 데이터로 설명할 수 있다. 이 데이터를 직접 볼 수 있는 코드가 적으면 적을수록 그 데이터는 많이 캡슐화된 것이고, 그 객체가 가진 데이터의 특징을 바꿀 수 있는 자유도가 그만큼 높다는 것이다.
데이터 멤버는 private 멤버이어야 좋은데, 그렇지 않으면 이 데이터에 접근할 수 있는 함수가 수도 없이 많아질 수 있기 때문이다. private 멤버는 멤버 함수와 프렌드 함수만 접근할 수 있다.
이러한 사실을 기반으로, 멤버 함수와 비멤버 함수중 어떤 것을 사용할지 생각해 보면 캡슐화가 높은 비멤버 함수가 좋은 선택이 된다. 비멤버 비프렌드 함수는 어떤 클래스의 private 멤버 부분을 접근할 수 있는 함수가 아니기 때문이다.
하지만, 주의해야 할 부분이 두 가지 있다.
- 비멤버 비프렌드 함수에만 적용되는 이야기이다.
프렌드 함수는 private 멤버에 대한 접근권한이 해당 클래스의 멤버 함수와 같기 때문에, 캡슐화에 대한 영향 역시 같다. 멤버 함수와 비멤버 함수 사이의 선택이 아니라, 멤버 함수와 비멤버 비프렌드 함수 사이의 선택이다. - 함수는 어떤 클래스의 비멤버가 되어야 한다 ≠ 그 함수는 다른 클래스의 멤버가 아니다.
위의 예제로 보면, clearBrowser는 다른 유틸리티 클래스에 정적 멤버 함수가 되어도 된다.
clearBrowser가 WebBrowser 클래스의 멤버가 아니기만 하면 된다. private 멤버의 캡슐화에 영향을 주지 않는 것이 중요한 부분이다.
더 나은 방법 (C++)
clearBrowser를 비멤버 함수로 두되, WebBrowserStuff와 같은 네임스페이스 안에 두는 것이다.
namespace WebBrowserStuff {
class WebBrowser { ... };
void clearBrowser(WebBrowser& wb);
...
}
네임스페이스는 클래스와 달리 여러 개의 소스 파일에 나뉘어 흩어질 수 있다.
WebBrowser처럼 응용도가 높은 클래스는 이런 종류의 편의 함수가 많이 생길 수 있다.
즐겨찾기 관련 함수, 인쇄 관련 함수, 쿠키 관리 함수 등이 있을 수 있다. 웬만한 사용자라면 이들 편의 함수들 중 몇 개만 관심을 둘 것이다.
즐겨찾기 기능에만 관심 있는 사용자가 다른 함수들에 대한 컴파일 의존성을 지닐 이유가 없다.
이것들을 기능별로 나눠 하나의 헤더에 선언하는 것이 좋다.
//webbrowser.h
namespace WebBrowserStuff {
class WebBrowser { ... };
... // 핵심 기능, 모든 사용자가 사용하는 비멤버 함수
}
//webbrowserbookmarks.h
namespace WebBrowserStuff {
... // 즐겨찾기
}
//webbrowsercookies.h
namespace WebBrowserStuff {
... // 쿠키
}
표준 C++ 라이브러리가 이러한 구조로 구성되어 있다.
std 네임스페이스에 속한 것들은 기능과 관련된 함수들이 수십 개의 헤더에 흩어져 선언되어 있다. 따라서, 컴파일 의존도를 낮출 수 있다.
클래스 멤버 함수를 사용하게 된다면 이러한 이점을 얻을 수 없다. 클래스는 여러 조각으로 나눌 수 없기 때문이다.
편의 함수 전체를 여러 개의 헤더 파일에 나누어 놓으면 편의 함수 집합의 확장도 쉬워진다.
해당 네임스페이스에 비멤버 비프렌드 함수를 원하는 만큼 추가해 주기만 하면 된다.
예를 들어, WebBrowser에 이미지 관련 함수를 추가하고 싶다면 헤더 파일을 새로 만든 후 WebBrowserStruff 네임스페이스에 비멤버 비프렌드 함수를 추가하면 끝이다. 클래스 멤버 함수를 선택했다면 이러한 부분도 불가능하다.
사용자가 클래스를 확장할 수 없기 때문이다. 클래스를 상속하여 구현한다고 하더라도 부모의 private 부분에 접근할 수 없다.