[도서] 개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴 - SOLID

Junseo Kim·2021년 3월 7일
0

[도서]

목록 보기
4/5

설계 원칙: SOLID

단일 책임 원칙

객체 지향의 기본은 책임을 객체에게 할당하는 데 있다.

클래스는 단 한 개의 책임을 가져야 한다.

클래스가 여러 책임을 갖게 되면 그 클래스는 각 책임마다 변경되는 이유가 발생할 수 있기 때문에 클래스가 한 개의 이유로만 변경되려면 클래스는 한 개의 책임만을 가져야 한다.(클래스를 변경하는 이유는 단 한 개여야 한다.) 단일 책임 원칙을 지키지 않으면 발생하는 문제점이 있다.

  • 책임의 개수가 많아질수록 한 책임의 기능 변화가 다른 책임에 주는 영향은 비례해서 증가한다.

  • 재사용을 어렵게 한다. 여러 책임이 한 클래스에 있으면 실제 사용하지 않는 기능까지 필요로하게 될 수 있다.

책임의 단위는 변화되는 부분과 관련된다. 서로 다른 이유로 바뀌는 책임들이 한 클래스에 포함되어 있다면 이 클래스는 단일 책임 원칙을 어기고 있다고 볼 수 있다. 메서드를 실행하는 것이 누구인지 확인해보면 보다 쉽게 책임을 분리시킬 수 있다. 클래스의 사용자들이 서로 다른 메서드를 사용한다면 각 메서드는 다른 책임에 속할 가능성이 높다.

개방 폐쇄 원칙

확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.
기능을 변경하거나 확장할 수 있으면서 그 기능을 사용하는 코드는 수정하지 않는다.

확장되는 부분(변화되는 부분)을 추상화해서 사용한다. 추상 타입을 사용하면 새로운 기능이 추가되거나 변화되더라도 사용자 쪽의 코드를 변경시키지 않는다.

개방 폐쇄 원칙은 상속을 이용해서도 구현할 수 있다. 하위 클래스가 부모 클래스를 상속받아 메서드를 오버라이딩하여 기능을 확장시켜도 부모 클래스의 코드는 바뀌지 않는다.

추상화와 다형성을 이용해서 개방 폐쇄 원칙을 구현하기 때문에 추상화와 다형성이 제대로 지켜지지 않은 코드는 개방 폐쇄 원칙을 어기게 된다. 개방 폐쇄 원칙을 어기는 코드의 특징이 있다.

  • 다운 캐스팅을 한다. instanceof와 같은 타입 확인 연산자가 사용된다면 개방 폐쇄 원칙을 지키지 않을 가능성이 높다. 타입 캐스팅 후 실행하는 메서드가 변화 대상인지 확인해봐야한다.
  • 비슷한 if-else 블록이 존재한다. 이런 경우 추상화 할 수 있는지 확인한다.

개방 폐쇄 원칙은 변경의 유연함과 관련된 원칙이다. 기존 기능을 확장하기 위해 기존 코드를 수정해 줘야 한다면 새로운 기능을 추가하는 것이 힘들어진다. 변화되는 부분을 추상화하여 사용자 입장에서는 코드 변화가 없어야한다.

리스코프 치환 원칙

리스코프 치환 원칙은 개방 폐쇄 원칙을 받쳐 주는 다형성에 관한 원칙을 제공한다.

상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.

상위 타입 SuperClass와 하위 타입 SubClass가 존재할 때 아래와 같은 메서드가 제대로 동작해야한다.

public void someMethod(SuperClass sc) {
    sc.someMethod();
}

somMethod(new SubClass()); // 하위 타입 객체를 전달해도 정상적으로 동작해야 함.

리스코프 치환 원칙은 기능의 명세에 대한 내용이다. 기능 실행의 명세와 관련된 위반 사례는 아래와 같다.

  • 명시된 명세에서 벗어난 값을 리턴한다.(ex. 명세의 리턴값이 0이상인데 하위 타입에서 음수를 리턴)
  • 명시된 명세에서 벗어난 익셉션을 발생한다.(ex. 명세의 익셉션이 IOException인데 하위 타입에서 IllegalArgumentException을 발생)
  • 명시된 명세에서 벗어난 기능을 수행한다.

