[Spring] 01-3. SOLID (객체 지향 설계 5원칙)

지찬우·2022년 12월 17일
0

Spring

목록 보기
3/27
post-thumbnail

이 시리즈는 인프런 강의(김영한 님의 ‘스프링 핵심 원리 - 기본편’)로 공부하며 혼자 기록하고, 사람들과도 공유할 수 있도록 작성하는 글이다. 최대한 추가적인 정보는 공식 홈페이지, 문서를 보며 얻을 예정이다.
(개인적인 생각과 이해가 들어가 있기 때문에 저의 ‘무식함’이 있을 수 있습니다😜 혹시라도 이 글을 보게 되시는 분이 계시다면 잘못된 부분 댓글로 많이 알려주시면 너무 감사하겠습니다!!)


객체 지향 설계 5원칙 : SOLID

클린 코드로 유명한 로버트 C. 마틴이 좋은 객체 지향 설계의 5가지 원칙을 정리하였다.

  • SRP : 단일 책임 원칙
  • OCP : 개방 폐쇄 원칙
  • LSP : 리스코프 치환 원칙
  • ISP : 인터페이스 분리 원칙
  • DIP : 의존관계 역전 원칙

이 단어들의 앞 글자를 따서 SOLID라고 말하는 것이다. 이 중에서 OCP와 DIP가 중요하다고 한다.

그럼 각각이 무엇을 뜻하는지 알아보자.

1️⃣ SRP : 단일 책임 원칙(Single Responsibility Principle)

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

하나의 책임이라는 개념은 굉장히 모호하다. 중요한 기준은 변경이다. 변경이 있을 때 파급 효과가 적다면, 즉 변경이 있을 때 하나의 클래스만 수정하면 되는 경우 단일 책임 원칙을 잘 따랐다고 볼 수 있다.

2️⃣ OCP : 개방-폐쇄 원칙(Open-Closed Principle) 📌

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

이게 무슨 말이지. 열려 있으면서 닫혀 있어야 한다….?

다형성을 한 번 생각해 보자. 인터페이스를 구현한 새로운 클래스를 하나 만들어 새로운 기능을 구현하고자 한다. 이것은 기존 코드를 변경하는 것이 아닌, 확장하는 것이다. 따라서 다형성을 잘 활용해야 한다.

문제점❗️

하지만 문제점이 존재한다. 아래의 코드는 이전 시간에 다형성에 대해 설명하며 사용했던 코드이다.

public class MemberService {

    private MemberRepository memberRepository = new MemoryMemberRepository();
     										   //또는
											  = new JdbcMemberRepository();
		...
}

이 코드에서 구현 객체를 변경하려면 클라이언트(MemberService)의 코드를 변경해야 했다. 다형성을 잘 사용했지만 OCP 원칙을 지킬 수 없다는 것이다.

그럼 뭐 어떻게 해야 돼..❓

객체를 생성하고, 연관 관계를 맺어주는 별도의 조립과 설정자가 필요하다. 그것이 바로 ‘스프링’이다..! (정확히는 스프링 컨테이너)

앞으로 코드를 직접 작성하며 어떻게 해결하는지 배울 예정이니, 지금은 간단하게 그렇구나 정도로만 알고 넘어가도 될듯하다.

3️⃣ LSP : 리스코프 치환 원칙(Liskov Substitution Principle)

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

말이 어려운 것 같다. 간단히 말하면, 다형성에서 하위 클래스(인터페이스 구현체 클래스)가 인터페이스의 규약을 모두 지켜야 한다는 것이다. 단순히 컴파일 성공을 이야기하는 것이 아니다.

자동차 예시를 한 번 더 들어보자. 자동차의 액셀은 앞으로 가는 기능을 한다. 이건 모든 운전자가 알고 있는 사실이고, 자동차 역할의 규칙이라고 할 수 있다. 만약 액셀을 밟으면 뒤로 가거나 멈추도록 구현한다면, 이는 자동차 역할의 규약을 지키지 않은 것이기 때문에 LSP를 위반한 것이다.

(추가적으로 ‘리스코프’가 사람 이름 같아서 wikipedia에 한 번 찾아보았다. full name은 바바라 리스코프이며, 리스코프 치환 원칙을 개발한 미국의 컴퓨터 과학자라고 한다. 이분은 미국 컴퓨터 과학의 박사 학위를 받은 최초의 여성들 중 한 분이라고 한다 ㄷㄷ)

4️⃣ ISP : 인터페이스 분리 원칙(Interface Segregation Principle)

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

이 개념은 그나마 쉬운 것 같다.(OCP 개념이 너무 어려웠어…..)

자동차 인터페이스를 ‘운전 인터페이스’와 ‘정비 인터페이스’로 분리하면, 사용자 클라이언트를 ‘운전자 클라이언트’와 ‘정비사 클라이언트’로 분리할 수 있다. 이렇게 분리하면 정비 인터페이스 자체가 변해도 운전자 클라이언트는 영향을 받지 않는다. 따라서 인터페이스가 명확해지고 대체 가능성이 증가하게 된다.

(스프링 프레임워크의 내부 코드를 살펴보면 엄청 철저하게 분리되어 있다고 한다.)

5️⃣ DIP : 의존관계 역전 원칙(Dependency Inversion Principle) 📌

(난 처음에는 DIP가 DI(Dependency Injection)에 Principle만 붙은 말인 줄 알았다. 하지만 아니었다.😜)

프로그래머는 ‘추상화’에 의존해야 하고, ‘구체화’에 의존하면 안 된다. 의존성 주입은 이 원칙을 따르는 방법 중 하나이다.

다시 말해 구현 클래스에 의존하지 말고, 인터페이스에 의존해야 한다는 의미이다.(그러니까 DI는 DIP를 따르는 방법 중 하나라는 의미인 것 같다!) 역할과 구현 중 ‘역할’에 의존해야 한다는 것과 같다.

어김없이 나오는 자동차 예시이다😁 운전자가 자동차 역할에 대해 알면 되는 것이지, 차종마다 내부 구조를 모두 알고 있고, 다른 차를 운전하려면 그 차에 대해 다시 구조를 이해하고 공부할 필요가 없다는 것이다. 운전자는 자동차 역할을 알기만 하면 어떤 차종이든 운전을 할 수 있으니까.

이처럼 구현체에 의존하게 되면 변경이 어려워진다. 인터페이스에 의존해야 유연하게 구연체를 변경할 수 있다.

문제점❗️

하지만 OCP에서 설명한 클라이언트(MemberService)는 인터페이스에 의존하지만, 구현 클래스도 동시에 의존하고 있다.(구현 클래스를 직접 선택하므로) 해당 코드를 한 번 더 보자.

public class MemberService {

    private MemberRepository memberRepository = new MemoryMemberRepository();
     										   //또는
											  = new JdbcMemberRepository();
		...
}

이것은 사실상 DIP 위반이다. MemoryMemberRepository에서 JdbcMemberRepository로 변경하려면, 클라이언트 코드를 변경해야 하기 때문이다.

객체 지향의 핵심은 다형성이라고 이야기했다. 하지만 다형성 만으로는 OCP와 DIP를 지킬 수 없다.

그럼 뭐 어떻게 해야 돼..❓

앞으로 배워 나갈 내용이 이 부분인 것이다.


오늘 강의는 굉장히 재미있게 들은 것 같다. ‘이러한 원칙들이 있고, 어떠한 문제점들이 존재한다’라는 내용을 배웠고, 앞으로 그 문제점을 어떻게 해결해 나갈지 궁금하면서도 기대가 된다.

profile
좋은 개발자가 되자.

0개의 댓글