Spring/Spring 이론

Spring + java 이론 :) SOLID원칙, 객체지향 설계 5원칙 **백번천번만번십만번 보기**

euncheol kim 2022. 5. 2. 23:49

goal

객체지향 설계 원칙 (객체지향 설계 5원칙)의 SOLID원칙을 이해한다.

1 ] 객체지향 설계의 5원칙, SOLID원칙


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


1. SRP (단일 책임 원칙)


[1] 개념

  • 모든 클래스는 하나의 책임만 가지며, 그 책임을 완전히 캡슐화해야 함을 말함
  • 클래스가 제공하는 모든 기능은 이 책임과 주의깊게 부합해야 한다.
    • 예로, 보고서 출력/편집의 모듈을 만든다고 가정한다.
    • 보고서 편집의 사유는 두 가지로 변경될 수 있다.
      • 첫 번째 : 보고서의 형식이 바뀐 경우
      • 두 번째 : 보고서의 내용이 바뀐 경우
    • 분류된 각각의 것이 편집 목적의 책임을 져야하기 때문에 클래스나 모듈로 나누어야 한다.
    • 다른 시기다른 이유로 변경되어야하는 두 가지를 묶는 것은 나쁜 설계일 수 있다.

[2] 한 클래스를 한 관심사에 집중하도록 유지하는 것이 중요한 이유

  • 클래스가 더욱 단단해지기 때문이다.
    • [1]개념의 예를 가져와 더 살펴본다면, 편집 과정에 변경이 일어나면, 같은 클래스의 일부로 있는 출력 코드가 망가질 위험이 대단히 높아진다.


2. OCP (개방 폐쇄 원칙)


[1] 개념

  • 모든 클래스가 열려있음과 닫혀있음이 확실해야한다.
    • 열린 경우 : 클래스의 확장이 자유로워야 한다.
    • 닫힌 경우 : 프로그램의 변경이 일어나더라도 핵심 기능을 구현한 코드(핵심 기능을 담은 클래스)는 변화가 일어나면 안 된다.



3. LSP (리스코프 치환 원칙) - 가장 애매하고 오해하기 쉬움


참고자료 : [SOLID] 리스코프 치환 원칙(LSP)란?, https://steady-coding.tistory.com/383, 제이온(J.ON)

[1] 개념

  • 상위 타입의 객체하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.
  • 다른말로, 'B가 A의 자식 타입이면 부모 타입인 A객체는 자식 타입인 B로 치환해도, 작동에 문제가 없다.'
  • 개발자의 관점으로,
    • 부모 클래스 타입인 A를 사용하는 기존의 프로그램 코드가 자식 클래스 B로 대입 시켰을 때도,
    • 문제 없이 작동하도록 하기 위해서, 자식 클래스는 부모 클래스가 따르던 계약 사항을 자식도 따라야 한다.

예시코드 "정사각형은 직사각형이 될 수 있지만 직사각형은 정사각형이 될 수 없습니다."

  • Rectangle.java <직각사각형>
public class Rectangle {
    private int width;
    private int height;

    public void setWidth(final int width){
        this.width = width;
    }
    public void setHeight(final int height) {
        this.height = height;
    }
    public int getWidth() {
        return width;
    }
    public int getHeight() {
        return height;
    }
}

  • Square.java <직각사각형을 상속받아 만든 정사각형>
public class Square extends Rectangle {
    @Override
    public void setWidth(final int width) {
        super.setWidth(width);
        super.setHeight(width);
    }

    @Override
    public void setHeight(final int height) {
        super.setWidth(height);
        super.setHeight(height);
    }
}

※ 의미 :: 정사각형은 세변의 길이가 같아야 하므로, Rectangle클래스의 width와 height를 재정의함

  • 다른 클래스에서 Retangle클래스를 이용

# 메소드의 의미 : "직사각형의 세로 길이는 가로보다 길어야한다."

public void increaseHeight (final Rectangle rectangle) {
    if (rectangle.getHeight() <= rectangle.getWidth()) {
        rectangle.setHeight(rectangle.getWidth() + 1);
    }
}

※ 의미 : rectangle객체의 세로 <= 가로인 경우, 객체의 세로에 기존 가로의 길이 + 1을 하는 코드

  • 정사각형이 아닌 직사각형에 대해서는 위 메소드가 올바르게 작동한다.
    • new Rectangle이 객체로 들어간 경우
  • 정사각형의 길이는 가로 == 세로이므로 위 메소드를 실행하면 가로와 세로의 길이가 모두 1씩 증가한다.
    • new Square이 객체로 들어간 경우
  • 즉, ''메소드 실행 후, 직사각형의 길이는 가로보다 세로가 길어야 한다''라는 가정이 깨지게 되는 것
  • 이를 해결하기 위해서 instanceof를 통해 타입 비교를 실시한다.



  • increaseHeight메소드의 수정
public void increaseHeight (final Rectangle rectangle) {
    if (rectangle instanceof Square) {
        throw new IllegalStateException();
    }

    if (rectangle.getHeight() <= rectangle.getWidth()) {
        rectangle.setHeight(rectangle.getWidth() + 1);
    }
}

하지만, 이 코드는 OCP에 어긋나는 코드이다. 왜냐하면 increaseHeight()가 확장에는 열려 있지 않기 때문이다.

따라서, Square 클래스는 Rectangle클래스를 상속받으면 안 된다.


LSP 정리
  • 상위 타입의 객체하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.를 구현 클래스에 대입하여 이야기해보면
    • 상위 타입의 클래스 : Rectangle클래스의 setHeight()메소드의 명세는 아래와 같다.
      • 매개변수로 값이 들어오면 세로의 길이가 변한다
      • 가로의 길이는 변하지 않는다
    • 하위 타입의 클래스 : Square클래스는 setHeight()메소드의 명세는 아래와 같다.
      • 매개변수로 값이 들어오면 세로의 길이가 변한다
      • 매개변수로 값이 들어오면 가로의 길이도 변한다
  • 즉, 상위 타입에서 정한 명세를 하위 타입에서도 그대로 지킬 수 있을 때상속을 진행해야한다.
  • 혹은, 상속을 하게 된다면 해당 메소드를 정의하지 않아야 한다.

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


  • 클라이언트가 자신이 이용하지 않는 메소드에 의존하지 않아야 한다는 원칙이다.
  • 큰 덩어리의 인터페이스들을 구체적이고 작은 단위들로 분리시켜 클라이언트들이 꼭 필요한 메소드들만 이용할 수 있게 하는 것
  • 작은 단위로 나뉜 인터페이스는 역할 인터페이스라고도 한다.

5. DIP (의존관계 역전 원칙)

참고자료 : Spring + java 이론 :) 의존성 주입의 의미와 개선 코드, https://kimeuncheol.tistory.com/42, 내자료​