SOLID 원칙

SOLID 원칙은 객체지향 프로그래밍에서 다루는 5가지 원칙이다. SOLID 원칙을 적용하면서 프로그래밍 한다면, 코드를 확장하고 유지 보수 관리하기가 용이해지며, 불필요한 복잡성을 제거해 리팩토링에 소요되는 시간을 줄여 프로젝트 개발의 생산성을 높일 수 있다. 객체지향이란 객체들이 서로 메시지를 주고 받으며 협력관계를 유지하는 형태의 프로그래밍 패러다임이다.

단일 책임 원칙, Single Responsibility Principle

한 클래스는 하나의 책임만 가져야 한다.

단일 책임 원칙은 작성된 클래스는 하나의 책임을 수행는데 집중해야 한다는 원칙이다. 책임은 문맥과 상황에 따라 크기가 변할 수 있다. 만약 어떠한 변화가 발생했을 때, 파급 효과가 적다면 적절하게 책임을 분리한 것이다. 책임을 잘 분리한다면, 책임 영역이 확실하여 한 책임의 변경에서 다른 책임의 변경으로의 연쇄작용에서 자유로울 수 있다. 예를 들어 A 클래스의 코드를 수정하였더니 B 클래스도 수정해야하고, C 클래스를 수정했더니 다시 A 클래스로 돌아가서 수정해야 한다면 한 책임의 변경에서 다른 책임의 변경으로 연쇄작용이 발생하는 것이다.

정리하자면 클래스가 변경되는 이유는 단 한가지여야 한다. 여러 책임을 가지고 있으면 각기 다른 이유에 의해서 클래스가 변경되는 이유가 여러가지가 되기 때문이다.

장점

  1. 유지보수성 향상

  2. 재사용성 증가

  3. 가독성 향상

적용시 주의점

  1. 클래스명은 책임의 소재를 알 수 있게 작명

  2. 결합도와 응집도를 따져 책임을 분리

    • 무조건 책임을 분리한다고 단일 책임 원칙이 적용되는 건 아니다.

    • 각 개체 간의 응집력이 있다면 병합이 순 작용의 수단

    • 각 개체 간의 결합력이 있다면 분리가 순 작용의 수단

    • 응집력을 높게, 결합도는 낮게 설계해야 한다.

개방-폐쇄 원칙, Open/Closed Principle

소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.

개방-폐쇄 원칙은 소프트웨어 요소(클래스, 모듈, 함수 등)는 확장을 허용하는데 열여 있어야 하고, 변경에 대해서는 닫혀 있어야 한다. 여기서 말하는 확장에는 열려있다는 새로운 변경 사항이 생겼을 때 최소한의 비용으로 즉, 기존 구성요소는 수정하지 않고 새로운 코드를 추가함으로써 기능을 확장시킨다는 의미이다. 변경에는 닫혀있다는 객체를 직접적으로 수정하는 건 제한해야 한다는 의미이다. 새로운 변경 사항이 발생했을 때, 객체를 직접적으로 수정해야 한다면 새로운 변경 사항에 대해 유연하게 대응할 수 없는 애플리케이션인 것이다.

개방-폐쇄 원칙은 자바의 특징인 다형성을 활용하여 지킬 수 있다.

적용방법

  1. 변경(확장)될 것과 변하지 않을 것을 엄격히 구분한다.

  2. 이 두 모듈이 만나는 지점에 인터페이스를 정의한다.

  3. 구현에 의존하기보다 정의한 인터페이스에 의존하도록 코드를 작성한다.

개방-폐쇄 원칙에 위반하지 않는 설계를 할 때 가장 중요한 것은 무엇이 변하는 것인지, 무엇이 변하지 않는 것인지를 구분해야 한다는 점이다. 따라서 인터페이스를 정의할 때 여러 경우의 수에 대한 고려와 예측이 필요하다.

리스코프 치환 원칙, Liskov Substitution Principle

프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.

리스코프 치환 원칙은 하위 타입은 언제나 기반(부모) 타입으로 교체할 수 있어야 한다. 하위 클래스(자식 클래스)는 상위 클래스(부모 클래스)의 모든 속성을 가져야 하며, 상위 클래스가 갖는 메서드의 시그니처를 변경하지 않아야 한다. 리스코프 치환 원칙을 만족시키기 위해서는 부모 메서드의 오버라이딩을 조심스럽게 따지며 해야한다. 부모 클래스에서는 동일한 수준의 선행 조건을 기대하고 사용하였다가 예상치 못한 문제가 발생할 수 있기 때문이다.

