SOLID를 고민해보자 (2)

허진혁·2022년 11월 5일
0

고민해보자

목록 보기
5/12
post-thumbnail

고민해보자 두번째 시리즈는 SOLID 입니다.
이번편은 LSP / ISP / DIP를 다루겠습니다.

Liskov Substitution Principle : 리스코프 치환 원칙

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

이 부분을 읽고나서 바로 반짝인 단어가 있다. 다형성 !!!

일단은 다형성까지 생각해내는 것은 성공했다. 많은 발전이 이루어진 것이다.
그렇다면 여기서 끝내지 말고, 다음 고민을 이어가야 한다.

다형성이 잘 활용하려면
즉, 하위 타입의 객체가 상위 타입을 사용할 때의 규칙, 제약들이 무엇이 있을까?

1. 하위 타입 객체에서 상위 타입의 메서드를 오버라이딩 할 때, 상위 타입이 정해둔 파라미터를 사용해야 한다.

상위 타입에서 잘 사용되던 파라미터는 하위 타입에서도 잘 사용 되어야 한다.
이 부분을 바로 떠오른걸 보니 조금(아주 조금)은 성장함을 느낀다.

2. 하위 타입 객체의 오버라이딩된 메서드의 리턴 값은 상위 타입 객체의 메서드의 리턴 값의 범위를 벗어나지 않는다.

이 부분은 전혀 고려해보지 못했던 부분이다. 그래도 받아들이는데에는 불과 몇초밖에 걸리지 않았다.

상위 타입의 객체를 하위 타입의 객체로 변경했는데, 하위 타입의 객체가 기존 상위 타입의 객체의 메서드를 실행했을 때, 예측하지 못한 리턴 값을 반환한다면 그게 바로 오류가 발생하는 것이다.

정리해보면 앞서 말한 OCP는 추상화와 다형성을 기반으로 잘 지켜지는지 확인할 수 있었다면, LSP는 다형성을 잘 지켰는지 확인하는 것이다.

LSP는 "확장에 대한 규약" 이다.
상위 타입의 객체를 하위 타입의 객체로 치환해도 정상 작동한다면, 하위 타입의 객체가 역할을 잘 수행하고 있는 것이고, 이를 통해 확장에 유연함을 찾을 수 있다.

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

인터페이스는 그 인터페이스를 사용하는 클라이언트를 기준으로 분리해야 한다

"클라이언트"는 누구를 가르키는 것일까???

클라이언트는 로직을 구현한 클래스일 것이다.
인터페이스로 메서드 규약을 정해두고 클래스가 구현하도록 하는 경우를 많이 보기도 하고 많이 만들기도 했다.

하지만 추상화와 다형성을 기반으로만 생각했었다. 다른 생각도 해보아야 한다.

인터페이스를 클라이언트(로직을 구현한 클래스, 이하 생략)기준으로 분리한다는 것을 같이 고민해 보았다.

인터페이스를 구현한 클래스는 결국 인터페이스에 의존한다.

클라이언트가 인터페이스에 의존한다는 것은 클라이언트 변화로 인터페이스가 변화되겠지만, 반대로 인터페이스 변화는 클라이언트를 변화될 것이다.

위의 말을 10번은 반복해서 되새김질 해보았다. 그리고 한번 글씨로 써보니 한 문장만 집중하게 되었다.

인터페이스 변화는 클라이언트를 변화할 것이다.

하나의 인터페이스에 다양한 기능들이 있다고 가정한다면, 이를 구현할 클래스는 모든 메서드를 구현해야 한다. 즉, 필요없는 메서드가 있음에도 불구하고 구현해야 한다는 것이다. (인터페이스는 규약이니까!!!)

심지어 위와 같은 상황에서 필요없는 메서드가 인터페이스에서 변경이 일어난다면, 필요없음에도 불구하고 구현한 클래스는 변경이 불가피하다.

여태까지 클래스들의 공통된 기능을 인터페이스에 넣는다고 생각을 해왔었는데, 이게 오산이었던 것이다.

클라이언트가 꼭 필요한 기능들을 인터페이스에 정의해 두는 것이고, 이것이 클라이언트 기준으로 인터페이스를 만들어야 한다는 것이다.

정리해보면, 인터페이스가 클라이언트 기준으로 분리되어 있다면, 클라이언트는 자신에세 꼭 필요한 기능만 구현할 수 있게 되고, 클라이언트가 변경이 필요할 경우 인터페이스를 변경하더라도 다른 클라이언트에게는 영향이 가지 않는다.

ISP를 통해 "클라이언트 특화 인터페이스" 가 만들어질 것이고
결과적으로, 유지/보수/확장에 유리한 코드가 된다.

Dependency Inversion Principle : 의존 역전 원칙

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

고수준 모듈 은 "어떤 의미 있는 단일 기능을 제공하는 모듈"이라고 정의되고,
저수준 모듈 은 "고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능의 실제 구현"이라고 정의되어요.

말은 모듈이라는 낯선 단어이지만, 객체로 바꾸어 생각한다면 자주 접했다.

추상화를 통해 고수준 모듈을 만들고, 구현한 저수준 모듈은 고수준 모듈에 의존한다. 고수준 모듈을 기반으로 저수준 모듈은 확장할 수 있게 된다.

DIP가 가장 잘 드러난 것으로 전략 패턴이 떠올랐다.

전략 패턴을 생각해보면 추상화를 담은 Strategy전략과 상세 구현을 담은 AbcStrategy 전략을 만들었다.

DIP를 공부하다보니 OCP와 정말 유사하게 느껴졌다.
알고보니 DIP는 OCP의 지원군인 것이다.

전체 정리

SOLID 원칙은 변화 유연하게 대처할 수 있는 설계 원칙 이다.

SRP(단일 책임 원칙) 와 ISP(인터페이스 분리 원칙) 는 객체가 커지지 않도록 막아준다.

객체를 작게 유지하면서, 해당 객체의 변경이 다른 객체의 변경으로 영향을 주지 않도록 해준다.
원하는 객체만 손쉽게 변경할 수 있다면, 변화에 유연한 설계를 만든다.

LSP(리스코프 치환 원칙) 와 DIP(역전 의존 원칙) 은 OCP(개방 폐쇄 원칙) 를 지원해준다.

LSP를 통해 다형성을 확보하고, DIP를 통해 변화되는 부분을 추상화해요.
변화가 추상화된 타입에 여러 객체를 주입/참조시키면서, 확장에는 열려있고, 변경에는 닫혀있는 코드를 쉽게 작성할 수 있게 되어요.

이 역시 변화에 유연한 설계를 해낸 것이에요.

객체 지향 설계의 가이드라인이 되어주는 SOLID 원칙을 염두에 두고 코드를 작성하다 보면, 점점 유연한 코드가 되어갈 수 있다 !!!!!

profile
Don't ever say it's over if I'm breathing

0개의 댓글