티스토리 뷰
어떻게 클래스를 설계하면 좋은지 알아보자. 또한 객체 지향 설계의 기본 개념과 원칙을 중심으로 사고하는 요령에 관해 설명하겠다.
클래스 설계의 기본
클래스 설계는 현실 세계의 업무 분담과 비슷하다. 각 클래스의 역할을 정하고 업무 책임을 분담하는 작업이라고 할 수 있다. 클래스의 이름은 담당하는 역할을 나타낸다. 1개의 클래스는 1개의 역할을 해야 한다. 객체 지향에 익숙하지 않는 사람은 자료구조와 알고리즘 중심으로 프로그램을 생각한다. 하지만 객체 지향 프로그래밍에서는 자료구조와 알고리즘은 객체를 작동시키기 위한 수단에 지나지 않는다. 클래스를 사용하는 관점에서 자료구조와 알고리즘보다는 어떤 역할과 책임이 있는지가 훤씬 중요하다.
클래스의 역할
설계 의도를 명확하게 하려면 어느 정도의 역할 분담을 정해둬야 한다. 역할에 대한 클래스 패턴을 다음과 같다.
- 작업 역할 클래스와 관리 역할 클래스
- 중재 역할 클래스
- 청구 역할 클래스
- 생성 역할 클래스
- 전용 클래스와 범용 클래스
작업 역할 클래스와 관리 역할 클래스
작업 역할 클래스는 구체적인 구현을 하는 말단 클래스이다. 1개의 역할에 대해서만 책임을 진다.
관리 역할 클래스는 작업 클래스를 제어한다. 여러개의 역할 클래스를 엮는 책임을 가진다. 실직전인 작업은 수행하지 않는다. 상황을 판단하고 작업 클래스에 지시하고 조정하는 역할을 한다. 큰 클래스를 분할하여 클래스화하여 이양하는 방법과 비슷하다.
중재 역할 클래스
중재 역할 클래스는 객체들의 조정을 전문으로 하는 담당 클래스이다. 작업 중재 역할이므로 다른 역할을 수행하면 안된다. 중재 역할을 사용하면 클래스의 결합 수를 줄이는 효과가 있다. 적절하게 중재 역할 클래스를 만들면 클래스들의 연관을 소결합으로 만들 수 있다.
창구 역할 클래스
창구 역할 클래스는 작업을 의뢰받고 실질적인 작업을 다른 클래스에 전달하는 역할을 수행한다. 중재 역할은 회사 내부에서 동료들의 조정을 담당하지만, 창구 역할은 외부 회사와의 창구 업무를 담당한다는 점에서 차이가 있다. 창구 역할도 중재 역할과 마찬가지로 결합 수를 줄이는 효과가 있다. 또한 창구 역할 클래스는 내부 조직 구조를 은폐하는 역할도 담당한다. GoF의 디자인 패턴에서 Facade패턴에 해당한다.
생성 역할 클래스
생성 역할 클래스는 객체 생성을 전문으로 수행하는 공장과 같은 클래스이다. 이렇게 하면 생성할 객체의 변경 또는 수정이 간단해 진다. 또한 클래스들의 연관을 줄이는 효과도 가져온다. GoF의 디자인 패턴에서 Abstract Factory 패턴에 해당한다. 그밖에도 객체 생성이 복잡한 과정이 필요한 경우 생성 역할 클래스를 만들어서 사용하는 것이 좋다.
전용 클래스와 범용 클래스
특정 애플리케이션에서만 사용하는 전용 클래스와 재사용을 목적으로 하는 범용 클래스는 구별해서 설계하는 것이 좋다. 어중간하게 범용 클래스를 만들거나 범용 클래스만 사용해서 코드를 작성하면 코드의 의도를 파악하기 어려워진다. 따라서 코드의 의미를 전달할 수 있도록 적당히 조절해서 설계하는 것이 중요하다. 일단 전용 클래스를 생성해서 코드의 의미를 전달하고 전용 클래스에서 범용 클래스를 간접적으로 사용하는 것이 좋다. 어쩔 수 없이 설계에 문제가 있는 클래스를 사용할 경우에도 전용 클래스를 통해 간접적으로 범용 클래스를 활용하면 어느정도 안전해집니다.
추상 인터페이스 사용 방법
추상 인터페이스 개념은 객체 지향 설계 원칙 또는 디자인 패턴을 이해하기 위해서라도 필수이다. 추상 인터페이스 활용 방법은 다음과 같다.
- 클래스 공통화
- 구현 수단 교환
- 변화하기 쉬운 부분 분리
- 부적절한 결합 관계 분리
- 패키지 경계 분리
클래스 공통화
C++ 또는 자바 등의 정적언어는 다른 형식의 클래스들을 일괄 관리할 수 없다. 하지만 공통으로 추상 인터페이스를 가지는 클래스라면, 추상 인터페이스 형식을 바탕으로 변환하여 일괄 관리할 수 있다. 부모 클래스 또는 추상 클래스를 이용해서 클래스 공통화를 구현할 수 있다.
구현 수단 교환
구현 수단을 교환하고 싶다면 추상 인터페이스를 사용한다. 특정 구현 수단에 직접 결합해버리면 구현 수단을 변경할 때마다 코드를 수정해야 한다. 사용자 측과 구현 측의 중간에 추상 인터페이스가 있다면 사용자 측 코드를 변경하지 않더라도 구현 수단을 교환할 수 있다. GoF 디자인 패턴의 Strategy 패턴이 이같은 역할을 한다.
변화하기 쉬운 부분 분리
변화하기 쉬운 부분과 어려운 부분을 서로 분리하기 위해서 추상 인터페이스를 사용한다. 변화하기 쉬운 부분을 추상 인터페이스화하면 클래스 외부로 빼낼 수 있다. 추상 인터페이스는 변화하기 쉬운 클래스가 갖도록 한다.
부적절한 결합 관계 분리
직접 결합되어서는 안 되는 클래스를 분리할 때 추상 인터페이스를 사용한다. 중재 역할 또는 창구 역할을 담당하는 추상 인터페이스를 작성하면, 클래스의 직접적 결합을 분리해서 소결합으로 만들 수 있다.
패키지 경계 분리
프로그램 규모가 커지면 관련 있는 클래스를 그룹화해서 패키지(이름 공간, 모듈)로 정리합니다. 이때 다른 패키지에 있는 클래스와 직접 결합해버리면 변경에 영향을 받을 가능성이 있다. 패키지를 독립시키고 외부 변경의 영향을 피하고자 할 때 추상 인터페이스를 사용한다. 이 내용은 의존 관계 역전의 원칙과 같다.
추상 인터페이스는 사용자 측에 배치
추상 인터페이스는 원칙적으로 사용자 측에 배치한다. 그리고 사용자 측의 내부 클래스로 만들도록 하자. 만일 내부 클래스로 만들 수 없다면 사용자의 클래스가 있는 패키지 안에 만들자. 이렇게 해야 외부와의 의존 관계를 줄일 수 있다.
추상 인터페이스는 분리와 교환을 위한 도구
추상 인터페이스는 주로 결합 관계 분리 또는 구현 교환을 할 때 사용하는 도구임을 알 수 있다. 결합 관계를 분리하면 변경으로 인항 영향 범위를 최소화할 수 있으며 구현 교환도 가능해진다. 추상 인터페이스를 사용하면 설계는 확실히 유용해 지지만, 클래스 수가 증가하여 복잡해지는 단점도 생긴다. 따라서 미래의 변화를 과도하게 예측해 추상 인터페이스를 남용하지 말자.
그 밖의 주의점 또는 테크닉
클래스를 설계할 때 주의할 점과 C++클래스의 특성을 사용한 테크닉을 소개한다.
- 생성자로 완전한 상태 만들기
- 멤버 함수 호출 순서와 관련한 대처 방법
- 초기화는 생성자, 뒤처리는 소멸자로 실행
- 구현 상세를 은폐하는 이름 붙이기
생성자로 완전한 상태 만들기
객체는 생성자가 호출되는 시점에 완전한 상태로 생성해야 한다. 별도의 초기화 함수를 호출하거나 setter를 사용해서 멤버 변수를 설정해야 작동하는 클래스는 주의해야 할 대상이다. 절차 지향적 관점에서 클래스를 설계하면 그런 클래스가 만들어 진다. 클래스는 생성자에서 모든 초기화 작업을 수행하고 소멸자에서 완전한 종료 처리를 할 수 있어야 한다.
멤버 함수 호출 순서와 관련한 대처 방법
멤버 함수의 호출 순서에 제약이 있다면 순서대로 호출할 것을 설계적으로 보장해야 한다. 호출 순서를 보장하려면 호출 순서를 제어하는 멤버 함수를 만들면 된다. 또는 호출되는 순서에 제약이 있는 클래스를 제어하는 제어 역할 클래스를 새로 만들어서 구현해도 된다.
초기화는 생성자, 뒤처리는 소멸자로 시행
초기화 처리와 종료 처리가 필요한 API 또는 클래스가 있다면, 생성자와 소멸자를 사용해서 초기화 처리와 종료 처리를 확실하게 해주어야 한다.
구현 방법을 설명하는 이름 대신 목적을 나타내는 이름을 사용하라.
클래스의 멤버 함수의 이름을 지을 때 구현 방법을 설명하는 이름으로 만들면 안된다. 예를 들어 게임 객체의 사망을 판정하는 함수를 만들 때 플래그 변수를 이용하였다고 getDeadFlag()라는 멤버 함수로 이름을 지었다. 이는 구현 방법을 이름으로 나타낸 것이다. 이후에 사망 판정 방법을 hp <= 0
으로 변경하였다면 함수 이름과 구현 방법이 일치하지 않아서 부자연스러운 구현이 된다. 하지만 멤버 함수의 목적을 나타내는 is_dead()로 함수 이름을 사용하면 구현 수단이 변경되어도 전혀 문제가 없게 된다.
'객체지향 설계 > C와 C++ 게임코드로 알아보는 코딩의 기술' 카테고리의 다른 글
클래스 설계 익히기 (0) | 2018.09.01 |
---|---|
객체 지향 설계의 원칙 (0) | 2018.08.31 |
객체 지향 설계의 기본 (0) | 2018.08.30 |
클래스를 만드는 요령 (0) | 2018.08.30 |
함수를 만드는 요령 (0) | 2018.08.30 |