강의 : https://www.udemy.com/course/design-patterns-in-java-concepts-hands-on-projects/
Single Resposibility Principle
There should never be more than one reason for a class to change.
클래스를 변경하고자 하는 이유가 단 한 가지여야 한다. 예를 들어서 입력 값을 검증하고, 저장하는 함수가 있다고 하면, 두 가지 일을 하는 것이다. 이 경우 입력 값을 검증하는 함수는 별도의 클래스로 추출한다.
이 원칙을 생각할 때에는, 이 함수가 어떤 일을 하는지에 대해서 생각해 보면 된다.
Open-Closed Principle
Software entities(Classes, Modules, Methods etc.) should be open for extension but closed for modification
- Open for extension: Extend existing behavior
- Closed for modification: Existing code remains unchanged
Base 클래스를 수정하지 않고 상속받은 클래스만을 구현함으로서 목적을 달성할 수 있어야 한다. Base 클래스를 변경하지 않고 상속을 받아서 동작을 수정하면 이 원칙에 맞는 것이다.
Liskov Substitution Principle
We should be to substitute base class objects with child class objects
This should not alter behavior / characteristics of program
하나의 구현체를 다른 구현체로 바꾸더라도, 그 동작은 바뀌지 말아야 한다. Base class에서 예상하는 동작이 바뀌면 안 된다.
예를 들어서, 직사각형 클래스와 정사각형 클래스가 있다.
- 정사각형은 가로와 세로 길이가 같은 직사각형이다. 따라서
class Square extends Rectangle
정의. - 정사각형은 가로나 세로를 설정할 때 세로와 가로를 함께 설정하도록 구현한다. (setWidth, setHeight 오버라이드)
-
그런데 다음 테스트는 실패한다.
val rectangle = Rectangle(10, 20) val square = Square(10) rectangle.height = 20 rectangle.width = 30 assertThat(rectangle.height).isEqualTo(20) assertThat(rectangle.width).isEqualTo(30) square.height = 20 square.width = 30 assertThat(square.height).isEqualTo(20) // Assertion 실패 assertThat(square.width).isEqualTo(30)
- setWidth, setHeight가 원래 의도했던 동작을 위반하였으므로 이는 리스코브 치환 법칙에 위배된다.
- 직사각형 → 정사각형으로 클래스 확장하지 말고, 모양 → 직사각형, 모양 → 정사각형으로 인터페이스 확장을 해라.
요점: 클래스의 동작을 바꾸는 것은 위험하다.
Interface Segregation Principle
Client should not be forced to depend upon interfaces that they do not use.
Client should not have to depend on methods that are defined in interfaces that they don’t use.
여기서 다루는 용어는 Interface pollution.
큰 인터페이스를 피해야 한다. 관련 없는 메소드들을 같은 인터페이스에 넣으면 안 된다.
이 규칙을 위반하는 인터페이스의 예시:
- 클래스들이 빈 메소드 구현을 갖고 있다
- 메소드들이 throw UnsupportedOperationException 들을 던진다
- null이나 기본값 / 더미 값을 반환한다
이유는, 특정한 클래스에서 사용될 수 없는 메소드를 가진다는 뜻이기 때문이다.
이 원칙을 지키면, 응집도가 높은 인터페이스를 만들 수 있다.
예를 들어서, PersistenceService 인터페이스에서 save, delete, findById 뿐 아니라 findByName 이 있으면
name이 없는 엔티티는 어떻게 할 것인가. throw UnsupportedOperationException!
Dependency Inversion principle
- High level modules should not depend upon low level modules. Both should depend upon abstraction
- Abstractions should not depend upon details. Details should depend upon abstractions
dependency : 우리가 작성하는 코드가 동작하기 위한 무언가
Dependency inversion : 우리가 의존성을 인스턴스화하는 것이 아니라, 누군가가 의존성을 전달해주는 것
Dependency injection이 아니라는 점을 생각하자.