새로운 관점으로 보는 객체지향 설계
객체지향 설계를 이야기할 때 우리는 주로 SOLID 원칙, 책임, 협력, 캡슐화, 유연성, 높은 응집도, 느슨한 결합도 등의 키워드를 먼저 떠올립니다. 하지만 오늘은 이러한 전통적인 접근 대신, 변화라는 관점에서 객체지향 설계를 살펴보고자 합니다.
소프트웨어 변화의 필요성
사용자 중심의 변화
소프트웨어는 결국 사용자의 관점에서 가치판단이 이루어집니다. 사용자의 상황이나 생각이 변화하면 소프트웨어의 사용성과 요구조건도 자연스럽게 변화하게 됩니다. 이러한 변화는 다음과 같은 과정을 통해 발견됩니다:
- 사용성 분석
- 요구사항 분석
- 사용자 피드백
- 시장 환경 변화
변화 대응의 중요성
빠르고 정확한 변화 대응은 소프트웨어의 가치를 유지하는 핵심 요소입니다. 이를 위해서는 다음 세 가지 조건이 충족되어야 합니다:
- 변화 예측: 미리 변화 가능성을 파악
- 변화 집중화: 변화가 필요한 지점을 한 곳으로 모음
- 신속한 변경: 실제 변화 발생 시 빠른 대응
도메인 이해하기: 게임 플레이어 유형 분석
온라인 게임의 예시를 통해 도메인 분석과 변화 대응을 살펴보겠습니다. 게임에서는 크게 네 가지 유형의 플레이어가 존재합니다:
-
킬러(Killers): Acting과 Players 축의 교차점에 위치
- 다른 플레이어와의 상호작용을 통한 경쟁 선호
- 주로 PvP 콘텐츠에 집중
- 성취 동기가 경쟁을 통해 발현
-
달성가들(Achievers): Acting과 World 축의 교차점에 위치
- 게임 세계 내에서의 목표 달성에 집중
- 레벨, 아이템, 업적 등에 관심
- 게임 시스템과의 상호작용이 주요 동기
-
체팅족들(Socializers): Interacting과 Players 축의 교차점에 위치
- 다른 플레이어와의 사회적 교류 중시
- 커뮤니티 활동과 소통에 집중
- 관계 형성이 주요 목적
-
모험가들(Explorers): Interacting과 World 축의 교차점에 위치
- 게임 세계 탐험과 발견에 흥미
- 숨겨진 콘텐츠 탐색 선호
- 게임 세계에 대한 호기심이 주요 동기
이러한 플레이어 유형 분석은 게임 시스템 설계의 기초가 되며, 각 유형별 요구사항과 변화에 대응할 수 있는 구조가 필요합니다.
구조적 변화의 시각화
기존 구조의 문제점
기존의 구조는 다음과 같은 특징을 가집니다:
// 기존의 깊은 계층 구조
return getAllUsers()
// getAllUsers()
// _getKillers()
// __getPlayerActingScore()
// _getAchievers()
// __getWorldActingScore()
// _getSocializers()
// __getPlayerInteractionScore()
// _getExplorers()
// __getWorldInteractionScore()
- 깊은 계층 구조로 인한 높은 결합도
- 변경 시 여러 계층을 거쳐야 하는 복잡성
- 파란색 박스로 표시된 부분이 실제 변경이 필요한 영역임에도, 전체 구조를 이해해야 접근 가능
개선된 구조의 이점
새로운 구조에서는 다음과 같은 개선점을 제공합니다:
// 선언부 - 변화 지점을 상위로 이동
const userGenerator = new UserGenerator();
const killerUserPolicy = new UserPolicy(
new IsKillerCondition(),
new PlayerActingScoreDefiner(
new PlayerInteractionScoreDefiner()
)
);
userGenerator.addPolicy(killerUserPolicy);
// 실행부 - 단순화된 인터페이스
return userGenerator.generate();
- 단순화된 계층 구조
- 변경 지점의 명확한 분리
- 정책 기반의 유연한 확장성
- 최상위 레벨에서의 직접적인 변경 포인트 제공
구조 개선에 따른 트레이드오프
의존성 역전 원칙(DIP)의 적용
개선된 구조에서 눈에 띄는 가장 큰 변화는 의존성 방향의 역전입니다:
-
추상화를 통한 의존성 역전
- UserPolicy 인터페이스 도입
- ScoreDefiner 추상화 계층 추가
- Condition 인터페이스를 통한 판단 로직 분리
-
고수준 모듈의 독립성 확보
- UserGenerator는 구체적인 구현에 의존하지 않음
- Policy를 통한 유연한 확장 가능
- 새로운 요구사항에 대한 빠른 대응 가능
증가하는 의존성(검은선)에 대한 고찰
늘어난 의존성의 영향:
-
코드 복잡도 증가
- 더 많은 인터페이스와 클래스 생성 필요
- 의존성 주입 설정의 복잡도 증가
- 전체 시스템 구조의 이해도 요구 증가
-
런타임 구조와 컴파일타임 구조의 차이
- 컴파일타임: 더 많은 의존성 관계
- 런타임: 유연한 객체 조합 가능
- 디버깅 과정의 복잡도 증가
-
개발자 학습 곡선
- 새로운 개발자의 진입 장벽 상승
- 구조 이해를 위한 문서화 필요성 증가
- 팀 내 지식 공유의 중요성 증가
트레이드오프 판단 기준
이러한 구조 변경의 채택 여부는 다음 기준으로 판단할 수 있습니다:
-
프로젝트 특성
- 변화의 빈도와 규모
- 팀의 기술적 성숙도
- 유지보수 기간
-
비즈니스 요구사항
- 시장 출시 시급성
- 확장 가능성
- 품질 요구사항
-
팀 상황
- 팀 규모와 구성
- 기술 스택 친숙도
- 유지보수 담당 인력
실용적 접근 방안
모든 변화 지점에 DIP를 적용하는 것이 아닌, 다음과 같은 선별적 적용을 추천합니다:
-
높은 변화 가능성 영역
- 비즈니스 규칙이 자주 변경되는 부분
- 다양한 구현이 예상되는 기능
- 외부 시스템과의 통합 지점
-
안정적인 영역
- 단순 CRUD 작업
- 시스템 유틸리티
- 잘 정립된 표준 기능
이러한 균형잡힌 접근은 개발 생산성과 유지보수성 사이의 최적점을 찾는데 도움이 됩니다.
구조 개선의 효과
개선된 구조의 장단점:
장점:
- 변화 지점까지의 접근성 향상
- 수정 범위의 명확한 한정
- 재사용성 증가
- 새로운 플레이어 유형 추가가 용이
단점:
- 전체 코드량 증가
- 모듈 간 관계의 복잡성 증가
- 개발자 간 커뮤니케이션 비용 상승
- 초기 설계 시간 증가
실무적 적용과 교훈
객체지향 설계를 변화라는 관점에서 바라보면 다음과 같은 이점이 있습니다:
- 비개발자와의 효과적인 커뮤니케이션 가능
- 오버엔지니어링 방지
- 실질적인 비즈니스 가치 창출
균형 잡힌 접근
성공적인 설계를 위해서는 다음 요소들의 균형이 필요합니다:
- 비즈니스 도메인 이해
- 효과적인 커뮤니케이션
- 기술적 역량
- 적절한 트레이드오프 판단
결론
객체지향 설계는 단순한 기술적 스킬이 아닌, 변화 관리의 도구로 보아야 합니다. 본 글에서 살펴본 게임 플레이어 유형 분석 사례처럼, 도메인에 대한 깊은 이해를 바탕으로 변화에 유연하게 대응할 수 있는 구조를 만드는 것이 중요합니다. 이는 비즈니스의 지속적인 성장과 발전을 위한 핵심 요소이며, 개발팀과 비즈니스팀 모두가 이해하고 공유할 수 있는 공통의 언어가 될 수 있습니다.