[Java] 객체 지향 설계 5원칙

공부하는 감자·2024년 1월 27일
0

Java

목록 보기
5/5

SOLID 원칙

SOLID 란?

  • 객체 지향 설계(OOP; Object Oriented Design)의 정수
  • 응집도는 높이고 결합도는 낮추는 고전 원칙을 객체 지향의 관점에서 재정립한 것. (High Cohesion, Loose Coupling)

2000년대 초반, 클린 코드로 유명한 로버트 C. 마틴(Robert C. Martin)이 객체 지향 프로그래밍 및 설계의 다섯 가지 기본 원칙으로 제시한 것을 마이클 페더스(Michael Feathers)가 두문자어로 소개한 것이다.

SOLID는 5가지 원칙의 앞 머리 알파벳을 따서 부르는 것이다.

  • SRP (Single Responsibility Principle): 단일 책임 원칙
  • OCP (Open Closed Principle): 개방 폐쇄 원칙
  • LSP (Liskov Substitution Principle): 리스코프 치환 원칙
  • ISP (Interface Segregation Principle): 인터페이스 분리 원칙
  • DIP (Dependency Inversion Principle): 의존 역전 원칙

SOLID에 대하여

  • SOLID는 개념이다!
  • SOLID는 객체 지향 프로그램을 구성하는 속성, 메서드, 클래스, 프레임워크 등 다양한 곳에 다양하게 적용되는 것이므로 SOLID가 적용되었는지 애매모호하거나 보는 사람의 관점에 따라 다르게 해석될 수 있는 소지가 있다.
  • SOLID를 잘 녹여낸 소프트웨어는 상대적으로 이해하기 쉬우며 리팩터링과 유지보수가 수월하다.
  • 객체 지향 4대 특성을 발판으로 하고 있다.
  • 스프링 프레임워크의 근간이다.

Single Responsibility Principle

하나의 클래스는 하나의 책임만 가져야 한다.
= 어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.

역할과 책임

‘남자’라고 하는 클래스가 있을 때, 역할(책임)에 따라 아래처럼 나눌 수 있다.

  • 남자친구 역할이 수정되어도, 다른 관계(아들, 사원, 소대원)는 영향 받지 않는다.

SRP 요약

  • 하나의 클래스에 역할과 책임이 너무 많으면 나쁜 냄새가 난다고 한다.
  • 실무에서 이 ‘하나의 책임’이라는 것은 모호하다. 문맥과 상황에 따라 다를 수 있고, 클 수도 있고 작을 수도 있다.
  • 책임이라는 것에 대한 범위를 적절하게 조절하는 게 중요하다.
  • 중요한 것은 '변경' 이다. 변경이 있을 때 파급이 적으면 이 SRP를 잘 따랐다고 볼 수 있다.

Open Colsed Principle

소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하지만 변경에 대해서는 닫혀 있어야 한다.
= 자신의 확장에는 열려 있고, 주변에 변화에 대해서는 닫혀 있어야 한다.

예제

아래 그림을 통해 개방 폐쇄 원칙을 알아보자.

  • 운전자는 자동차를 운전할 수 있다.
  • 자동차를 새로 한 대 추가할 수 있다. → 확장
  • 자동차가 마티즈에서 쏘나타로 변경되어도 운전자는 영향을 받지 않는다. → 변경

OCP 요약

  • 가장 중요한 원칙이다.
  • 다형성을 활용하면 이 원칙을 지킬 수 있다.
    • 역할과 구현을 분리하여 역할에만 의존하므로, 구현을 변경해도 영향을 미치지 않는다.
  • 이 원칙을 지키면 유연성, 재사용성, 유지보수성 등을 얻을 수 있다.

Liskov Subsititution Principle

서브 타입은 언제나 자신의 기반 타입(base type)으로 교체할 수 있어야 한다.
= 하위 클래스의 인스턴스는 상위형 객체 참조 변수에 대입해 상위 클래스의 인스턴스 역할을 하는 데 문제가 없어야 한다.

상속과 LSP

객체 지향의 상속은 다음 조건을 만족해야 한다.

  • 하위 클래스 is a kind of 상위 클래스
    • 하위 분류는 상위 분류의 한 종류이다.
  • 구현 클래스 is a able to 인터페이스
    • 구현 분류는 인터페이스할 수 있어야 한다.

위 문장대로 구현된 프로그램이라면 리스코프 치환 원칙을 잘 지키고 있다고 할 수 있다.

