객체지향 설계 - 5원칙(SOLID)

DragonTiger·2022년 1월 8일
0

SOLID란?

객체지향 설계는 긴 세월과 수많은 시행착오를 거치며 5가지 원칙이 정리되었다. 이것은 객체지향 설계의 5원칙이라고 하며, 앞글자를 따서 SOLID라고 한다.

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

이 원칙들은 응집도는 높이고 결합도는 낮추자는 고전 원칙을 객체 지향의 관점에서 재정립한 것으로 볼 수 있다.

SPR(Single Responsibility Principle) = 단일 책임 원칙

한 클래스는 하나의 책임만 가져야 한다.
위 그림은 아래 처럼 분리해야한다.

사진 출처 - SLENDER ANKLES's 개발블로그

중요한 기준은 변경이다. 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것
예) UI 변경, 객체의 생성과 사용을 분리

OCP(Open Closed Principle) = 개방 폐쇄 원칙

소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하지만 변경에 대해서는 닫혀 있어야 한다. 즉, 자신의 확장에는 열려있고, 주변의 변화에 대해서는 닫혀 있어야 한다는 것이다. 이것은 interface 를 통해 구현하여 해결한다.


public class MemberServiceImpl implements MemberService{

// 여기서 MemberRepository는 인터페이스

 //private MemberRepository memberRepository = new MemoryMemberRepository();
 private MemberRepository memberRepository = new JdbcMemberRepository();

}

jdbc로 변경하려면 코드 수정이 불가피하다.
구현 객체를 변경하려면 클라이언트 코드를 변경해야 한다.
분명 다형성을 사용했지만 OCP 원칙을 지킬 수 없다
객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요하다.

//설정자
public class AppConfig {

    public MemberService memberService(){
        return new MemberServiceImpl(new JdbcMemberRepository());
    }
}

public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

위 코드 AppConfig클래스라는 설정자클래스에서만 구현 객체를 갈아끼워주면 기존의 코드변경은 필요없다.
얼마든지 클래스를 만들어서 확장시킬순있지만, 기존의 코드에 변경이없으므로 OCP 원칙에 위배되지않는다.
고로 확장에는 열려있으며, 변경에는 닫혀있다.

LSP(Liskov Substitution Principle) = 리스코프 치환 원칙

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

  • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀
    수 있어야 한다
  • 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것, 다형성을 지원하기 위
    한 원칙, 인터페이스를 구현한 구현체는 믿고 사용하려면, 이 원칙이 필요하다.
  • 단순히 컴파일에 성공하는 것을 넘어서는 이야기
  • 자동차 인터페이스의 엑셀은 앞으로 가라는 기능, 뒤로 가게 구현하면 LSP 위반, 느리
    더라도 앞으로 가야함
Person 멍멍이 = new Dog();

멍멍이란 강아지가 태어나서 두발로걷기(), 사람들과 대화하기() 
이상하다,규약이 맞지않는다.

Animal 멍멍이 = new Dog();

멍멍이란 강아지가 태어나서 네발로걷기(), 멍멍!! 짖기()
이제는 말이된다.

ISP(Interface Segregation Principle) = 인터페이스 분리 원칙

인터페이스 분리 원칙은 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다는 원칙이다.인터페이스 분리 원칙은 큰 덩어리의 인터페이스들을 구체적이고 작은 단위들로 분리시킴으로써 클라이언트들이 꼭 필요한 메서드들만 이용할 수 있게 한다. 이와 같은 작은 단위들을 역할 인터페이스라고도 부른다. 인터페이스 분리 원칙을 통해 시스템의 내부 의존성을 약화시켜 리팩토링, 수정, 재배포를 쉽게 할 수 있다.

  • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다
  • 분리하면 인터페이스 자체가 변해도 클라이언트에 영향을 주지 않음
  • 인터페이스가 명확해지고, 대체 가능성이 높아진다

운전자가 자동차를 운전한다. 라는 명제를 객체간 관계로 비유하면 자동차에 대한 인터페이스, 운전자에 대한 인터페이스를 각각 분리하는 것이다.

그럼 운전자는 아빠가 될수도 있고, 택시기사,버스기사가 될수 있다. 자동차는 버스가 될 수도 있고, 택시가 될수도 있고, 스포츠카가 될 수도 있다. 확장성이 커지는 셈이다.

DIP(Dependency Inversion Principle) = 의존 역전 원칙

프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안된다.” 의존성 주입은 이 원칙
을 따르는 방법 중 하나다.
• 쉽게 이야기해서 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻
• 역할(Role)에 의존하게 해야 한다는 것과 같다. 객체 세상도 클라이언트
가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있다! 구현체에 의존하게 되면 변
경이 아주 어려워진다.

위에 OCP 코드를 가져와서 보면


public class MemberServiceImpl implements MemberService{

// 여기서 MemberRepository는 인터페이스

 private MemberRepository memberRepository = new JdbcMemberRepository();

}

위 코드에서 MemberServiceImpl는 memberRepository도 의존하고 JdbcMemberRepository클래스도 의존한다.
DIP를 위반하는 코드이다.

//설정자
public class AppConfig {

    public MemberService memberService(){
        return new MemberServiceImpl(new JdbcMemberRepository());
    }
}

public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

위 처럼 MemberServiceImpl는 MemberRepository라는 인터페이스만을 의존하게해서

MemberServiceImpl는 AppConfig에서 유현하게 구현체를 바꿔 낄수있다.
인터페이스를 의존해서 구현이 유연해지고 변경에 자유롭다.

참고
스프링 핵심 원리 - 기본편

profile
take the bull by the horns

0개의 댓글