📕SOLID원칙
객체지향 설계에서 지켜줘야 할 5개의 소프트웨어 개발 원칙을 말한다.
- SRP(Single Responsibility Principle) : 단일 책임 원칙
- OCP(Open Closed Principle) : 개방 폐쇄 원칙
- LSP(Listov Substitution Principle) : 리스코프 치환 원칙
- ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
- DIP(Dependency Inversion Principle) : 의존 역전 원칙
SOLID 객체 지향 원칙을 적용하면 코드를 확장하고 유지 보수 관리하기가 더 쉬워지며, 불필요한 복잡성을 제거해서,
프로젝트 개발의 생산성을 높일 수 있다.
SOLID의 5가지 원칙들은 서로 개념적으로 연관되어 있다는 점을 기억하며, 각 원칙의 설명을 이어가겠습니다.
📕SOLID의 5가지 원칙
📗SRP - 단일 책임 원칙
- 클래스는 단 한 개의 책임을 가져야 함.
- 클래스를 변경하는 이유는 단 하나여야 함.
여기서 말하는 책임은 기능이라고 이해하시면 편할 거 같습니다.
즉 하나의 클래스는 하나의 기능을 담당하여 하나의 책임을 수행하는데 집중되도록 클래스를 여러 개 설계하라는 말입니다.
왜 하나의 클래스의 책임이 많으면 안될까요?
하나의 클래스에 책임져야할 부분이 많을 경우, 섣불리 내부를 변경하기 어렵고,
해당 클래스와 연계된 클래스 모두가 영향을 받기 때문입니다.
즉 하나의 클래스에 저렇게 모아놓으면, 수정이 일어났을 때, 수정해야 할 코드가 많아집니다.
이러한 부분은 유지보수와도 이어지기 때문에, 반드시 책임을 분리해줘야합니다.
위 그림은 하나의 클래스의 집중되어 있던 책임을 분리시켜준 그림입니다.
이렇게 책임을 분산시키면, 각 클래스의 책임영역이 분명해집니다.
예를 들어 chef 클래스에 변경사항이 발생하더라도, 다른 클래스에는 영향이 가지 않습니다.
즉 책임의 변경으로부터 다른 책임의 변경으로 인한 연쇄작용이 없어집니다.
그리고 책임을 적절하게 분배함으로써, 코드의 가독성 향상, 유지보수에도 용이합니다.
SRP를 제대로 준수한다면, 변경이 필요할 때 수정할 대상이 명확해집니다.
이러한 장점은 시스템이 커질수록 극대화됩니다.
시스템이 커지면서 서로 많은 의존성을 갖게 되는 상황이고, 변경요청이 들어오면 특정 클래스 하나만 수정하면 되기 때문입니다.
단일책임 원칙을 적용하여 적절하게 분리하고, 서로 영향을 주지 않도록 추상화함으로써 애플리케이션의 변화에 손쉽게 대응할 수 있습니다.
📗OCP - 개방 폐쇄 원칙
클래스는 확장에 열려있어야 하며, 수정에는 닫혀있어야 한다
즉 변경을 위한 비용을 가능한 줄이고, 확장을 위한 비용을 극대화 해야 한다! 입니다.
💡확장에 대해 열려있다.
- 요구사항이 변경될 때 새로운 동작을 추가하여 애플리케이션의 기능을 확장할 수 있다.
💡수정에 대해 닫혀있다.
- 기존의 코드를 수정하지 않고 애플리케이션의 동작을 추가하거나 변경할 수 있다.
즉, 기능 추가 요청이 오면 클래스를 확장을 통해 손쉽게 구현하면서, 확장에 따른 클래스 수정은 최소화하는 것이다.
만약 새로운 변경사항이 있을 때, 객체를 직접적으로 수정해야 한다면, 유지 보수의 비용 증가로 이어지고, 이는 안 좋은 설계방식입니다.
예를 들어서 설명하겠습니다
학생클래스에서 학생의 성적이나 출석현황을 출력하는 기능을 가지고 있습니다.
클라이언트도 성적표와 출석현황을 출력하는 기능을 사용할 수 있습니다.
하지만 여기서 만약 새로운 기능인 도서 대여 기록 출력을 추가하면 클라이언트 클래스를 다시 수정해야 하고
이는 OCP를 위반하는 것입니다.
OCP를 위반하지 않는 설계를 할 때 중요한 것은 무엇이 변하는 것인지, 무엇이 변하지 않는 것인지 구분해야 합니다.
이것은 추상화와 연관이 있습니다.
OCP는 추상화를 통해서 구현할 수 있습니다.
자주 변화하는 부분인 성적표, 출석부, 도서 대역 기록과 같은 기능들을 추상화 함으로써, 기존의 클래스가 영향을 받지 않게 클라이언트가 개별적인 클래스에 접근하는 것이 아닌 추상화한 인터페이스로 접근을 하게 합니다.
요구사항의 변경이나 추가사항이 발생하더라도, 기존 코드를 크게 수정할 필요 없이, 유연하게 기능을 확장할 수 있습니다.
📗ISP - 인터페이스 분리 원칙
SRP가 클래스의 단일 책임을 명시했다면, ISP는 인터페이스의 단일 책임을 명시해 준다.
ISP 원칙은 인터페이스를 사용하는 클라이언트를 기준으로 분리해서, 클라이언트의 목적과 용도에 적합한 인터페이스 만을 제공해 주는 것이 목표다.
위 그림을 보면 전사, 궁수, 힐러는 스킬을 상속받고 있습니다.
전사는 근거리 스킬, 궁스는 원거리 스킬, 힐러는 힐러 스킬만을 사용한다고 가정할 때,
전사는 사용하지 않는 메서드를 포함하고 있고, 만약 사용하지 않는 메서드에서 변경이 일어날 시, 문제가 발생할 수 있습니다.
위 그림과 다른 것을 느끼셨나요??
스킬은 스킬을 상속받는 인터페이스로 잘게 분리되었고, 각 객체들은 자신이 필요한 메서드만을 사용할 수 있는 구조가 됩니다.
원거리 스킬, 힐러 스킬에서 변경이 일어나더라도, 근거리 스킬은 아무 영향을 받지 않습니다.
이렇게 인터페이스를 분리함으로써, 클라이언트가 사용하지 않는 인터페이스에 변경이 발생하더라도
영향을 받지 않도록 만들어 줘야 합니다.
📗DIP - 의존성 역전 원칙
의존 역전 원칙이란 고수준 모듈은 저수준 모듈의 규현에 의존해서는 안되며, 저수준 모듈이 고수준 모듈에 의존해야 한다는 것이다.
💡고수준 모듈
- 어떤 의미 있는 단일 기능을 제공하는 모듈(interface, 추상 클래스)
- 변하지 않는 것
💡저수준 모듈
- 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능의 실제 구현(메인 클래스, 객체)
- 빈번하게 변하는 것
저수준 모듈은 빈번하게 변경되고, 새로운 것이 추가될 때마다 고수준 모듈이 영향을 받기 쉬우므로, 의존관계를 역전시켜야 합니다.
따라서 한 마디로 상위의 인터페이스 타입의 객체로 통신하라는 원칙입니다.
그럼 여기서 의존관계란 무엇일까요?
한 클래스가 어떤 기능을 수행하려고 할 때, 다른 클래스의 서비스가 필요한 경우를 말하는데,
대표적으로 A 클래스의 메서드에서 매개변수를 다른 B 클래스의 타입으로 받아 B 객체의 메서드를 사용할 때, A 클래스는 B 클래스와 의존한다고 합니다.
예를 들어 내가 이즈리얼을 플레이합니다.
하지만 항상 이즈리얼을 플레이하는 것은 아닙니다. 여러 챔피언을 플레이합니다.
구체적인 챔피언인 이즈리얼은 변하기 쉽지만, 내가 챔피언을 플레이한다는 것은 변하기 어려운 것입니다.
따라서 구체적인 챔피언이 아닌 추상화된 챔피언 인터페이스에만 의존하게 함으로써, 사용자는 영향을 받지 않는 형태로 구성됩니다.
기존에는 이즈리얼은 의존하지 않는 클래스였지만, 추상적인 것인 챔피언 인터페이스에 의존하게 됐고
이것을 의존의 방향이 역전되었다고 한다.
이렇게 DPI 원칙을 따르면, 챔피언의 변경에 따라 코드를 변경할 필요가 없고, 챔피언의 확장에도 손쉽게 대응할 수 있다.
이처럼 자신보다 변하기 쉬운 것에 의존하는 것을 추상화된 인터페이스나 상위 클래스를 두어 변하기 쉬운 것의
영향받지 않게 하는 것이 의존 역전 원칙이다.
📗LSP - 리스코프 치환 원칙
리스코프 치환 원칙이란 부모 객체와 자식 객체가 있을 때 부모 객체를 호출하는 동작에서
자식객체가 부모 객체를 완전히 대체할 수 있다는 원칙이다.
즉 자식타입의 객체는 언제든 부모타입의 객체로 교체할 수 있어야 합니다.
객체지향에서 상속이 일어나면, 자식 객체는 부모 객체의 특성을 가지며 , 그 특성을 토대로 확장할 수 있습니다.
리스코프 치환 원칙은 무분별한 상속이 아닌, 올바른 상속을 위해 자식 개체의 확장이
부모 객체의 방향을 온전히 따르도록 권고하는 원칙이다.
예를 들어서 설명해 보겠습니다.
포유류와 원숭이 관계를 생각해 보면, 원숭이는 표유류입니다.
즉 부모클래스는 포유류, 자식 클래스는 원숭이라고 설정할 수 있겠습니다.
올바른 상속이란, 부모 클래스의 인스턴스 대신, 자식 클래스의 인스턴스를 별 다른 변경 없이 사용해야 합니다.
포유류의 특징은 다음과 같습니다.
- 포유류는 알을 낳지 않고, 새끼를 낳는다.
- 포유류는 체온이 일정한 정온동물이고 , 털이나 두꺼운 피부로 덮여있다.
- 등이 있습니다.
여기서 포유류 -> 원숭이로 대체해도 별 문제가 안 생긴다 왜냐? 올바른 상속 관계이기 때문이다.
하지만 오리너구리를 대입해 보면 문제가 생긴다. 왜냐하면 오리너구리는 알을 낳아 번식하기 때문이다. 하지만 나머지 특징은 만족한다.
(실제로 오리너구리는 조류와 포유류의 중간단계의 동물이지만, 엄연한 포유류라고 함)
객체지향에서 LSP를 만족하기 위해서는 오리너구리의 경우는 해당하지 않는다.
LSP를 준수하기 위해서는 올바른 상속 관계를 구현해야 합니다.
🙏잘못된게 있으면 지적 부탁드립니다.
✔️정리
정리해 보니 객체지향 설계 원칙인 SOLID는 추상화와 다향성을 굉장히 강조하고 있습니다.
구체적인 것에 의존하지 않고, 추상적인 것에 의존해서, 유지보수가 쉽고, 확장하기 쉬운 애플리케이션을 만드는 것을
객체지향에서는 강조하는 것을 알 수 있었습니다.
'Computer Science > OOP' 카테고리의 다른 글
객체 지향 프로그래밍(OOP)의 개념과 4가지 특징 (0) | 2023.05.28 |
---|