SOILD 원칙
이건 알고보면 당연하다고 생각되던게 많았던게 더 놀라웠다.
SOLID 라고 부르는 5가지 설계 원칙이 존재한다.
일반적인 객체지향 설계 과정은 다음과 같다.
- 요구사항(제공해야 할 기능)을 찾고 세분화한다. 그리고 그 기능을 알맞은 객체로 할당한다.
- 기능을 구현하는 데 필요한 데이터를 객체에 추가한다.
- 해당 데이터를 이용하는 기능을 구현한다. 기능들은 최대한 캡슐화, 즉 외부에서 안 쓸 기능은 은닉하는 것이 필요하다.
- 객체 간에 어떻게 메소드 호출을 주고받을지 결정한다. 즉 관리자를 두고 다 호출할지, 부분적으로 호출할지, 아니면 스태틱으로 만들어서 호출할지에 대한 정의를 한다.
위와 같은 설계 과정을 거치는데, 그 과정의 원칙이 바로 객체지향 설계 원칙이다. 그것이 바로 SOLID 원칙이다.
일단 하나씩 살펴보기로 하자.
1. SRP (Single Responsibility Principle) 단일 책임 원칙
- 클래스는 단 한 개의 책임을 가져야 한다.
- 클래스는 변경하는 이유가 단 하나여야 한다.
- 이를 지키지 않으면 한 기능의 변경에 의해 다른 기능과 관련된 코드에 영향을 미칠 수 있으며, 이렇게 되면 유지보수가 매우 힘들다.
이 원칙의 이유는 간단하다.
한 클래스가 수행할수 있는 기능이 많을수록 내부 함수끼리 강한 결합이 발생할 가능성이 높아지기 때문이다.
보통 내부 함수에선 굳이 값을 주고받지않고 클래스 멤버변수같은걸 돌려쓰지않는가? 그러지 말라는 것이다.
타 클래스에서 써먹을때 다 호출하거나 그 변수가 은닉을 해야하는 상수값일수도 있기 때문이다.
그리고 그 값이 변해야 원하는 값이 나올수도 있는 상황에선 전부다 엎어야하는 경우가 생길수도있다. 즉, 꼬인다는 것이다.
2. OCP (Open-Closed Principle) 개방-폐쇄 원칙
- 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.
- 즉, 기존의 코드를 변경하지 않고 기능을 수정하거나 추가할 수 있도록 설계해야 한다.
- 이를 지키지 않으면 is 와 같은 연산자를 사용하거나, 다운 캐스팅 발생.
2.OCP(Open-Closed) 개방 - 폐쇄 원칙.
- 확장에는 열려있어야 하고, 변경에는 닫혀 있어야함.
- 즉, 기존의 코드를 변경하지 않고 기능을 수정하거나 추가 할수있도록 설계해야함.
- 이를 지키지않으면 is 와 같은 연산자를 사용하거나, 다운 캐스팅 발생
어떤 모듈의 기능을 하나 수정할 떄, 그 모듈을 이용하는 다른 모듈들 역시 줄줄이 고쳐야 한다면 유지보수가 복잡할 것이다. 따라서 개방 폐쇄 원칙을 잘 적용하여 기존 코드를 변경하지 않아도 기능을 새롭게 만들거나 변경할 수 있도록 해야 한다.
그렇지 않으면 개체지향 프로그래밍의 가장 큰 장점인 유연성, 재사용성, 유지보수성 등을 모두 잃어버리는 셈이고, OOP를 사용하는 의미가 사라지게 된다.
OCP는 추상화(인터페이스)와 상속(다형성) 등을 통해 구현해낼수 있다.
자주 변화하는 부분을 추상화 함으로써, 기존 코드를 수정하지 않고도 기능을 확장할 수 있또록 함으로써 유연함을 높이는것이 핵심 이다.
3. LSP (Liskov Substitution Principle) 리스코프 치환 원칙
- 하위 타입 객체는 상위 타입 객체에서 가능한 행위를 수행할 수 있어야 한다.
- 즉, 상위 타입 객체를 하위 타입 객체로 치환해도 정상적으로 동작해야 한다.
- 예) 스타크래프트를 만들었을 때 모든 유닛이 공격 명령을 가지고 있다고 설정했는데, 부대 지정 후 공격 명령을 내렸을 때 공격이 불가능한 메딕 같은 유닛이 있으면 그 유닛은 아무것도 하지 않음.
- 즉, 상위 타입 객체를 하위 타입 객체로 치환해도 정상적으로 동작해야 한다.
- 상속관계에서는 꼭 일관성 있는 관계가 성립해야 한다.
- 상속관계가 아닌 클래스들을 상속관계로 설정하면 이 원칙이 위배됨(재사용 목적으로 사용하는 경우)
결국은, 리스코프 치환 원칙을 지키지 않으면 개방 폐쇄 원칙을 위반하게 되는 것이다.
기능 확장을 위해 기존의 코드를 여러 번 수정해야 할 것이다.
따라서 상속 관계를 잘 정의하여 LSP 원칙이 위배되지 않도록 설계해야 한다.
4. ISP (Interface Segregation Principle) 인터페이스 분리 원칙
- 자신이 사용하는 메소드에만 의존해야 한다는 원칙.
- 한 클래스는 자신이 사용하지 않는 기능을 가진 인터페이스는 구현하지 않아야 한다.
- 즉, 하나의 통일된 인터페이스보다는 차라리 여러 개의 세부적인(구체적인) 인터페이스가 나음(예: 이동 인터페이스, 공격 인터페이스 등).
- 인터페이스는 해당 인터페이스를 사용하는 기준으로 잘게 분리되어야 한다.
각 필요로 하는 인터페이스들을 분리함으로써, 사용하지 않는 인터페이스에 변경이 발생하더라도 영향을 받지 않도록 만들어야 하는 것이 핵심이다.
5. DIP (Dependency Inversion Principle) 의존성 역전 원칙
- 의존 관계를 맺을 때, 변하기 쉬운 것(구체적인 것)보다는 변하기 어려운 것(추상적인 것)에 의존해야 한다.
- 구체화된 클래스에 의존하기보다는 추상 클래스나 인터페이스에 의존해야 한다는 뜻이다.
- 즉, 고수준 객체는 저수준 객체의 구현에 의존해서는 안 된다.
- 저수준 객체가 고수준 객체에서 정의한 추상 타입에 의존해야 한다.
- 저수준 객체가 변경되어도 고수준 객체는 변경이 필요 없는 형태가 이상적이다.
5.DIP (Dependency Inversion) 의존 역전 원칙
- 의존 관계를 맺을 떄, 변하기 쉬운 것( 구체적인 것) 보다는 변하기 어려운것 (추상적인 것)에 의존해야함.
->구체화된 클래스에 의존하기보다는 추상클래스나 인터페이스에 의존해야한다는 뜻이다.
- 즉 고수준 객체는 저수준 객체의 구현에 의존해서는 안됨
- 저수준 객체가 고수준 객체에서 정의한 추상 타입에 의존해야함.
- 저수준 객체가 변경되어도 고수준 객체는 변경이 필요없는 형태가 이상적이다.
총 정리 요약본.
SRP와 ISP 는 객체가 커지는 것을 막아주고, 객체가 단일 책임을 갖도록 하고 객체마다 특화된 인터페이스를 구현하게 함으로써 한 기능의 변경이 다른곳까지 미치는 영향을 최소화 할수있다.
그리고 이는 기능 추가 및 변경 즉 유지보수가 용이하도록 만들어준다.
LSP와 DIP는 OCP를 서포트한다. OCP는 자주 변화되는 부분을 추상화 하고, 다형성을 이용함으로써, 기능 확장에는 용이하되, 기존 코드의 변화에는 보수적이도록 만들어 준다.
여기서 ' 변화되는 부분을 추상화' 할 수 있도록 도와주는 원칙이 DIP이고, 다형성 구현을 도와주는 원칙이 LSP인것이다.