S(SRP)
단일 책임 원칙 (Single responsibility principle)
O(OCP)
개방-폐쇄 원칙 (Open/closed principle)
L(LSP)
리스코프 치환 원칙 (Liskov substitution principle)
I(ISP)
인터페이스 분리 원칙 (Interface segregation principle)
D(DIP)
의존관계 역전 원칙 (Dependency inversion principle)
어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다. - 로버트 C.마틴
클래스를 변경하는 이유가 한가지이기 위해서는 하나의 액터에 대한 책임만 가지고 있어야 합니다.
→ 책임 : 하나의 특정 액터를 위한 기능 집합
→ 액터 : 기능(=클래스 ,모듈)을 사용하는 주체
위 사진을 보면 남자라는 클래스의 범위를 크게 잡았기 때문에 남자라는 클래스 가 많은 역할(액터)과 책임(기능)을 맡고 있는것을 볼 수 있다.(SRP 위반)
남자라는 사람을 역할에 맞게 분리하여 역할과 책임이 잘 나누어졌다.(SRP 준수)
쉬운 테스트 - 책임이 하나인 클래스는 테스트 케이스가 줄어들기 때문에 테스트가 쉬워집니다.
낮은 결합 - 단일 클래스의 기능이 적어져 종속성이 줄어든다.
쉬운 검색 - 작고 잘 조직된 클래스는 모놀리식 클래스보다 검색하기 쉽습니다.
구현하기 쉬움 - 하나의 책임만 가지고 있기 때문에 구현및 이해가 쉽습니다.
SRP는 SOLID 원칙에서 가장 뭔가 애매하고 잘 사용하기 힘든 원칙 이라고 생각한다.
SRP를 요약하면 하나의 클래스는 한가지(단일)의 책임을 가져야 한다는 것인데 이것에 과도하게 집중하여 분류하면 오히려 불필요하게 복잡해지고 이해하기 힘든 코드가 될 수 있으니 단일과 책임을 잘 이해하고 단일 책임의 범위를 적당한 선에서 결정해야 한다.
소프트웨어 엔티티(패키지, 클래스, 모듈, 함수 등)는 확장에 대해서는 개방되어야 하지만 변경에 대해서는 폐쇄되어야 한다. - 로버트 C. 마틴
변경을 위한 비용은 가능한 줄이고, 확장을 위한 비용은 가능한 극대화 해야 한다.
→ 요구사항의 변경 or 추가가 있더라도, 기존 구성요소는 수정이 일어나지 말아야 하며, 기존 구성요소를 쉽게 확장해서 재사용할 수 있어야 한다
위 그림에서 운전자는 마티즈와 소나타 라는 각각의 구현체에 의존하고 있기 때문에 새로운 자동차가 등장한다면 계속해서 운전자도 의존적으로 변경될것이다.
(변경에 대해 열려있고 확장에 대해 폐쇄되어있음 → OCP 위반)
자동차 라는 인터페이스를 추가하여서 구현체를 계속해서 확장해 나갈 수 있고 운전자는 변경에 대해 영향을 받지 않게 된다. (OCP 적용)
기능을 추가하거나 변경해야 할 때 이미 제대로 동작하고 있던 원래 코드를 변경하지 않아도, 기존의 코드에 새로운 코드를 추가함으로써 기능의 추가나 변경이 가능하다.
→ 객체지향 프로그래밍의 가장 큰 장점인 유연성, 재사용성, 유지보수성을 얻을 수 있다.
OCP를 가능케 하는 중요 메커니즘은 추상화와 다형성이며, 객체지향의 장점을 극대화하는 아주 중요한 원리라 할 수 있다.
서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.
-로버트 C.마틴
하위 클래스 is a kind of 상위 클래스 : 하위 분류는 상위 분류의 한 종류이다.
구현 클래스 is able to 인터페이스 : 구현 분류는 인터페이스 할 수 있어야한다.
위 그림은 가족의 조직도 인데 이런 계층도,조직도 는 LSP를 위반한다. 딸,사촌누나는 아버지,삼촌의 역할을 할 수 없기 때문이다.
→ 객체지향에서 상속은 조직도로 표현 하면 안된다.
위 분류도는 LSP를 잘 구현했다. 고래,박쥐는 포유류의 역할을 할 수 있기 때문이다.
객체지향에서 상속이라는 특성을 잘 이해하고 활용한다면 LSP원칙을 잘 지키고 있는 것이다.
LSP는 상속 구조가 다형성을 위해 사용될 수 있도록 해주며 OCP의 기반이 된다.
클라이언트는 자신이 사용하지 않는 메소드에 의존 관계를 맺으면 안 된다. -로버트 C.마틴
인터페이스를 분리하여서 자신이 사용하지 않는 메소드에는 의존하지 말아야 한다. 클라이언트들이 자기가 꼭 필요한 메소드만 사용 할 수 있게 작은 단위로 분리하여야 한다.
SRP에서 사용한 그림을 보면 어머니는 사격과 구보를 하지 않는데도 남자 메소드에 의존을 하고 있으며, 만약 사격하기(),구보하기()가 변경된다면 어머니 또한 재컴파일 하여야 한다.
SRP 원칙에서 남자를 여러가지 클래스로 나누어서 단일 책임을 갖게 했는데 그렇게 하면 너무 많은 클래스를 구현해야 된다. 그래서 남자라는 클래스를 여러가지 클래스로 나누지 않고 역할에 대한 인터페이스를 만들어 그 인터페이스를 구현한 클래스로 만들면 된다.
인터페이스 분리 원칙은 단일 책임 원칙과도 관련이 있다. 인터페이스 분리 원칙도 한 곳의 변경이 다른 곳에 미치는 영향을 최소화한다. 따라서 클라이언트를 기준으로 인터페이스를 잘 분리하여 설계하는것이 목표이다.
"고차원 모듈은 저차원 모듈에 의존하면 안 된다. 이 두 모듈 모두 다른 추상화된 것에 의존해야 한다."
"추상화된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화된 것에 의존해야 한다."
"자주 변경되는 구체(Concrete) 클래스에 의존하면 안된다."
-로버트 C 마틴"
상위 모듈은 하위 모듈에 의존하면 안되고 추상화는 세부사항에 의존하면 안된다.
자동차는 구체적인 구현체(스노우타이어)에 의존하고 있다.
자동차를 구체적인 구현체 로부터 추상화된 인터페이스(타이어)에 의존하게 함으로써 의존관계를 역전 시켰다.
변하기 쉬운것에 의존하지 않고 인터페이스를 두어서 변하기 쉬운것으로 부터 영향을 받지 않게 하는것이다.
자세한 것은 스프링에서의 DI를 통해 실제코드로 설명할 것이다.
객체지향 공부를 하면서 SOLID 원칙에 대해 많이 보았는데 프로그래밍 지식이 부족한 상태였어서 대충 보고 넘겼었다. 요즘 스프링을 공부하면서 DI컨테이너와 의존관계주입 개념에서 OCP와 DIP가 많이 강조되었고 소프트웨어 설계를 하면서 OOP에 대한 깊은 공부가 필요하다고 느껴서 SOLID 원칙에 대해 확실히 정리하려고 글을 작성했다. 앞으로 객체지향에 대한 공부는 꾸준히 할 예정이고 아마 많은 글을 작성하지 않을까 싶다.
https://yoongrammer.tistory.com/96
https://waves123.tistory.com/23