[ Gof와 SOLID 원칙의 소개 ]
1 . GOF
[ GOF ]
- 디자인 패턴을 만들어낸 그룹
- 디자인 패턴은 SOLID 원칙을 지키기 위해 사용된다 .
2 . SOLID 원칙
[ SOLID 원칙 ]
- SOLID 원칙은 소프트웨어 설계를 더 이해하기 쉽고, 유연하며 유지보수 가능하게 만드는 설계 원칙의 집합 .
- 객체 지향 프로그래밍에서 유용하다 .
[ SOLID 원칙의 종류 ]
- Single Responsibility ( 단일 책임 원칙 )
- Open - Closed ( 개방 폐쇄 원칙 )
- Liskov substitution ( 리스코프치환 원칙 )
- Interface segregation ( 인터페이스 분리 원칙 )
- Dependency Inversion ( 의존 역전 원칙)
[ SOLID 원칙 ]
1 . Single Responsibility ( 단일 책임의 원칙 )
[ 설명 ]
- 모든 클래스는 하나의 책임만 가진다 .
- 클래스는 그 책임을 완전히 캡슐화 해야 한다 .
- 클래스가 제공하는 모든 기능은 이 책임에 부합해야 한다 .
- 클래스 변경의 이유도 한가지여야 한다 .
[ 장점 ]
- 가독성 : 짧은 클래스가 읽기 쉽다 .
- 확장성 : 작은 클래스로부터 상속이 쉽다 .
- 재사용성 : 부분에서 재사용할 수 있도록 작고 모듈식으로 설계
[ 예시 ]
// 잘못된 예: Player 클래스가 플레이어 입력과 이동을 모두 처리함.
public class Player : MonoBehaviour
{
void Update()
{
HandleInput();
Move();
}
void HandleInput()
{
// 입력 처리 로직
}
void Move()
{
// 이동 로직
}
}
// 올바른 예: 책임을 두 개의 다른 클래스로 분리함.
public class Player : MonoBehaviour
{
private PlayerInput playerInput;
private PlayerMovement playerMovement;
void Awake()
{
playerInput = new PlayerInput();
playerMovement = new PlayerMovement();
}
void Update()
{
playerInput.HandleInput();
playerMovement.Move();
}
}
public class PlayerInput
{
public void HandleInput()
{
// 입력 처리 로직
}
}
public class PlayerMovement
{
public void Move()
{
// 이동 로직
}
}
2 . Open - Close ( 개방폐쇄 원칙 )
[ 설명 ]
- 확장 ( 기능의 추가 ) 에 대해서는 개방적(open)이고, 수정에 대해서는 폐쇄적(closed)이어야 한다
- 추상화가 좋은 예시이다 .
- 확장에 대해 개방적이다 .
- 모듈의 동작을 확장 할 수 있음을 의미한다 .
- 요구 사항이 변경될때 , 새로운 동작을 추가해 모듈을 확장한다 .
- 즉 , 모듈이 하는 일을 변경 할 수 있다 .
- 수정에 대해 폐쇄적이다 .
- 새로운 변경사항이 발생했을 때 객체를 직접 수정하지 않고 적용해야 한다 .
- 모듈의 라이브러리가 그 예시이다 .
[ 장점 ]
- 다형성과 확장을 가능케 하는 객체지향의 장점을 극대화 할 수 있다 .
[ 예시 ]
// 잘못된 예: 새로운 동작을 추가하기 위해 Enemy 클래스를 수정함.
public class Enemy : MonoBehaviour
{
public void TakeDamage(int damage)
{
// 피해 로직
}
public void Move()
{
// 이동 로직
}
public void SpecialAttack()
{
// 특수 공격 로직
}
}
// 올바른 예: Enemy 클래스를 수정하지 않고 상속을 사용하여 확장함.
public class Enemy : MonoBehaviour
{
public virtual void TakeDamage(int damage)
{
// 피해 로직
}
public virtual void Move()
{
// 이동 로직
}
}
public class SpecialEnemy : Enemy
{
public override void Move()
{
base.Move();
// 특수 적의 추가 이동 로직
}
public void SpecialAttack()
{
// 특수 공격 로직
}
}
3 . LisKov Substitution ( 리스코프 치환 원칙)
[ 설명 ]
- 파생 클래스는 기본 클래스를 대체 할 수 있어야 한다 .
- 즉 , 클래스의 방향성이 유지되어야 한다는 것 .
- 하위 클래스를 강력하고 유연하게 만드는 원칙
[ 장점 ]
- 코드 재사용성, 유지 관리성 및 유연성을 향상하여 더 나은 소프트웨어 설계를 촉진한다 .
- 기능을 인터페이스로 나누면 수월할것이다 .
[ 예시 ]
// 잘못된 예: 파생 클래스가 기본 클래스의 예상 동작을 변경함.
public class Weapon
{
public virtual void Fire()
{
// 발사 로직
}
}
public class LaserGun : Weapon
{
public override void Fire()
{
// 레이저 전용 발사 로직
}
}
public class Player : MonoBehaviour
{
private Weapon weapon;
void Start()
{
weapon = new LaserGun();
}
void Update()
{
weapon.Fire();
}
}
// 올바른 예: 파생 클래스가 기본 클래스의 계약을 준수함.
public abstract class Weapon
{
public abstract void Fire();
}
public class LaserGun : Weapon
{
public override void Fire()
{
// 레이저 전용 발사 로직
}
}
public class Player : MonoBehaviour
{
private Weapon weapon;
void Start()
{
weapon = new LaserGun();
}
void Update()
{
weapon.Fire();
}
}
4 . Interface Segregation ( 인터페이스 분 원칙)
[ 설명 ]
- 인터페이스를 잘게 분리하여 , 클라이언트의 목적과 용도에 적합한 인터페이스 만을 제공하는 것 .
- 즉, 큰 덩어리의 인터페이스를 구체적이고 작은 단위로 분리한다 .
- 클라이언트는 자신이 이용하지 않는 메서드에 의존하지 않아야 한다 .
[ 장점 ]
- 시스템 내부의 의존성을 약화하고 , 유연성을 강화한다 .
[ 예시 ]
// 잘못된 예: 큰 인터페이스가 클래스에 필요하지 않은 메서드를 구현하도록 강요함.
public interface ICharacter
{
void Attack();
void Defend();
void Heal();
}
public class Warrior : MonoBehaviour, ICharacter
{
public void Attack()
{
// 공격 로직
}
public void Defend()
{
// 방어 로직
}
public void Heal()
{
// 힐 로직 (Warrior에게는 필요하지 않음)
}
}
// 올바른 예: 특정 기능을 위한 작은 인터페이스로 분리함.
public interface IAttack
{
void Attack();
}
public interface IDefend
{
void Defend();
}
public interface IHeal
{
void Heal();
}
public class Warrior : MonoBehaviour, IAttack, IDefend
{
public void Attack()
{
// 공격 로직
}
public void Defend()
{
// 방어 로직
}
}
public class Medic : MonoBehaviour, IHeal
{
public void Heal()
{
// 힐 로직
}
}
5 . Dependency Inversion ( 의존 역전 원칙)
[ 설명 ]
- 상위 모듈은 하위 모듈의 것을 직접 가져오는게 아니라 추상화에 의존해야 한다 .
- 즉 , 객체에서 어떠한 class를 참조해서 사용해야 한다면 , 직접 참조하는게 아닌 대상의 상위 요소 (추상 클래스 / 인터페이스)로 참조해야 한다 .
- 추상화는 세부사항에 의존해서는 안되고 , 세부사항이 추상화에 의존해야 한다 .
- 클래스가 다른 클래스와 관계가 있으면 안된다 .
[ 장점 ]
- 디커플링으로 서로간의 의존성을 줄인다 .
[ 예시 ]
// 잘못된 예: 고수준 모듈이 저수준 모듈에 직접 의존함.
public class GameManager : MonoBehaviour
{
private Database database;
void Start()
{
database = new Database();
database.Save();
}
}
public class Database
{
public void Save()
{
// 저장 로직
}
}
// 올바른 예: 고수준 모듈과 저수준 모듈이 모두 추상화에 의존함.
public interface IDatabase
{
void Save();
}
public class Database : IDatabase
{
public void Save()
{
// 저장 로직
}
}
public class GameManager : MonoBehaviour
{
private IDatabase database;
void Start()
{
database = new Database();
database.Save();
}
}
출처
💠 객체 지향 설계의 5가지 원칙 - S.O.L.I.D
객체 지향 설계의 5원칙 S.O.L.I.D 모든 코드에서 LSP를 지키기에는 어려움. 리스코프 치환 원칙에 따르면 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대신하더라도 의도에 맞게 작동되어
inpa.tistory.com