SOLID 법칙 中 LID

휴먼스케이프

인터페이스 분리 원칙, 리스코프 치환 원칙, 의존성 역전 원칙에 대해 알아보기

안녕하세요. 휴먼스케이프 Frontend Software Engineer 남현욱입니다.

이번 포스트에서는 SOLID 법칙 중 인터페이스 분리 원칙, 리스코프 치환 원칙, 의존성 역전 원칙에 대해 알아보도록 하겠습니다.

이 포스트의 모든 코드는 TypeScript로 작성합니다.

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

“구현체 스스로가 사용하지 않는 기능에 영향받지 않아야 한다.”

아래에 Animal이라는 인터페이스 하나가 있습니다.

이제 이 인터페이스로, Bird와 Human 클래스를 만들어보도록 하겠습니다.

위의 코드에서, Human 클래스에 fly() 메서드를 구현하려고 하니 무언가 조금 이상합니다. 사람은 날 수가 없기 때문입니다.

그럼 이 문제를 어떻게 해결할까요?

인터페이스를 상속하는 인터페이스를 통해 해결

위의 예제대로 설명하면, Animal 인터페이스를 상속하는 FlyableAnimal 인터페이스를 만들어서 해결할 수 있습니다.

여래 개의 인터페이스를 상속하여 해결

혹은 다른 스타일로, Flyable 인터페이스를 만들고 Bird 클래스가 Flyable과 Animal 인터페이스를 둘 다 상속하여 해결할 수도 있습니다.

정리

인터페이스 분리 원칙은 인터페이스는 자신의 쓰임새와 사용에 맞게끔 분리해야 한다는 원칙입니다.

만약 인터페이스가 너무 범용적으로 정의되어 있으면, 그 인터페이스의 구현체에 필요없는 추상이 있더라도 구현해야 하는 문제가 생깁니다. 또한 그 인터페이스가 변경될 때마다 사용 여부에 상관없이 구현체를 변경해야 하는 상황까지 생깁니다. 이는 개방-폐쇄 원칙(Open-Closed Principle)을 위반합니다.

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

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

리스코프 치환 원칙에서 제일 많이 등장하는 정사각형/직사각형 예제를 통해 알아봅시다.

Rectangle이라는 클래스와 Rectangle을 상속하는 Square라는 클래스가 있습니다. Rectangle 클래스는 너비와 높이를 가지며, getter와 setter를 이용해 둘을 조정하고, 넓이를 구하는 getter 하나가 있습니다. Square 클래스는 너비와 높이의 getter, setter를 재정의합니다.

이 코드를 실제로 사용하게 된다면 어떻게 될까요?

Rectangle을 사용하면 true, Square를 사용하면 false가 나오게 됩니다. 이는 리스코프 치환 원칙을 위배한 사례가 됩니다.

어떻게 해결할까요?

논리상으로 “정사각형은 직사각형이지만, 직사각형은 정사각형이 아니다.” 라고 생각해서 코드에 상속 관계를 반영할 수 있지만, SOLID 법칙의 기준으로 보면 둘은 코드에서 상속 관계로 존재할 수 없다는 것을 위 코드를 통해 알아보았습니다.

정사각형-직사각형 관계보다 더 포괄적인, 도형을 상속하도록 변경하고 코드를 리팩토링해봅시다.

훨씬 깔끔해졌다.

이제 리스코프 치환 원칙을 위반하지 않는 코드가 되었습니다. 둘의 상속 관계가 없어졌기 때문입니다.

정리

리스코프 치환 원칙은 베이스 클래스가 서브 클래스로 치환되어도 동일한 동작을 보장해야 한다는 원칙입니다.

리스코프 치환 원칙은 상속의 규정이며 치환 가능성이 조금이라도 위배된다면 아키텍쳐의 오염으로 번질 수 있습니다. 또한 이 원칙도 개방-폐쇄 원칙을 위반하지 않게 해주는 원칙이라고 설명하기도 합니다.

의존성 역전 원칙 (Dependency Inversion Principle)

“고수준 모듈은 저수준 모듈에 직접 의존해서는 안 된다. 또한 추상은 상세를 의존해서는 안 되며, 상세는 추상을 의존해야 한다.”

키보드의 입력을 콘솔의 출력으로 복사하는 프로그램을 만들어봅시다.

그러던 도중, “파일도 입력할 수 있게 만들자.” 라는 새로운 요구사항이 생겼습니다. 하지만 copy 메서드는 오직 Keyboard 클래스만을 매개변수로 받고 있습니다.

어떻게 해결하나요?

인터페이스 Input, Output을 만들어 사용하도록 바꿔보겠습니다.

그 이후, Keyboard와 Console을 동일하게 다시 만들어서 사용해봅시다.

이제, “파일도 입력할 수 있게 만들자.” 라는 요구사항을 지킬 수 있게 되었습니다. 물론 콘솔 말고도 모니터에서 출력되게도 만들 수 있습니다.

정리

의존성 역전 원칙은 클래스를 참조할 일이 있을 때 그 클래스를 직접 참조하지 말고 그 대상의 추상 클래스를 만들어서 참조해야 한다는 원칙입니다.

의존성 역전 원칙을 준수하면, 변화에 유연한 코드를 만들 수 있습니다. 위의 예제에서 Input으로 Keyboard, File, Output으로 Console, Monitor 말고도 Input과 Output의 요건을 충족한 구현체라면 어느 것이라도 copy 함수를 사용할 수 있습니다.

여담

의존성 역전 원칙에서 Input과 Output 인터페이스는 string 타입을 (어쩌면 구현체를) 참조하고 있습니다. 의존성 역전 법칙이 깨진 걸까요?

https://github.com/hw0k/isp-lsp-dip

감사합니다.

Get to know us better! Join our official channels below.

Telegram(EN) : t.me/Humanscape KakaoTalk(KR) : open.kakao.com/o/gqbUQEM Website : humanscape.io Medium : medium.com/humanscape-ico Facebook : www.facebook.com/humanscape Twitter : twitter.com/Humanscape_io Reddit : https://www.reddit.com/r/Humanscape_official Bitcointalk announcement : https://bit.ly/2rVsP4T Email : support@humanscape.io

기업문화 엿볼 때, 더팀스

로그인

/