좋은 클래스 설계를 위해서 신경써야하는 부분들에 대해서 정리
- 새로 정의한 타입의 객체 생성 및 소멸은 어떻게 진행할 것인가?
→ 이 부분을 통해서 클래스의 생성자 및 소멸자의 설계가 변경된다.
또한 메모리 할당 함수(new, delete) 를 직접 작성할 경우에는 이들 함수의 설계에도 영향을 준다.
- 객체 초기화는 객체 대입과 어떻게 달라야 할 것인가?
→ 생성자와 대입 연산자의 동작 및 둘 사이의 차이점을 결정짓는 요소이다.
- 새로운 타입으로 만들 객체가 값에 의해 전달되는 경우에 어떤 의미를 둘 것 인가?
→ 어떤 타입에 대해서 값에 의한 전달을 구현하는 쪽은 복사 생성자인데, 해당 부분에 대한 설계를 어떻게 했느냐는 의미이다.
- 새로운 타입이 가질 수 있는 적법한 값에 대한 제약은 무엇으로 잡을 것인가?
→ 특정 클래스의 데이터 멤버는 몇 가지 값 조합에만 유효하거나, 반대로 특정 값에 대해서는 유효하지 않을 수 있다.
이러한 특징을 클래스의 불변속성 ( invariant ) 이라고 하는데, 클래스 차원에서 일관성 있게 지켜주어야 한다.
class Rational
{
public:
Rational(int n = 0, int d = 1);
~Rational();
public:
void setDenominator(int d)
{
d == 0 ? error_msg("zero divide"): denominator = d;
}
private:
int numerator, denominator;
};
- 예를 들어서 위의 코드에서 denominator 멤버 변수는 결코 0이 될 수 없으며, 그것이 클래스 설계자의 의도이다.
생성자, 대입 연산자, 각종 Write 관련 함수 ( setter ) 는 불변속성에 많이 좌우되는 편이다. ( + 예외 처리 관련 )
- 기존의 클래스 상속 계통망에 맞출 것인가?
→ 이미 있는 클래스로부터 상속시킨다고 하면, 당연히 해당 부모 클래스에 의해 제약을 받게 된다.
특히나 멤버 함수의 가상, 비가상 여부는 가장 큰 요인이다.
반대로 해당 클래스를 다른 클래스의 상속 시킬 목적으로 (즉, 부모 클래스로서 만든다면 ) 설계한다면 어떤 함수를 가상화 할지도 중요한 결정 요인이 된다.
- 어떤 종류의 타입 변환을 허용할 것 인가?
→ 다른 타입과의 변환을 허용할 건 인가에 대한 고민도 필요하다.
만약 암시적(implicitly) 변환을 허용한다면 operator T2 와 같은 함수를 따로 준비하던가, 비명시 호출 생성자를 T2의 멤버로 만들어야 한다.
만약 명시적(explicit) 타입 변환만 허용하고 싶다면, 해당 변환을 맡는 별도 이름의 함수를 만들되 타입 변환 연산자 ( 인자 하나로 호출되는 ) 혹은 비명시호출 생성자는 만들지 말아야 한다.
- 어떤 연산자와 함수를 두어야 의미가 있을까?
→ 클래스 멤버 함수의 목적이 여기서 결정된다.
- 새로운 타입의 멤버에 대한 접근 권한을 어느 쪽에 줄 것인가?
→ 어떤 클래스 멤버를 public, protected, private 영역에 둘 것인가를 결정하는 질문이다.
또한 프렌드 함수 및 클래스, 중첩 클래스에 대한 결정에도 영향을 준다.
- 선언되지 않은 인터페이스로 무엇을 둘 것인가?
→ 새로 만들 타입이 제공할 보장이 어떤 종류인지에 대한 질문이다.
보장하겠다고 선택할 수 있는 후보는 수행 성능 및 예외 안전성, 자원 사용이 있다.
이것들에 대해서 보장을 하겠다는 결정을 하면 거기에 맞는 제약이 같이 결정된다.
- 새로 만들 타입이 얼마나 일반적인가?
→ 단순히 다른 클래스 하나가 아니라 동일 계열의 타입군 전체를 정의해야 하는 상황이라면,
새로운 클래스가 아닌 새로운 클래스 템플릿을 정의해야 한다.
- 정말로 필요한 타입인가?
→ 단순히 비멤버 함수를 새롭게 정의하거나, 템플릿을 몇 개 더 정의해서 해결된다면 새 클래스를 정의하는 것은 낭비이다.
Things to Remember
- 클래스 설계는 타입 설계와 마찬가지이다. 새로운 타입을 정의하기 전에, 위의 고려 사항들을 고민해보자.
댓글 영역