OOP - 객체지향설계 5원칙 SOLID를 파훼쳐보자!

devdo·2021년 12월 16일
0

Spring

목록 보기
1/9
post-thumbnail

김영한 스프링 핵심 원리 출처

객체지향을 위한 SOLID 원칙을 알아보자.

한마디로, 결합도와 응집도에 대한 얘기! 결합도를 낮추고 응집도를 높여라!

결합도는 모듈(클래스) 간의 상호 의존 정도로서 결합도가 낮으면 모듈 간의 상호 의존성이 줄어들어 객체의 재사용이나 수정, 유지보수가 용이하다.

응집도는 하나의 모듈 내부에 존재하는 구성 요소들의 기능적 관련성으로, 응집도가 높은 모듈은 하나의 책임에 집중하고 독립성이 높아져 재사용이나 기능의 수정, 유지보수가 용이하다.

여기서, <클린 아키텍쳐> 로버트.C.마틴 책을 빌려고 설명하고자 한다.


1. SRP 단일 책임 원칙

Single Resposibility Principle

“한 클래스는 하나의 책임만 가져야 한다.”
"어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다" - 로버트 C.마틴

객체에 책임(역할)은 최소화 하자. 즉, 덩치가 큰 클래스는 쪼개라.

왜냐? 따로 책임이 많은 클래스를 호출할 때 필요하지 않는 내용까지 가져오게 되니까.

예시.
클라이언트 객체는 실행만 하는 책임만 담당해야 한다. ex) MemberServiceImpl
외부에서 의존성을 주입하는 역할은 따로 둔다. ex) AppConfig

public class AppConfig {
    // AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
    // 그리고 생성한 객체 인스턴스의 참조를 생성자를 통해서 주입해준다.(생성자 주입 방식)

    // 생성자 주입 방식
    // 리팩토링 중복부분 -> 메서드로 다른 구현체 만듦
    // 효과 : AppConfig을 보면 역할과 구현클래스가 한눈에 들어온다.
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    private MemoryMemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    public DiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }
}

2. OCP 개방 폐쇄 원칙

Open Cloesd Principle

“소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀 있어야 한다(변경할 필요가 없다.)

인터페이스를 구현한 클래스가 내부 기능을 변경해도, 또는 새로운 클래스들이 확장해도 클라이언트 클래스는 변경되지 않는다.

  • ex) Service 인터페이스를 의존한 Controller가 Service 인터페이스 구현한 ServiceImpl의 update()를 사용한다. ServiceImpl에서 update() 기능 구현을 변경해도 Service 인터페이스를 의존한 Controller에는 영향이 없다.

  • ex) Service 인터페이스 구현한 Service1Impl, Service2Impl 클래스들이 여러 개 확장해도 Service 인터페이스를 의존하는 Controller는 변경에 닫혀 있다.

  • ex) JDBC - 데이터베이스가 오라클에서 MySQL로 바뀌더라도 Connection을 설정하는 부분 외에는 따로 수정할 필요가 없다

  • 개방 폐쇠 원칙을 무시하고 프로그램을 작성하면 객체 지향 프로그래밍의 가장 큰 장점인 유연성, 재사용성, 유지보수성 등을 얻을 수 없다.

  • 솔직히 객체지향 원리중 가장 중요한 핵심 원칙!


3. LSP 리스코프 치환 원칙

Liskov Substiution Principle

"서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다." - 로버트 C.마틴

다형성에서 하위 구현 클래스는 인터페이스 규약을 다 지켜야 한다는 말.
대신, 클래스는 계층도가 아닌 분류도를 따라야 한다고 말함.

  • ex) 상속은 조직도나 계층도( 아버지 - 딸 ) 가 아닌 분류도( 펭귄 - 뽀로로 )가 되어야 한다.

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

Interface Segregation Principle

"클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안 된다." - 로버트 C.마틴

인터페이스 메서드 최소화하자.

SRP랑 같은 문제를 두고 다른 해결방식이다.(개인적으로 SRP를 하면 같이 따라오게 되어 있다고 본다.)

도메인 별로 클래스를 분리하면 그에 해당하는 도메인 인터페이스의 메서드도 줄어들 거니까.

  • 자동차 인터페이스 -> 운전 인터페이스, 정비 인터페이스로 분리
  • 사용자 클라이언트 -> 운전자 클라이언트, 정비사 클라이언트로 분리
  • 분리하면 정비 인터페이스 자체가 변해도 운전자 클라이언트에 영향을 주지 않음
  • 인터페이스가 명확해지고, 대체 가능성이 높아진다.

5. DIP 의존관계 역전 원칙

Dependency Inversion Principle

“프로그래머는 추상화(인터페이스)에 의존해야지 구체화에 의존하면 안된다. 의존성 주입은 이 원칙을 따르는 방법 중 하나다.”

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

  • 역전은 의존성 방향이 절차지향 프로그래밍때 상위객체가 하위 객제에 의존하는 것과 반대되기 때문이다.(인터페이스를 통해 하위객체가 상위객체에 의존하게 한다)

  • 객체 세상도 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있고 구현체 변경에 닫혀 있을 수 있다. (OCP도 해결됨.)

  • 구현체에 의존하게 되면 변경이 아주 어려워진다.

// DIP , OCP 위반
public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

// 의존성 주입, 여기서 정책을 바꿔 discountPolicy가 뭔지 알 필요없다. 외부에서 주입해주니까. 코드에 의존 x, 인터페이스만 의존
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();    // OrderServiceImp가 구현클래스에 의존한다.
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();      // 정책이 바뀌어 코드까지 바뀐다. DIP 위반!
    // FixDiscountPolicy -> RateDiscountPolicy 변경은?


...
}

정리

인터페이스가 역할(책임)을, 구현클래스가 구현으로 철저한 분리를 해야한다는 게 객체지향의 핵심임을 김영한님 인프런 강의를 통해 알게 되었다. 그런 의미에서 객체지향원리 중 OCP, ISP, DIP가 가장 의미가 와닿았고 그다음 SRP, LSP가 와닿게 되었다.

어려운 개념을 김영한님 강의로 잘 정리가 되어서 괜힌 김영한 김영한 하는 게 아니구나를 느꼈다.



소스

https://github.com/mooh2jj/springcore1.git

profile
배운 것을 기록합니다.

0개의 댓글