개요
Solid 설계 원리는 5가지가 존재한다. 잘 설계되었는지 평가하는 데에 도움이 되는 설계 원칙으로 단일 책임의 원리 - SRP, 개방 폐쇄의 원리 - OCP, 리스코프 교체의 원리 - LSP, 의존 관계 역전 원리 - DIP, 인터페이스 격리의 원리 - ISP가 있다.
SRP(Single Responsibility Principle) - 단일 책임 원리
단일 책임의 원리는 한 class를 변경해야 하는 이유는 오직 하나뿐이어야 한다는 것이다. 즉, 하나의 클래스에 너무 많은 역할 / 책임을 부여하여 클래스가 너무 많은 역할을 수행하는 것이다. 아래의 예시 클래스를 한번 생각해보자.
Employee 클래스가 있고 다음의 public 메소드를 가진다.
calcuatePay, calculateTaxes, writeToDisk, readFromDisk, parseXML, displayOnEmployeeReport, displayOnPayrollReport, displayOnTaxReport 메소드를 Employee 클래스가 가지고 있다.
이 Employee 클래스는 calculate에 대한 책임과 Disk로부터의 읽고 쓰기, XML parse하기, 화면에 여러 정보 display 해야하는 책임을 가진다. 즉, 실제로 Employee에 필요한 책임은 calculate에 대한 것 하나밖에 없는데 너무 많은 책임을 Employee가 가지고 있는 것이다. 이 클래스를 단일 책임 원리를 위배하지 않도록 구성하려면 결합된 여러 책임들을 분리하여 별도 클래스에 배정해야 한다.
OCP(Open - Closed Principle) - 개방 폐쇄의 원리
개방 폐쇄의 원리는 소프트웨어의 요소가 확장에 대해서는 열려있지만 변경에 대해서는 닫혀있어야 한다는 것이다. 이 원리도 예시를 통해 이해해보자.
놀이공원에서 할인 정책에 따라 티켓을 구매하는 경우를 생각해보자. 할인 정책에는 학생 할인, VIP 할인, 노약자 할인이 존재하고, 이것들을 if - else 구문을 이용해 구현하였다.
만약 이 할인 정책에 군인 할인이 추가된다면 어떨까? 군인 할인이 추가되었으므로 기존의 if - else 문장을 수정해야 하며, 새로운 할인 정책이 추가 / 변경된다면 기존 if - else 문장을 수정해야하는 문제가 있다. 이렇게 기존 문장을 변경하게 되면 오류가 발생할 가능성이 높아지고 유지 보수가 어려워진다는 단점이 있다.
위 경우에서 OCP를 만족하게 하려면 Java의 인터페이스 기능을 이용해 확장 가능하도록 설계하면 된다. Discount 인터페이스를 만들어 인터페이스에 할인 적용 메서드 applyDiscount를 만든다. 이렇게 만든 인터페이스를 활용하여 확장이 용이하도록 설계하여 OCP를 만족할 수 있다.
LSP - 리스코프 교체의 원리
리스코프 교체의 원리란 하위 타입을 상위 타입과 교체해도 프로그램이 정상 작동해야 한다는 것이다. super class에서 받는 매개변수를 sub class가 모두 받아야 하고, super class가 사용되었을 때 유효하면 sub class가 사용되었을 때에도 유효하다.
부모 객체 Rectangle 가 정수형 width, height 필드 2개를 가지고 있고 width 와 height를 set하는 setWidth , setHeight 메서드를 가진다고 가정하자. 이제 우리는 상속 기능을 이용해 Square 객체를 만들었다. 그리고 Square 객체가 대각선을 뜻하는 diagonal을 필드로 가지고, 부모 객체 Rectangle의 setWidth를 오버라이딩하여 아래와 같이 작성하였다. setWidth(diagonal) { this.width = diagonal / sqrt(2) }
LSP를 만족하려면 프로그램에서 Square 객체를 사용하는 부분에서 Rectangle 객체를 대신 사용해도 프로그램이 정상적으로 작동해야 한다. 하지만 Square 객체와 Rectangle 객체를 바꿔 사용하면 예상과 다른 동작을 수행할 것이다. LSP는 이렇게 자식 객체와 부모 객체를 바꿔서 사용해도 정상적으로 프로그램이 작동해야 하는 것을 뜻한다.
따라서 인터페이스를 상속할 때에 LSP를 철저하게 준수해야 한다.
DIP - 의존 관계 역전 원리
의존 관계 역전의 원리는 추상적인 것이 구체적인 것에 의존해서는 안되며, 구체적인 것이 추상적인 것에 의존해야 한다는 것이다. 높은 수준의 인터페이스, 추상 클래스가 낮은 수준의 구현보다 변화 빈도가 낮기 때문에 더 안정적이라는 것이다.
위 Rectangle 클래스 예제의 다른 경우를 생각해보자. Shape 인터페이스가 있다면 Rectangle 클래스는 Shape 인터페이스를 참고하여(의존하여) 만들어질 것이다. 역으로 Shape 인터페이스가 더 낮은 수준의 개념인 Rectangle 클래스에 의존적이게 되면 안된다는 것이다.
즉, 높은 수준의 모듈은 저 수준의 모듈에 의존하지 않고, 낮은 수준의 모듈이 높은 수준인 추상적 개념에 의존해야 한다.
ISP - 인터페이스 격리의 원리
인터페이스 격리의 원리는 클라이언트가 자신이 사용하지도 않는 메서드에 의존해서는 안된다는 것이다. 다른 여러 클라이언트의 기능을 하나의 인터페이스 / 클래스에 포함시켜 거대하게 만들면 어떻게 될까? 포함된 클라이언트의 기능이 변경되어 수정 사항이 발생하면 다른 클라이언트도 다시 컴파일해야 하는 경우가 발생한다. 즉, 클라이언트에 불필요한 결합이 생긴다는 것이다.
한 인터페이스에 1 ~ 5까지의 메스드가 존재하고, 이 인터페이스를 사용해 Temp 클래스를 만들었다고 생각하자. 이렇게 만든 Temp 클래스가 인터페이스의 1 ~ 5 메서드를 모두 구현했는데 1 ~ 3까지의 메서드에 throw NotImplementedException을 이용했다면 어떨까? Temp 클래스는 인터페이스의 사용하지도 않는 메서드를 가졌으므로 인터페이스를 분리하여 필요한 인터페이스만 사용하도록 하면 된다. 이렇게 인터페이스를 분리하여 사용하면 특정 기능이 변경되어도 작은 부분만 변경하면 된다.
'CS > 객체지향설계 & 패턴' 카테고리의 다른 글
[객체지향설계 & 패턴] 프로토타입 패턴 (0) | 2025.05.10 |
---|---|
[객체지향설계 & 패턴] 브리지 패턴 (0) | 2025.04.06 |
[객체지향설계 & 패턴] 퍼사드 패턴 (1) | 2025.04.06 |
[객체지향설계 & 패턴] 어댑터 패턴 (0) | 2025.04.06 |
[객체지향설계 & 패턴] 디자인 패턴 개론 (0) | 2025.03.22 |