TextRPG를 제작하면서 특정 물체(캐릭터, 적 등)를 Actor라는 최상위 클래스에서 기본적인 요소를 관리했다.
Unreal처럼 만들어보고 싶다는 이유에서 였는데 여러 문제가 발생했는데
해당 Actor가
위 두가지 때문에 캐릭터(폰)라는 한정된 역할만 할 수 있었다.
그때 같은 기수의 Dr. Dragon 'D' Water 님께서 SOLID 원칙을 알려주셨다.
이전에도 FE엔지니어를 하면서 Repository 패턴을 사용할 일이 있었는데 그때 들어본 원칙이기도 하지만 제대로 알아볼 생각은 하지 않았었다.
실제로 3년넘게 실무하면서 class 사용해본 일이 손에 꼽을 정도로 FE에서는 class를 잘 사용하지 않는다.
단일 책임 원칙
클래스는 1개의 이유로만 변경되어야 한다.
class Player {
public:
void Update();
void Render();
void SaveToFile(); // 저장 책임
void SendNetwork(); // 네트워크 책임
};
// 분리
struct PlayerState {
int hp;
Vector3 position;
};
class PlayerSystem {
public:
void Update(PlayerState&);
};
class SaveSystem {
public:
void Save(const PlayerState&);
};
개방/폐쇄 원칙
확장에는 열려있고 수정에는 닫혀있어야한다.
// 무기 추가할 때마다 값 추가해야함
int CalcDamage(std::string type) {
if (type == "sword") return 10;
if (type == "gun") return 20;
}
// 개선
struct WeaponData
{
int damage;
}
class Weapon
{
WeaponData weaponData;
public:
int ApplyDamage()
{
return weaponData.damage;
}
}
리스코프 치환 원칙
자식 클래스는 부모 클래스를 완전히 대체 가능해야한다.
class Character
{
public:
virtual void Attack() = 0;
};
class Pacifist : public Character
{
public:
// 부모 Character는 Attack 호출 시 함수가 실행되는 것을 기대함
// Pacifist에서 예외처리로 프로그램을 비정상 종료시키기 때문에 LSP 원칙에 어긋남
void Attack() override { throw; }
}
// 개선
class IAttackable
{
public:
virtual void Attack() = 0;
}
class NPC { }; // 공격 없음
class Box { }; // 공격 없음
class Warrior : public IAttackable
{
void Attack() override { ... }
}
인터페이스 분리 원칙
클라이언트는 자신이 사용하지 않는 인터페이스에 의존해서는 안된다.
class IUnit
{
public:
virtual void Move() = 0;
virtual void Attack() = 0;
virtual void Fly() = 0; // 거북이 Actor에 Fly???
}
// 개선
class MoveComponent { void Move(); }
class AttackComponent { void Attack(); }
class FlyComponenty { void Fly(); }
class Unit
{
MoveComponent* move;
AttackComponent* attack;
// 해당 Unit은 Fly 기능이 없으므로 객체 생성X
}
의존성 역전 원칙
고수준 모듈은 저수준 모듈에 의존해서는 안되고 둘 다 추상화에 의존해야한다.
// Game이 더 고수준 모듈
class Game
{
// 저수준 모듈 Sword에 의존하면 원칙 위배
Sword sword;
}
// 개선
class Weapon
{
public:
virtual void Attack() = 0;
}
class Game
{
std::unique_ptr<Weapon> weapon;
Game(satd::unique_ptr<Weapon> w) : weapon(std::move(w)) { };
}
SRP → “변경 이유 1개” 시스템 단위 분리
OCP → “코드 말고 데이터로 확장” 데이터 기반 확장
LSP → “상속 대신 능력 분리” 상속 최소화
ISP → “인터페이스 → 컴포넌트로” 컴포넌트
DIP → “new 하지 말고 주입해라” DI + smart pointer