SOLID 5원칙에 대해
java에서 항상 나오던 기본 원칙 중 하나인 SOLID를 예시를 들어 설명하고자 한다.
개념 자체는 알고 있지만 구체적인 예시를 떠올려 보다 생각나 포스팅하였다.
SRP(Single Responsibility Principle) : 단일 책임 원칙
한 클래스는 하나의 책임만 가져야 한다는 원칙.
개인적으로 비교적 가장 쉬운 개념이라 생각이 든다.
객체지향적으로 만들어진 모든 Class들은 각각의 역할에 맞는 메서드, 변수, 책임 등을 가져야 한다.
로그인 클래스가 존재한다면 이 클래스는 로그인에 관련된 기능만을 가져야 한다.
수정, 삭제, 확장 역시도 로그인에 관련된 내용일 것이다.
OCP(Open - Closed Principle) 개방 - 폐쇄 원칙
기능의 개방(확장)에는 열려 있고, 변경에는 닫혀있어야 한다.
클래스나 모듈, 함수 등 .. 모든 것이 새로운 기능의 추가가 가능해야 한다.
하지만 확장의 도중 이전 기능이 변경된다거나 하는 일은 지양되어야 할 것이다.
예를 들어 Person 클래스의 '이동' 기능이 있다고 하자.
각각 사람마다 이동의 모션이 다르다면 '이동' 기능 자체를 하위 클래스로 분리하고, 오버라이드를 통해 구현시키면 될 것이다.
이렇게 한다면 기존의 코드를 변경하지 않고(변경에는 닫혀있고), 각각의 움직임을 확장할 수 있다.
이처럼, OCP의 중요 메커니즘은 추상화와 다형성이다.
LSP(Liskov Substitution Principle) 리스코프 치환 원칙
하위 클래스는 언제나 상위 클래스의 자리를 대체할 수 있어야 한다.
프로그램에서 부모 클래스의 인스턴스를 자식 클래스의 인스턴스로 대체해도, 프로그램의 정확성(correctness)에 영향을 주지 않아야 한다.
그러니까 상속받는 클래스는 이전 부모의 클래스의 세부 기능(메서드)을 무조건 구현할 수 있어야 한다는 말이다.
예시
'새'라는 부모 클래스와 '참새', '타조'라는 두 자식 클래스가 있고, 여기서 '새' 클래스에는 '날다(fly)'라는 메소드가 있다고 가정하자.
1. 리스코프 치환 원칙을 따르는 경우
'참새' 클래스는 '새'의 '날다' 메소드를 그대로 상속받거나 적절하게 오버라이드(override)할 수 있다. 프로그램에서 '새' 대신 '참새'를 사용해도 '날다' 기능은 문제없이 동작한다.
2. 리스코프 치환 원칙을 따르지 않는 경우
만약 '타조' 클래스에서 '날다' 메소드를 Override 했지만, 'cantFlyException'는 예외를 던진다면, 이는 리스코프 치환 원칙을 위반하는 것이다.
이 경우 부모 클래스인 '새'의 인스턴스를 '타조'로 대체했을 때 프로그램이 예외적인 행동을 보이거나 오류를 발생시키기 때문이다.
ISP(Interface Segregation Principle) 인터페이스 분리 원칙
큰 하나의 인터페이스보다는 특정 클라이언트에 특화된 여러 개의 작은 인터페이스가 더 낫다는 것을 의미
위의 문장으로 설명할 수 있겠다.
다음과 같은 예시를 보자.
1. '프린터'라는 큰 인터페이스가 '인쇄', '스캔', '복사' 기능을 모두 가지고 있다고 하자.
2. 만약 어떤 클라이언트가 '인쇄' 기능만 필요로 한다면, '스캔'과 '복사' 기능까지 갖는 '프린터' 인터페이스에 의존하는 것은 비효율적일 것이다.
3. 따라서 '인쇄', '스캔', '복사' 각각을 별도의 인터페이스로 분리하여 클라이언트가 필요한 기능만을 선택하여 사용할 수 있게 한다.
DIP(Dependency Inversion Principle) 의존관계 역전 원칙
고수준 모듈이 저수준 모듈에 직접 의존하지 않고, 양쪽 모두 추상화에 의존해야 한다는 원칙.
즉, 세부 사항이 추상화에 의존하는 것이 아니라 추상화가 세부 사항에 의존해야 한다.
말이 조금 어려운데, 더 풀어 이야기 하자면 다음과 같다.
구체적인 것(Class)이 추상화된 것(Interface)에 의존해야 한다. 자주 변경되는 클래스에 의존하지 말자.
1. '레시피'와 '요리사'를 생각해 보자. 요리사는 레시피를 필요로 한다.
2. 저수준 모듈은 '요리 레시피' 이며, 고수준 모듈인 '요리사'는 저수준의 레시피를 사용한다.
3. 요리사가 특정 피자 레시피에 직접 의존하는 대신, '레시피' 인터페이스에 의존하도록 설계한다면?
세부 설명을 해보자.
1. 요리사는 특정 피자 레시피의 세부 구현에 의존하지 않는다.
2. 대신, 요리사는 모든 피자 레시피가 따르는 추상화된 인터페이스에 의존하며, 이 인터페이스는 피자 레시피의 다양한 구현(치즈, 불고기, 하와이안)에 대한 상세 정보가 없다.
3. 하지만 이를 통해 DB의 구현이 바뀌거나 새로운 종류의 피자 레시피가 추가되더라도 요리사 클래스는 변경할 필요가 없다. 요리사는 단지 인터페이스를 통해 레시피에 접근하기만 하면 되기 때문!
4. 이로써 시스템의 유연성과 모듈 간 결합도의 낮춤이라는 DIP의 주된 이점을 달성할 수 있다.
레퍼런스
https://dev-coco.tistory.com/142