참고
1. 스프링 핵심 원리 강의(김영한)
2. https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-ISP-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EB%B6%84%EB%A6%AC-%EC%9B%90%EC%B9%99?category=967430
SOLID
클린 코드의 저자(로버트 마틴)의 객체 지향 설계 원칙 5가지
- SRP(Single Responsibility Principle): 단일 책임 원칙
- OCP(Open-Closed Principle): 개방-폐쇄 원칙
- LSP(Liskov Substitution Principle): 리스코프 치환 원칙
- ISP(Interface Segregation Principle): 인터페이스 분리 원칙
- DIP(Dependency Inversion Principle): 의존관계 역전 원칙
SRP: Single Responsibility Principle
- 하나의 클래스는 하나의 책임만 가져야 함
- 클래스의 변경이 있을 때, 파급 효과가 적으면 SRP 준수한 것 (절대적인 기준은 없으며, 스스로 기준을 잡아야 한다.)
- 성능보다는 유지보수를 위한 원칙에 가까움
예시
아래와 같이 '공학계산기'와 '그래프계산기'가 있다.
이때, 공학계산기의 더하기 함수는 공학계산기와 그래프계산기 두 클래스의 계산 함수를 책임지고 있다.
class 공학계산기 {
public static void 더하기(){}
public void 공학계산(){
...
더하기();
...
}
}
class 그래프계산기 {
public void 그래프계산(){
...
공학계산기.더하기();
...
}
}
만약, 아래와 같이 더하기 함수와 같은 사칙연산 전용 클래스를 만들면, SRP를 더욱 잘 준수할 수 있을 것이다.
class SimpleCaculator{
public static void 더하기(){}
}
class 공학계산기 {
public void 공학계산(){
SimpleCaculator.더하기();
}
}
class 그래프계산기 {
public void 그래프계산(){
SimpleCaculator.더하기();
}
}
OCP: Open-Closed Principle
- 확장에는 열려 있고, 변경에는 닫혀 있어야 함
- 다형성을 활용(인터페이스 혹은 추상클래스 ➡️ 클래스 구현)
예시
위 공학용계산기에서 인터페이스를 이용해 OCP를 적용하면 아래와 같다.
이후, SimpleCaculator의 버전에 따라 편리하게 확장할 수 있고, 인터페이스에 의해 SimpleCalculator의 요소들은 닫혀 있게 된다.
interface SimpleCaculator {
public void 더하기();
}
class SimpleCaculatorV1 implements SimpleCaculator{
public void 더하기(){}
}
class SimpleCaculatorV2 implements SimpleCaculator{
public void 더하기(){}
}
class 공학계산기 {
// SimpleCaculator simpleCaculator = new SimpleCaculatorV1();
SimpleCaculator simpleCaculator = new SimpleCaculatorV2();
public void 공학계산(){
simpleCaculator.더하기();
}
}
LSP: Liskov Substitution Principle
- 프로그램의 정확성은 유지하며 하위 타입의 인스턴스를 변경할 수 있어야 함
- 상위 인터페이스의 규약을 정확히 지켜야 함 (추상, 인터페이스 - 구현체 간 다형성을 잘 지키라는 의미)
예시
아래 SimpleCacluatorV2는 인터페이스의 규약을 지키고 있지 않다. 이는 LSP에 위반된다. (IDE에서 에러라인으로 알려준다.)
interface SimpleCaculator {
public void 더하기();
}
class SimpleCaculatorV1 implements SimpleCaculator{
public void 더하기(){}
}
class SimpleCaculatorV2 implements SimpleCaculator{
public void 더하기(int a, int b){} //LSP 위반.
}
ISP: Interface Segregation Principle
- 인터페이스용 SRP
- 하나의 범용 인터페이스보다 여러 개의 인터페이스가 좋음 (커다란 단일 인터페이스 < 여러 개의 작은 인터페이스)
- 인터페이스가 명확해지고, 대체 가능성 ⬆️
- 인터페이스를 분리하면, 구현체에서 필요한 기능만 받아 구현 가능 (불필요한 구현 방지)
- 즉, 인터페이스가 복잡해지면 분리를 고려하자
예시
아래는 공학계산기에 필요한 모든 기능이 하나의 인터페이스에 모두 담겨있는 ISP를 준수하지 않는 코드이다.
interface SimpleCaculator {
public void 더하기();
public void 공학계산();
public void 공학더하기();
public void 공학계산결과출력();
}
class SimpleCaculatorV1 implements SimpleCaculator{;
public void 더하기(){}
public void 공학계산(){}
public void 공학더하기(){}
public void 공학계산결과출력(){}
}
class 공학계산기 {
// SimpleCaculator simpleCaculator = new SimpleCaculatorV1();
SimpleCaculator simpleCaculator = new SimpleCaculatorV1();
}
아래와 같이, 인터페이스를 기능/역할 별로 분리해주어야 한다.
interface SimpleCaculator {
public void 더하기();
}
interface 공학기능 {
public void 공학계산();
public void 공학더하기();
}
interface 계산출력 {
public void 계산결과출력();
}
class SimpleCaculatorV1 implements SimpleCaculator{
public void 더하기(){}
}
class 공학계산기 implements 공학기능, 계산출력{
SimpleCaculator simpleCaculator = new SimpleCaculatorV1();
public void 공학계산(){}
public void 공학더하기(){}
public void 계산결과출력(){}
}
DIP: Dependency Inversion Principle
- 구현체가 아닌, 추상화에 의존해야 한다.
- 즉, 구현 클래스가 아닌 인터페이스에 의존해야 한다는 뜻.
예시
아래는 공학계산기 클래스가 내부의 '더하기' 메서드에 의존하고 있다. 이는 DIP에 위배된다.
class 공학계산기 {
// SimpleCaculator simpleCaculator = new SimpleCaculatorV1();
SimpleCaculator simpleCaculator = new SimpleCaculatorV1();
public void 더하기(){}
public void 공학계산(){
...
더하기();
...
}
public void 공학더하기(){
...
더하기();
...
}
}
아래와 같이 분리해서 DIP를 준수할 수 있다.
interface SimpleCaculator {
public void 더하기();
}
interface 공학기능 {
public void 공학계산();
public void 공학더하기();
}
class SimpleCaculatorV1 implements SimpleCaculator{
public void 더하기(){}
}
class 공학계산기 implements 공학기능{
SimpleCaculator simpleCaculator = new SimpleCaculatorV1();
public void 공학계산(){}
public void 공학더하기(){}
}
'스프링' 카테고리의 다른 글
[Spring] 동시성 문제 - ThreadLocal로 개선 (0) | 2024.04.07 |
---|---|
[Spring]IoC(Inversion of Control)와 DI (0) | 2024.03.28 |
스프링 - WebSocket (0) | 2024.03.07 |
[WAS] 쓰레드 풀 (0) | 2024.03.07 |
[Query DSL] Query DSL 왜 쓸까? (0) | 2024.03.07 |