본문 바로가기
Web/spring study

[Spring] 좋은 객체 지향 설계 5가지 원칙(SOLID)

by 장인이 2022. 5. 26.

0. SOLID란?

- 로버트 마틴(클린코드 저자로 유명)이 좋은 객체 지향 설계의 5가지 원칙을 정리함

 

SRP: 단일 책임 원칙(single responsibility principle)

OCP: 개방-폐쇄 원칙(open/closed principle)

LSP: 리스코프 치환 원칙(liskov substitution principle)

ISP: 인터페이스 분리 원칙(interface segregation principle)

DIP: 의존관계 역전 원칙(dependency inversion principle)

 

1.  SRP 단일 책임 원칙

(Single Responsibility Principle)

 

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

- 하나의 책임이라고 하면, 기준이 모호해 질 수 있다.

  -> 사람에 따라 책임이 클수도, 작을 수도 있다.

  -> 상황, 경우에 따라 다르다

 

- 그럼 어떻게 판단하느냐???

  -> "변경"이 중요한 기준이다.

  -> 변경이 있을 때 파급 효과가 적다면, 단일 책임 원칙을 잘 지킨 것

 

ex) 사소한 UI 하나를 변경하는데, 여러 파일들을 다 고쳐야 한다?

    -> 잘못 설계된 것

 

- 결국 클래스 책임 범위를 너무 작게 잡으면, 파일이 많이 생성됨

- 너무 크게 잡으면, 단일 책임 원칙을 지킬 수 없게 됨

  -> 적절하게 조절하는 것이 개발자의 경험에 의한 것

 

2. OCP 개방-폐쇄 원칙

(Open/Closed Principle)

 

- 가장 중요한 원칙

 

- 소프트웨어 요소들은 확장은 열려 있으나, 변경에는 닫혀 있어야 함

  -> 이게 무슨 소리일까??

  -> 코드를 변경 안하고 기능을 추가한다고?

 

- 이전 게시글에서 적었던 "다형성"을 생각해보자.

- 다형성을 활용한다면, OCP를 지키면서 코드를 확장할 수 있다.

  -> 기존의 인터페이스를 구현한 새로운 클래스를 만들어서, 새로운 기능을 구현한다면?

  -> 새로운 클래스, 즉 코드를 "추가"한 것이므로 확장을 한거지, 변경한 것은 X

 

public class MemberService {
	private MemberRepository memberRepository = new MemoryMemberRepository();
}
public class MemberService {
	// private MemberRepository memberRepository = new MemoryMemberRepository();
    private MemberRepository memberRepository = new JdbcMemberRepository();
}

<이전 게시글에서 다향성의 예시로 들은 코드>

- 어? 근데 위 코드를 보면 구현 객체를 변경하기 위해 클라이언트 코드를 변경했다.

  -> 다형성을 사용했지만 OCP 원칙을 지킬 수 없다.. (결국 클라이언트 코드를 수정했지 때문)

 

- 따라서 객체를 생성하고, 연관관계 맺어주는 설정자가 필요하다.

 

3. LSP 리스코프 치환 원칙

(Liskov Substitution Principle)

 

- 리스코프 치환 원칙은 어떻게 보면 오해하기 쉬운 원칙

- "B가 A의 자식 타입이면 부모 타입인 A객체는 자식 타입인 B로 치환해도, 작동에 문제가 없어야 함"

 

- 즉, A와 B 부모 자식에 대한 정의가 논리적으로 확실해야, 자식 클래스인 B로 치환해도 문제가 없다는 내용이다.

 

- 이를 더 자세히 풀어보면,

-> 부모 클래스 A를 타입으로 하고, 자식 클래스 B를 대입시킬 경우에도 문제없이 작동하기 위해서(다형성) 자식클래스는 부모클래스가 따르던 논리적 조건(계약 사항)을 따라야 한다.

(계약 사항: 클래스의 멤버가 어떻게 작동하는 지 대한 구현 조건 사항 등 의미)

 

- 즉, 최소한 자식 클래스 B자신의 부모 클래스 A에서 가능한 모든 행위를 수행할 수 있어야 한다.

 

ex) 동물이라는 부모 클래스를 인간이라는 자식 클래스로 상속받는 것은 가능하다.

    -> 인간은 동물의 특징을 모두 수행할 수 있다.

 

ex) 직사각형이라는 부모 클래스를 정사각형이라는 자식 클래스로 상속받는 것은 LSP를 위배한다.

    -> 정사각형은 직사각형의 특징을 모두 수행할 수 없다.

    -> 반례) 직사각형은 세로의 길이만 20 늘릴 수 있지만, 정사각형은 불가능하다.

    (정사각형은 가로와 세로의 길이가 무조건 같아야 하므로)

 

4. ISP 인터페이스 분리 원칙

(Interface segregation principle)

 

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

 

ex) 설계를

1. 자동차 인터페이스 / 사용자 클라이언트

2. 운전 인터페이스, 정비 인터페이스 / 운전자 클라이언트, 정비사 클라이언트

 

- 1번과 다르게 2번의 경우는 기능을 분리함, 이때의 이점은?

  -> 정비 인터페이스를 변경해도, 운전자 클라이언트에 영향을 주지 않음

  -> 인터페이스 각각이 명확해지며, 대체 가능성이 높아짐

 

5. DIP 의존관계 역전 원칙

(Dependency inversion principle)

 

- DIP는 "추상화에 의존하고, 구체화에 의존하면 안된다"라는 내용을 따르는 방법

- 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻

 

- 즉, 클라이언트가 인터페이스에 의존해야지만, 구현체를 유연하게 변경할 수 있게 됨

  -> 구현체에 의존한다면? 변경이 어려워진다.

 

- 그런데 OCP에서 설명했던 MemberService예시를 다시 보면

- 인터페이스에 의존하지만, 동시에 구현 클래스도 의존한다.

  -> Why? MemberService 클라이언트가 구현 클래스를 직접 선택했기 때문이다.

  ex) MemberRepository m = new MemoryMemberRepository();

 

- 이는 DIP를 위반하는 행위이다.

 

6. 그럼 어떻게 해야함?

- 객체 지향의 핵심은 다형성이고, SOLID 원칙도 알게 되었다.

- 하지만 다형성 만으로는, 부품을 갈아 끼우는 것처럼 코딩하기는 힘들다

(구현 객체를 변경할 때, 클라이언트 코드도 변경해야 함)

 

- 즉, 다형성 만으로는 OCP, DIP를 지킬 수 없다는 것이 결론

- 뭔가 다른 요소가 필요하다....

 

 


위 내용은 김영한 님의 인프런 강의 "스프링 핵심 원리 - 기본편"의 내용과 강의자료를 토대로 작성된 게시글입니다.

강의 링크:

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

댓글