하위 타입이 명세에 벗어난 동작을 하게 되면 이 명세에 기반한 코드는 비정상적으로 동작할 수 있으므로 하위 타입은 상위 타입에서 정의한 명세를 벗어나지 않는 범위에서 구현해야 한다.

리스코프 치환 원칙은 확장에 관한 것이기도 하다. 리스코프 치환 원칙을 어기면 개방 폐쇄 원칙을 어길 가능성이 높아진다.

instanceof 연산자가 사용된다면 리스코프 치환 원칙이 위반된다고 볼 수 있다.(상위 타입만을 이용해서 프로그래밍을 할 수 없다는 뜻. 즉, 하위 타입이 상위 타입을 대체할 수 없으므로 새로운 하위 타입이 추가될때마다 코드를 수정해야할 가능성이 높아짐)

인터페이스 분리 원칙

인터페이스는 그 인터페이스를 사용하는 클라이언트를 기준으로 분리해야 한다.(클라이언트는 자신이 사용하는 메서드에만 의존해야 한다.)

인터페이스 분리를 하지 않으면 한 멤버 변수에 대한 시그니처의 변경이 발생한 경우 모든 코드를 재컴파일해야 할 수 있다. 각 클라이언트가 필요로 하는 인터페이스들로 분리함으로써, 각 클라이언트가 사용하지 않는 인터페이스에 변경이 발생하더라도 영향을 받지 않도록 만들어야 한다.(클라이언트 간의 영향 최소화)

자바에서는 JVM이 .class 파일을 로딩하는 과정에서 동적으로 링크과정을 거치기 때문에 '사용하지 않는 인터페이스 변경에 의해 발생하는 소스 재컴파일'문제는 발생하지 않는다.

하지만 용도에 맞게 인터페이스를 분리하는 것은 다일 책임 원칙과 연결된다. 따라서 자바를 사용할지라도 클라이언트 입장에서 사용하는 기능만 제공하도록 인터페이스를 분리해줘야한다.

인터페이스 분리 원칙은 클라이언트 입장에서 인터페이스를 분리하하는 원칙이다. 각 클라이언트가 사용하는 기능을 중심으로 인터페이스를 분리함으로써, 클라이언트로부터 발생하는 인터페이스 변경의 여파가 다른 클라이언트에 미치는 영향을 최소화 한다.

의존 역전 원칙

고수준 모듈은 저수준 모듈의 구현에 의존해서는 안 된다. 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다.

고수준 모듈은 어떤 의미 있는 단일 기능을 제공하는 모듈이다.(큰 틀)
저수준 모듈은 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능의 실제 구현이다.(개별 요소)

예를 들어 고수준 모듈을 '바이트 데이터를 읽어와 암호와 하고 결과 바이트 데이터를 쓴다'고 정의한다면, 이를 구현하기 위해 저수준 모듈을 '파일에서 바이트 데이터를 읽어온다', 'AES 알고리즘으로 암호화한다', '파일에 바이트 데이터를 쓴다'고 정의할 수 있다.

저수준 모듈이 변경되더라도 고수준 모듈은 변경되지 않아야한다. 이를 위해 저수준 모듈이 고수준 모듈을 의존하게 만들어서 해결한다. 추상 타입을 만들어서 이를 해결한다. 고수준 모듈은 추상 타입에 의존하도록하고, 저수준 모듈 또한 추상 타입에 의존하도록 한다. 이런 경우 저수준 모듈이 변경되더라도 고수준 모듈에는 변화가 없다.

의존 역전 원칙은 소스코드의 의존을 역전시키는 것이지 런타임에서의 의존을 역전시키는 것은 아니다.(런타임에서는 고수준 모듈이 저수준 모듈에 의존)

의존 역전 원칙은 타입의 소유도 역전시킨다. 추상 타입을 고수준 모듈이 소유하게 되면서 다른 구현체가 생기더라도 필요없는 기능을 사용하지 않을 수 있다.

0개의 댓글