인터페이스 규약을 지켜야 한다

OCP의 자동차 예제를 다시 보자.

  • 자동차라는 인터페이스가 있고 구현체가 구현을 한다.
  • 엑셀() 은 자동차가 앞으로 가게 구현해야 한다.

만약 엑셀() 을 호출했을 때 자동차를 뒤로 가게 구현한다면, 컴파일 오류는 나지 않지만 리스코프 치환 원칙은 지키지 않았다고 할 수 있다.

  • 엑셀을 밟으면 무조건 앞으로 가야 한다는 인터페이스 규약은 있다.
  • 이 규약을 무조건 맞춰야 한다.
    • 기능적으로 이 규약에 대해 보장을 해줘야 된다.
  • 따라서, 엑셀을 밟으면 뒤로 가게 만든다면 LSP를 위반하는 것이 된다.

Interface Segregation Principle

클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안된다.
= 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.

SRP와 ISP

  • 단일 책임 원칙(SRP)와 인터페이스 분할 원칙(ISP)는 같은 문제에 대한 두 가지 다른 해결책이라고 볼 수 있다.

예를 들어, SRP에서 들었던 남자 클래스의 예제에 ISP를 적용하면 다음과 같이 구현할 수 있다.

남자 클래스처럼 기능이 너무 많으면 복잡하므로 기능을 적당한 크기로 잘 쪼개는 게 중요하다.

그러면 인터페이스가 명확해지고 대체 가능성이 높아진다.

인터페이스 최소주의 원칙

  • 인터페이스를 통해 메서드를 외부에 제공할 때는 최소한의 메서드만 제공하라.
  • 즉, 외부에 노출되는 메서드를 제한할 수 있다.

Dependency Inversion Principle

고차원 모듈은 저차원 모듈에 의존하면 안 된다. 이 두 모듈 모두 다른 추상화된 것에 의존해야 한다.
= 추상화된 것은 구체적인 것에 의존하면 안 된다. 구체적인 것이 추상화된 것에 의존해야 한다.
= 자주 변경되는 구체(concrete) 클래스에 의존하지 마라.

  • OCP와 더불어 가장 중요한 것이다.
  • 프로그래머는 추상화에 의존해야지 구체화에 의존하면 안된다.
  • 쉽게 이야기하면, 클라이언트 코드가 구현 클래스를 바라보지 말고 인터페이스만 바라보라는 뜻이다.

의존 관계의 역전

다음은 운전자가 자주 변경되는 구체 클래스(마티즈)에 의존하고 있는 모습이다.

  • 자동차가 바뀌면 운전자에도 영향이 간다.

띠라서, 인터페이스를 두고 운전자가 해당 인터페이스를 의존하도록 만들었다.

  • 자동차가 바뀌어도 운전자에는 영향이 가지 않는다.
  • 운전자는 구현체(마티즈)가 아닌, 추상화된 인터페이스(자동차)에 의존한다.

즉, 자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스를 두어 변하기 쉬운 것의 변화에 영향받지 않게 하는 것이 바로 의존 역전 원칙이다.

다형성과 OCP, DIP

사실 다형성 만으로는 OCP와 DIP를 지킬 수가 없다.

  • 구현체를 바꿀 때, 바꾸면서 기존 코드의 변경이 발생한다.
  • 따라서, 의존을 따로 주입해주는 등의 기능을 해주는 스프링 프레임워크가 나온 것이다.

SoC

SoC는 관심사의 분리 (Separation Of Concerns)의 앞 글자를 딴 것이다.

  • 관심이 같은 것은 하나의 객체 안으로, 혹은 친한 객체로 모은다.
  • 관심이 다른 것은 가능한 따로 떨어뜨려 서로 영향을 주지 않도록 분리한다.
  • 하나의 속성, 하나의 메서드, 하나의 클래스, 하나의 모듈, 또는 하나의 패키지에는 하나의 관심사만 들어 있어야 한다.
  • SoC를 적용하면 자연스럽게 SRP, ISP, OCP에 도달하게 된다.
  • 스프링 또한 SoC를 통해 SOLID를 극한까지 적용하고 있다.

Reference

참고 서적

📔 스프링 입문을 위한 자바 객체 지향의 원리와 이해

인프런 강의

스프링 핵심 원리 - 기본편 강의 - 인프런

profile
책을 읽거나 강의를 들으며 공부한 내용을 정리합니다. 가끔 개발하는데 있었던 이슈도 올립니다.

0개의 댓글