적용방법

  1. 만약 두 개체가 똑같은 일을 한다면 둘을 하나의 클래스로 표현하고 이들을 구분할 수 있는 필드를 둔다.

  2. 똑같은 연산을 제공하지만, 이들을 약간씩 다르게 한다면 공통의 인터페이스를 만들고 둘이 이를 구현한다. (인터페이스 상속)

  3. 공통된 연산이 없다면 별개인 2개의 클래스를 만든다.

  4. 만약 두 개체가 하는 일에 추가적으로 무언가를 더한다면 구현 상속을 사용한다.

자바의 컬렉션 프레임워크가 리스코프 치환 원칙을 적용하였다. LinkedList 자료형을 사용하다가, 중간에 HashSet 자료형으로 바꿔도 add() 메서드 동작을 보장받고 싶다면 Collection 이라는 인터페이스 타입으로 변수를 선언하여 할당하면 된다. 여기서 Collection은 상위 타입이고, LinkedList와 HashSet은 하위 타입이다.

리스코프 치환 원칙을 지킨다면 하위 클래스 확장에 자유롭고, 하위 클래스에서의 동작 호환성을 보장할 수 있다. 반면 준수하지 않는 경우, 코드의 안정성과 예측 가능성이 떨어지며, 다형성을 사용할 때 원치않는 결과가 발생할 수 있다.

인터페이스 분리 원칙, Interface Segregation Principle

특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.

인터페이스 분리 원칙은 인터페이스를 각각 사용에 맞게 잘게 분리해야한다는 설계 원칙이다. A 클래스가 B 인터페이스를 구현할 때 사용하지 않는 인터페이스는 구현하지 말아야 한다는 말로 이어진다. 단일 책임 원칙이 클래스의 단일 책임을 강조한다면 인터페이스 분리 원칙은 인터페이스의 단일 책임을 강조한다. 그렇다고 둘의 차이점이 단순히 클래스와 인터페이스의 차이점만 있는 것은 아니다. 인터페이스 분리 원칙은 어떤 인터페이스가 여러 책임 혹은 여러 역할을 갖는 것을 인정한다.

그렇다면 단일 책임 원칙을 만족하면 인터페이스 분리 원칙 또한 성립할까? 반드시 그렇다고 볼 순 없다. 아래 예제를 살펴보자.

글쓰기, 읽기, 수정, 삭제 라는 메서드를 가진 게시판 인터페이스가 있고, 이를 상속받은 일반 사용자와 관리자가 있다. 이 형태는 단일 책임 원칙을 만족한다. 게시판 클래스가 게시판에 관련된 책임을 수행하고 있기 때문이다. 하지만 일반 사용자의 경우 게시판 삭제 메서드는 사용할 수 없기 때문에 불필요한 인터페이스를 구현하고 있다. 그렇기에 인터페이스 분리 원칙은 만족하지 못한다.

장점

  • 의존성 최소화

  • 유지보수성 향상

  • 가독성 향상

의존 역전 원칙, Dependency Inversion Principle

프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다.

의존 역전 원칙은 A 클래스를 참조해서 사용해야하는 상황에서 A 클래스를 직접 참조하는 것이 아니라 그 대상의 상위 요소(추상 클래스 혹은 인터페이스)에 의존해야 한다는 원칙이다. 의존 역전 원칙을 준수하여 추상화에 의존한다면 시스템의 모듈 간 결합이 낮아지므로, 모듈을 더 쉽게 재사용하고 변경할 수 있고, 유연성이 향상돼, 새로운 모듈을 쉽게 추가하거나 교체할 수 있다.

개방-폐쇄 원칙을 준수하려면 우선 기본적으로 의존 역전 원칙이 만족되어야 한다.

의존 역전 원칙을 만족하는 간단한 설계는 애플리케이션은 인터페이스를 의존하고, 해당 인터페이스를 여러 객체들이 구현하면 된다.

이전에는 의존 관계가 고수준 모듈 --> 저수준 모듈 의 방향이였다면, 고수준 모듈 --> 추상화 계층 <- - 저수준 모듈 이런 식으로 추상화 계층이 생기면서 의존 관계가 역전된 형태가 되어 의존 역전 원칙이라 부른다.

ref

SOLID(객체 지향 설계) SOLID 단일 책임의 원칙 SRP 스프링 핵심 원리 - 기본편 객체지향 개발 5대 원리: SOLID [Java] OOP(객체지향 프로그래밍) 설계 원칙

Last updated