Computer Science/기본

객체 지향 설계 5원칙 - SOLID

TwinParadox 2021. 3. 1. 17:03
728x90

이론적으로만 알고 있던 SOLID에 대해서 다시 한 번 정리해볼 필요가 있다고 생각해서 정리했다.

면접 준비할 때만 개념적으로 이해하고 있던 내용도 있고,

주변에서 항상 들어왔던 이야기를 바탕으로 최대한 신경 쓰려고 하는 원칙도 있었던 것 같다.

코딩 면접을 준비하는 사람들은 최소한 아래 용어에 대해서는 알고 있어야 한다.

 

이 글에서는 해당 용어에 대한 설명 외에도, 다른 분의 정리와 본인의 생각을 결합하여 끄적여봤다.

 

 

SRP, Single Responsibility Principle, 단일 책임 원칙

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

  • 하나의 책임??

    • 아무것도 모르던 시절엔 이 "책임"의 범위를 정하는 것이 가장 어려운 것 같다.

    • 크고 작은 것을 어떻게 정할 것인지에 대한 기준이 필요하지 않을까?

  • 이 책임을 정하는 기준은 변경에 초점을 두면 된다.

    • 만약 어떤 부분이 변경되었을 때 그 파급 효과가 적다면, 책임의 범위를 잘 정했고, 이 원칙을 잘 따르고 있다고 보자.

    • 프로젝트 경험이 많지 않은 커리어지만, 이런 개념을 전혀 고려하지 않고 처음 설계했던 학생 시절을 돌이켜 보면 클래스 하나가 모든 기능을 물고 있었다.
    • 이를 생각하면 이것만 생각해도 어느정도 감을 잡을 수 있을 것 같다.

 

OCP, Open/Close Principle, 개방-폐쇄 원칙

  • 소프트웨어 요소는 확장에 열려 있고 변경에는 닫혀 있어야 한다.

    • 단어는 이해했는데, 뭐 어쩌란 것인지 이해하기 어려웠던 부분.

    • 확장을 위해선 당연히 코드를 변경해야 하는데 변경에는 닫혀 있어야 한다는 말은, 아무것도 하지 말라는 말처럼 들릴 수 있다.

  • 다형성 여기에 끌고온다면?

  • 인터페이스를 설계하고 그것을 구현한 클래스 하나를 만들어 새로운 기능을 구현한다고 생각해보자.

    • 역할(인터페이스)과 구현(클래스)이 분리된다.

    • 인터페이스를 바꾸지 않고 구현한 클래스를 만들어 새로운 것을 하나 만들어낸다.

  • 문제가 될 수 있는 상황이 있다.
public class MemberService {
    // private MemberRepository memberRepository = new MemoryMemberRepository();
    private MemberRepostiroy memberRepository = new JdbcMemberRepository();
}
  • 구현 객체를 변경하기 위해 클라이언트 코드를 변경한다면, 결국 다형성을 사용하지만 OCP를 망친다.

    • 이 문제를 해결하려면 객체 생성, 연관 관계에 있어 어떤 작업이 필요하지 않을까...?

 

LSP, Liskov Substitution Principle, 리스코프 치환 원칙

  • S 타입의 객체 o1, 각각에 대응하는 T 타입 객체 o2가 있고, T 타입을 이용해 정의한 모든 프로그램 P에서 o2의 자리를 o1으로 치환해도 P의 행위가 변하지 않으면 S는 T의 하위 타입이다.
  • 프로그램의 객체는 프로그램의 정확성을 깨지 않으면서 하위 타입의 인스턴스를 바꿀 수 있어야 한다.

    • 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 하고, 다형성을 지원하기 위한 원칙, 인터페이스를 구현한 구현체는 믿고 사용하려면 원칙이 필요하다.

 

ISP, Interface Segregation Principle, 인터페이스 분리 원칙

  • 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다.
  • 즉, 특정 클라이언트를 위한 인터페이스 여럿이 범용적인 것 하나보다 낫다

    • 인터페이스 하나를 범용적으로 짜놓고, 그것을 클래스에서 구현했다고 생각해보자.
    • 어떤 동작 변화 하나가 몇몇 클래스에는 영향을 주지 않아야 하는데, 인터페이스 하나만으로 모든 것을 다 관리하게 되니, 모든 클래스에 영향을 준다.
    • 당장 사용하는 코드에서 기능끼리 분리해낼 수 있다면, 그 역할을 조금 더 나눠줄 수 있다면 더 나눠주자.

 

DIP, Dependency Inversion Princinple, 의존성 역전 원칙

  • 상위 수준 모듈은 하위 수준 모듈에 의존하지 않는다. 둘 모두 추상화에 의존한다.
  • 추상화에 의존하고 구체화에 의존하지 않는다.

    • 구현 클래스에 의존하지 말고, 인터페이스에 의존하게 하자. 그래야 클라이언트에 대해 유연한 변경이 가능하다.

public class MemberService {
    // private MemberRepository memberRepository = new MemoryMemberRepository();
    private MemberRepostiroy memberRepository = new JdbcMemberRepository();
}
  • 아까 그 코드가 다시 등장한다. 여기서 MemberService는 인터페이스에 의존하지만, 구현 클래스에도 동시에 의존하는 상황이 된다.

    • MemberService 클라이언트가 구현 클래스를 선택하면서 DIP 위반!

 

 

글을 마무리하며

SOLID는 용어만 보고 이해하면 개념을 안다고 이야기하기 보다는 그냥 외웠다고 보는 것이 맞지 않을까 싶다.  확실히 이해하는 것이 쉽지는 않겠지만, 그래도 개념을 한 번씩 생각해보는 건 실전에 적용해보는 것이라고 생각한다.

 

 

Reference

www.nextree.co.kr/p6960/

johngrib.github.io/wiki/SOLID/

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

728x90