객체지향 SOLID 원칙 (수정중)

푸른하늘·2022년 10월 2일
0
post-thumbnail

원칙

어떠한 이론 및 행동에서 한결같이 지켜야 하는 기본적인 규칙 및 법칙

우리는 삶을 살아가기 위해 주어진 원리/원칙 아래서 생활하고 있습니다.

이러한 원칙을 "왜"? 지켜야 할까요?

좁은 의미로는 사고방식, 신념, 가치관 될 수가 있고

넓은 의미로는 국가의 통치 이념 통치 방법으로 될 수가 있습니다.

즉, 이러한 원칙을 적용하는 이유는 개인의 "삶"과 국민의 "삶"을 효율적으로 향상시키 때문입니다.

소프트웨어 관점의 원칙

기계의 행위를 쉽게 변경할 수 있도록 하는 것

왜?

소프트웨어 : Soft (부드러운 ) Ware(제품) 입니다.

결국 소프트웨어는 부드러움을 가지고 있는 제품이기 때문에 소프트웨어의 본연의 법칙을 추구하려면 반드시 "부드러워"야 한다.

변경이 쉽게 할려면, 기획 이나 디자인 쪽에서 기능에 대한 생각을 바꾸면 변경사항을 바꾸는데 어려움이 없어야 합니다.

소프트웨어 개발 비용의 증가는 바로 이러한 형태의 어려움에 있기 때문입니다.

SOLID 원칙

변경에 유연하며 , 이해하기 쉽다 , 많은 소프트웨어에서 사용할 수 있는 컴포넌트의 기반이 된다.

좋은 소프트웨어 시스템?

좋은 소프트웨어 시스템은 Clean Code 로 부터 나옵니다.
극단적으로 음식을 예로 들자면,

좋은 요리사 -> 좋은재료 -> 좋은 음식
좋은 요리사 -> 나쁜재료 -> 좋은 음식 OR 나쁜 음식
초보 요리사 -> 좋은재료 -> 좋은 음식 OR 나쁜 음식
초보 요리사 -> 나쁜재료 -> 나쁜 음식

SOLID는 좋은 요리사로 좋은 재료를 만드는 것을 의미합니다.

SOLID의 원칙 5가지

단일 책임 원칙 : Single Responsibility Principle(SRP)

개방 폐쇄의 원칙 : Open Close Principle (OCP)

리스코프 교체의 원칙 : Liskov Substitution Principle(LSP)

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

의존 관계 역전의 원칙 : Dependency Inversion Principle(ISP)

Single Responsibility Principle (SRP)

클래스르 변경하는 이유는 단 한가지 여야 한다.

"하나의 클래스는은 오직 하나 , 오직 하나의 액터에 대해서만 책임져야 한다."

모듈 : 소스 파일
액터 : 해당 변경을 요청하는 한 명 이상의 사람들 (클래스 ,모듈)

즉, 하나의 클래스는 하나의 기능을 가지는게 좋다 입니다.

Before SRP

Car Class 안에 하나의 클래스안에 하나의 액터만 책임을 저야하는데 총 4개를 책임지고 있다.

이는, SRP 원칙에 위배된다.

만약 , Drive 가 사용하는 코드가 변경이 된다면 Drive 뿐만 아니라 ,Car/ CarWash Mechanic 을
재컴파일 재배포를 해야합니다.

이처럼 클래스에 많은 책임이 있는 경우 책임 중 하나를 변경하면 사용자가 모르는 사이에 다른 책임에 영향을 줄 수 있으므로 버그가 발생할 가능성이 높아집니다.

또한 응집도 처럼 동일한 기능이 잘 뭉쳐져 있는 것이 제일 좋다

After SRP

