본문 바로가기

Java

SOLID 원칙

SOLID 원칙이란

 

SOLID 원칙은 객체 지향 프로그래밍과 설계에서 코드의 유지보수성과 확장성을 높이기 위해 제안된 다섯 가지 설계 원칙을 의미한다. 각각의 원칙은 코드의 결합도를 낮추고 응집도를 높이며 코드의 가독성과 재사용성을 높이는 것을 목표로 한다.

 


 

1. 단일 책임 원칙 (Single Responsibility Principle, SRP)

  • 클래스(객체)는 단 하나의 책임만 가져야 한다는 원칙
  • 여기서 책임은 하나의 기능 담당으로 볼 수 있다.
  • 즉, 하나의 클래스는 하나의 기능 담당하여 하나의 책임을 수행하는데 집중되도록 클래스를 따로따로 여러 개 설계하라는 원칙
  • 다른 의미로는 클래스가 변경되어야 하는 이유가 단 하나여야 한다는 것을 의미함
사용자의 데이터를 처리하는 로직이 필요하다고 가정하자.

사용자 데이터 검증, 데이터베이스 저장, 사용자 인증 등의 로직을 하나의 클래스에 구현하면 하나의 클래스가 너무 많은 책임을 갖게 된다. 따라서 이러한 책임을 각각의 클래스로 분리해서 구현해야 한다.

 


 

2. 개방-폐쇄 원칙 (Open-Closed Principle, OCP)

  • 클래스는 확장에 열려 있어야 하며, 수정에는 닫혀 있어야 한다는 원칙
  • 확장에 열려있다 : 새로운 변경 사항이 발생했을 때 유연하게 코드를 추가함으로써 큰 힘을 들이지 않고 애플리케이션의 기능을 확장할 수 있음
  • 수정에 닫혀있다 : 새로운 변경 사항이 발생했을 때 객체를 직접적으로 수정을 제한함
추상 클래스나 인터페이스를 사용하면 이러한 원칙을 지키며 코드를 구현할 수 있다.

 


 

3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)

  • 서브 타입은 언제나 기반(부모) 타입으로 교체할 수 있어야 한다는 원칙
  • 즉, 상위 타입의 객체를 사용하는 프로그램에서 하위 타입의 객체로 교체해도 프로그램이 제대로 작동해야 한다는 원칙
  • 쉽게 말하면 LSP는 다형성 원리를 이용하기 위한 원칙 개념이다.
    - 다형성 : 어떤 객체의 속성이나 기능이 상황에 따라 여러 가지 형태를 가질 수 있는 성질
  • 간단히 말하면 리스코프 치환 원칙이란, 다형성의 특징을 이용하기 위해 상위 클래스 타입으로 객체를 선언하여 하위 클래스의 인스턴스를 받으면, 업캐스팅된 상태에서 부모의 메서드를 사용해도 동작이 의도대로 흘러가야 하는 것을 의미하는 것이다.
  • 따라서 기본적으로 LSP 원칙은 부모 메서드의 오버라이딩을 조심스럽게 따져가며 해야한다. 왜냐하면 부모 클래스와 동일한 수준의 선행 조건을 기대하고 사용하는 프로그램 코드에서 예상치 못한 문제를 일으킬 수 있기 때문이다.
자바에선 대표적으로 Collection 인터페이스를 LSP의 예로 들 수 있다.

Collection 타입의 객체에서 자료형을 LinkedList에서 전혀 다른 자료형 HashSet으로 바꿔도 add() 메서드를 실행하는데 있어 문제가 없다.

한마디로 다형성 이용을 위해 부모 타입으로 메서드를 실행해도 의도대로 실행되도록 구성을 해줘야 하는 원칙이라 이해하면 된다.

 

4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)

  • 인터페이스를 각각 사용에 맞게 끔 잘게 분리해야한다는 설계 원칙
  • SRP 원칙이 클래스의 단일 책임을 강조한다면, ISP는 인터페이스의 단일 책임을 강조한다.
  • ISP 원칙은 인터페이스를 사용하는 클라이언트를 기준으로 분리함으로써, 클라이언트의 목적과 용도에 적합한 인터페이스 만을 제공하는 것이 목표
  • 다만 ISP 원칙의 주의해야 할 점은 한번 인터페이스를 분리하여 구성해놓고 나중에 무언가 수정사항이 생겨서 또 인터페이스들을 분리하는 행위를 가하지 말아야 한다는 것이다.
결제 기능을 구현한다고 가정하자.

현금 결제, 카드 결제, 가상계좌를 이용해서 결제하는 기능을 PaymentProcessor 라는 인터페이스에 3개의 추상 메소드로 선언했다고 가정하자. 이런 경우가 ISP를 위반한 경우이다.

오히려 CashPaymentProcessor , CreditCardPaymentProcessor , VirtualAccountPaymentProcessor 라는 3개의 인터페이스로 분리하면 ISP 원칙을 지킬 수 있다. 물론 이는 어디까지나 예시라 실질적으로 ISP를 적용할 때는 상황에 맞게 인터페이스를 구현해야 한다.

 

5. 의존성 역전 원칙 (Dependency Inversion Principle, DIP)

  • 고수준 모듈은 저수준 모듈에 의존해서는 안 되고 둘 다 추상화에 의존해야 한다는 원칙
  • 어떤 Class를 참조해서 사용해야 하는 상황이 생긴다면 그 Class를 직접 참조하는 것이 아니라 그 대상의 상위 요소(추상 클래스 or 인터페이스)로 참조하라는 원칙
  • 즉, 클래스간의 결합도를 느슨하게 하라는 원칙이다.
  • 다시 말해 객체 간에 의존 관계를 맺을 때 변화하기 쉬운 것 또는 자주 변화하는 것보다 변화하기 어려운 것 혹은 거의 변화가 없는 것에 의존하라는 것이다.
UserService라는 클래스에서 사용자의 비밀번호를 암호화하는 로직을 사용한다고 가정하자.

암호화 알고리즘은 여러 개라 각 알고리즘 별로 클래스를 만들고 UserService에서 이를 호출하는 방식을 사용할 수 있다.

하지만 DIP 원칙을 지키려면 PasswordEncoder라는 인터페이스를 만들고 이를 구현하는 SimplePasswordEncoder, SHA256PasswordEncoder, BCryptPasswordEncoder를 클래스를 구현해야 한다.

이렇게 되면 UserService는 PasswordEncoder에 의존하기 때문에 클래스간 결합도를 낮출 수 있다.

출처 : 
https://www.nextree.co.kr/p6960/
https://mangkyu.tistory.com/194
https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-5%EA%B0%80%EC%A7%80-%EC%9B%90%EC%B9%99-SOLID
https://www.codestates.com/blog/content/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%ED%8A%B9%EC%A7%95

 

'Java' 카테고리의 다른 글

Singleton 패턴  (0) 2024.07.16
추상 클래스와 인터페이스  (0) 2024.07.13
인터페이스(Interface)  (0) 2024.07.12
상속(Inheritance)  (0) 2024.07.12
동일성(identity)과 동등성(equality)  (1) 2024.06.07