게임톤 2주차가 끝나가고 있습니다. 슬슬 어설프게나마 돌아가는 게임이 나와야 하겠죠…

하지만 저는 개인적인 욕심도 있고 해서, 게임을 빠르게 만들기보다는 완전하게 만들고 싶더군요. 뭐, 그렇다고해서 얼마나 이루어질지는 모르겠지만요.

뭔가 엄청나게 멋진 기능을 넣는 것도 중요하지만, 규모가 점점 커져감에 따라 코드의 유지, 관리가 중요해질 테니 그것도 고려해야겠죠. 포트폴리오도 탄탄하게 갖춰볼 겸 해서, 객체지향의 컨셉을 코드에 녹여내보고자 했습니다.

단일 책임 원칙

하나의 객체는 하나의 책임만을 갖는다.

예전 글에서, 플레이어의 State를 Interface를 구현하는 방식으로 잘게 쪼갠 것을 보셨나요? 아무리 원버튼 방식의 하이퍼캐주얼 장르라 하더라도, 플레이어가 갖게 될 상태는 수없이 많아질 수 있습니다. 이럴 때 플레이어의 Update메소드 안에서 모든 상태에서의 조작 반응 로직을 집어넣는다면, 플레이어 클래스는 너무 많은 동작에 대한 책임을 지게 됩니다.

매 프레임 Update안에서 플레이어가 착지한 상태인지 확인하고, 공중에 떠있다면 언제 착지할지, 더블 점프를 했는지 확인하고… nested if-else로 이루어진 수십줄의 코드는 가독성도 좋지 않고, 나중에 문제가 생겨도 어딜 고쳐야할지 막막할 겁니다.

이를 해결하기 위해, IState 인터페이스에서 업데이트와 조작 처리의 구현을 강제하고, 각 상태에 대한 State 클래스를 만들어줍니다. Run State에서는 오직 달리는 상태에만 신경쓰면 됩니다. Jump State에서는 오직 공중에 뜬 상태만 책임 집니다. 이게 단일 책임 원칙에 부합하는 내용인지는 잘 모르겠지만… 적어도 제가 이해하기로는 그렇습니다.

개방 폐쇄 원칙

확장에는 열려있어야 하고, 변경에는 닫혀있어야 한다.

레벨 디자인 툴을 설계하면서, 가장 크게 고민하게 된 부분은 다양한 종류의 오브젝트를 어떻게 씬에서 생성하게 만들까였습니다. 일단 오브젝트를 씬에 놔야하니까, x, y 좌표값은 모두 갖고 있을 테고, 나무는 높이나 굵기, 가지 개수를 변수로 받겠죠. 아이템은 점수를, 장애물은 피해량 같은 걸 변수로 받을 겁니다. 지금까지 만들어진 레벨 디자인 툴은 오직 나무만 배치할 수 있습니다. 이 클래스 안에는 공통 변수 외에 나무를 위한 변수까지 함께 들어가 있죠. 이걸 공통 변수만 뽑아낼 수는 없을까요. 아직 개방 폐쇄 원칙의 컨셉을 살린 부분은 없지만, 레벨 디자인 툴에서 제대로 보여줄 수 있다면 좋겠습니다.

리스코프 치환 원칙

상위 타입 객체를 하위 타입 객체로 치환해도 동작에 문제가 없어야 한다.

아직 프로젝트에서 상속을 제대로 활용하지는 않았기 때문에, 이 원칙 또한 살린 부분은 없습니다. 다만, 레벨디자인 툴에서 아이템 특성에 따라 상속받은 클래스를 계속 만들 예정이니, 여기서 보여야겠군요.

인터페이스 분리 원칙

“클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안된다.” - 로버트 C. 마틴

하나의 일반적인 인터페이스보다는, 구체적인 여러 개의 인터페이스가 낫다는 원칙. 예를 들어봅시다. 본 프로젝트는 현재 플레이어 상태처럼 게임 자체의 상태 또한 구분하기 위해 IGameState를 정의해 놓은 상태입니다. 그리고 이 IGameState는 Stack으로 관리되고 있죠.(게임 상태가 스택 구조로 관리되어야 하는 이유는 조만간 포스팅해야겠습니다.) 이 IGameState는 게임의 각 상태를 제어하기 위한 메소드들을 갖고 있습니다. Enter, Start, Update, HandleInput, Exit까지 5개인데요. 게임 상태에 돌입하고, 씬을 새로 시작하고, 프레임을 업데이트하고, 입력을 처리하고, 상태에서 빠져나가는 정도입니다. 그런데, 여기서 상점이라는 상태를 새로 만들면서, SendShoppingRequest라는 메소드를 구현하게 됩니다. 이걸 IGameState 안에 넣게 된다면 상점이 아닌 다른 상태들은 이 메소드가 필요없겠죠. 이건 따로 만들어주면 되겠는데요… 근데 상점 클래스에서 인터페이스를 다중으로 구현하면, 이건 SendShoppingRequest 메소드는 나중에 어떻게 호출하면 되는 걸까요…? 이 예시가 맞긴 한 건지 모르겠군요.

그뿐만이 아닙니다. 챕터 선택이나 로비 화면은 실제로는 매 프레임 업데이트할 내용이 없어서 메소드를 비워둔 상태입니다. 로비 화면은 버튼으로만 조작을 처리하기 때문에 HandleInput 메소드도 텅 비었습니다. Start 메소드도 씬을 로드해야 하는 스택이 아니면 비워두게 되겠죠. 아무것도 구현되지 않은 메소드를 갖고 있는 것만으로도 이 원칙은 위배된 상태인가요…

아직 멀었나 봅니다.

의존 역전 원칙

객체는 저수준 모듈보다 고수준 모듈에 의존해야 한다.

저수준 모듈은 구체적인 구현이 완료된 객체를, 고수준 모듈은 추상적인 객체나 인터페이스를 뜻합니다. 이 원칙은 다른 원칙을 잘 지키기 위해 클래스나 인터페이스를 활용하다보면 저절로 실현되기 때문에 다른 원칙들보다는 우선 순위는 좀 떨어지는 편이군요.



C++, JAVA를 쓴지도 꽤 오랜 시간이 지났지만 제대로 된 객체지향 프로그래밍은 해본 적이 없는 기분이 드네요. 이젠 함수형 프로그래밍이 뜬다는데, 가면 갈수록 공부할 것들이 늘어나기만 하는군요.