이처럼 각 클래스에안에 하나의 액터만 책임을 진행이 된다면
변경이 발생하더라도 다른 관련 없는 동작에 영향을 미치지 않습니다.

  • 쉬운 테스트 : 책임이 하나인 클래스는 테스트 케이스가 줄어들기 때문에 테스트가 쉬워진다.
  • 낮은 결합 : 단일 클래스의 기능이 적어져 종속성이 줄어든다.
  • 쉬운 검색 : 작고 잘 조직된 클래스는 검색하기 쉽다.
  • 구현하기 쉬움 : 하나의 책임을 가지고 있기때문에 구현하기가 쉽다.

Open Close Principle (OCP)

소프트웨어 객체는 확장(상속)에는 열러 있어야 하고, 변경에는 닫혀 있어야 한다.

"기능을 추가할 때 기존의 코드를 변경하지 않고 추가할 수 있어야 한다"

만약 당신이 요구사항을 살짝 확장하는데 소프트웨어를 엄청나게 수정해야 한다면?

  • 개발 비용 증가
  • 시간적 요소 증가
  • 흥미 유발 감소

Before OCP

File을 DB에 저장할려면 FileStorage 클래스를 수정해야 합니다.
이는 FileStorage 클래스가 확장과 수정에 대하여 열려있다는 것을 보여주는 것 입니다.

After OCP

save() 추상 메소드를 가지고 있는 Firestorage를 추상 클래스를 정의하고 , File 객체를 저장하기 위한 ORACLE , MYSQL 클래스를 만듭니다. 이 클래스는 FileStorage 클래스를 상속하고 있습니다.

이처럼 추상클래스를 이용해 상속을 하면 FileStorage에 수정 없이 DB를 추가할 수 있게 되어 OCP를 따를수 있습니다.

Liskov Substitution Principle (LSP)

서브타입은 언제나 기반 타입으로 교체할 수 있어야 한다

"상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다."
"자식 클래스는 부모 클래스를 대체할 수 있어야 한다."

대표적인 문제인 Rectangle / Square 문제다

직사각형 -> 정사각형 (x)
정사각형 -> 직사각형 (O)

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;
    }
}

직사각형 객체를 상속받아서 아래처럼 정사각형 객체를 재정의 할 수 있습니다.

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);
    }
}

다만, 정사각형은 가로와 세로의 길이가 같으므로 setWidth()나 setHeight()를 호출하면 가로와 세로를 모두 값을 바꿔줘야해서 메소드를 재정의하였습니다.

    public void increaseHeight(final Rectangle rectangle) {
        if (rectangle.getHeight() <= rectangle.getWidth()) {
            rectangle.setHeight(rectangle.getWidth() + 1);
        }
    }
  • 직사각형의 가로와 세로를 비교한다
  • 세로가 가로보다 짧거나 같다면 가로의 길이에 1을 더한 만큼의 길이를 갖게 만드는 역할한다.
  • 정사각형이 아닌 직사각형에 대해서는 위 메소드가 올바로 작동 합니다.

하지만, 정사각형의 경우는 다르다.
정사각형은 항상 가로와 세로의 길이가 같으므로 위 메소드를 실행하면 가로와 세로 모두 1씩 증가한다.

즉, 우리가 원하는 "메소드 실행 후, 직사각형의 길이는 가로보다 세로가 길어야 한다."는 가정이 깨지게 되는 것입니다. 따라서, instanceof를 통해 타입 비교를 해야 합니다.

    public void increaseHeight(final Rectangle rectangle) {
        if (rectangle instanceof Square) {
            throw new IllegalStateException();
        }

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

이렇게 해당 도형이 정사각형일 경우 익셉션을 발생시키는 식으로 코드를 수정할 수 있습니다.
하지만, 이것은 개방 폐쇄 원칙에 어긋나는 코드입니다.
왜냐하면, increaseHeight()가 확장에는 열려 있지 않기 때문이죠.

즉 , 상속을 할때는 치환을 해도 정상작동이 되도록 만들어야한다.
만약 그렇지 않을경우에는 상속을 받으면 안된다.

Interface Segregation Principle(ISP)

어떤 클래스가 인터페이스를

"하나의 일반적인 인터페이스 보다 여러개의 구체적인 인터페이스를 사용해라"

profile
Developer-Android-CK

0개의 댓글