![[디자인 패턴] 1. SOLID와 전략 패턴](https://image.inblog.dev?url=https%3A%2F%2Finblog.ai%2Fapi%2Fog%3Ftitle%3D%255B%25EB%2594%2594%25EC%259E%2590%25EC%259D%25B8%2520%25ED%258C%25A8%25ED%2584%25B4%255D%25201.%2520SOLID%25EC%2599%2580%2520%25EC%25A0%2584%25EB%259E%25B5%2520%25ED%258C%25A8%25ED%2584%25B4%26logoUrl%3Dhttps%253A%252F%252Finblog.ai%252Finblog_logo.png%26blogTitle%3Djay0628&w=2048&q=75)
1. SOLID
객체 지향 설계의 5원칙
S : SRP (단일 책임 원칙) - 하나의 클래스는 하나의 책임을 가진다.
O : OCP (개방 폐쇄 원칙) - 새로운 코드에는 개방적이고, 기존의 코드를 수정하는 데에는 폐쇄적이어야 한다.
L : LSP (리스코프 치환 원칙)
I : ISP (인터페이스 분리 원칙)
D : DIP (의존 역전 원칙) - 구체적인 것에 의존하지 말고 추상적인 것에 의존해야 한다.
1. OCP (Open Close Principle)
개방 폐쇄 원칙으로 기존의 코드를 수정하는 데에는 폐쇄적이고 새로운 파일을 만드는 것에는 개방되어있어야 한다.
2. DIP (Dependency Inversion Principle)
의존 관계를 역전하여 기존에 구체적인 것에 의존해 있던 것을 추상적인 것에 의존하도록 해야한다.
3. SRP (Single Responsibility Principle)
하나의 클래스가 하나의 책임을 가지는 것이 좋다.
Q. 클래스는 왜 하나의 책임만 가져야 할까?
A. 예시를 통해 이해해보자!
☕ 하나의 커피머신에 모든 기능이 들어있다면?
예를 들어, 어떤 커피머신이 있다고 가정해보자. 이 커피머신은 다음 두 가지 기능을 가지고 있다.
- 분쇄기: 커피콩을 2cm 이하로 곱게 갈아줌
- 추출기: 2cm 이하로 갈린 커피 가루를 물과 함께 추출해 커피를 만들어줌
처음에는 모든 기능이 하나의 기계에 통합되어 있어 편리해 보일 수 있으나 실제로 커피가 나오지 않는 상황이 발생했을 때, 어디에서 문제가 발생했는지를 확인하기 어렵다.
- 커피콩이 제대로 분쇄되지 않았을 수도 있고,
- 잘 분쇄되었지만 추출 과정에서 문제가 생겼을 수도 있다.
이처럼 여러 책임이 섞여 있으면 문제의 원인을 추적하고 디버깅하기 어려워진다.
🔧 모듈을 분리하면 단위 테스트와 유지보수가 쉬워진다
아래와 같이 분쇄기와 추출기를 각각 독립된 모듈(또는 클래스)로 분리해보자.
Grinder
클래스 → 커피콩을 2cm 이하로 분쇄하는 역할
Extractor
클래스 → 분쇄된 가루로 커피를 추출하는 역할
장점 1. 문제 원인을 빠르게 파악할 수 있음
커피가 안 나올 때, Grinder에서 2cm 이하로 잘 분쇄되었는지 먼저 확인하고, 문제가 없다면 Extractor 쪽을 살펴보면 된다. 이처럼 문제가 발생한 위치를 빠르게 진단할 수 있다.
장점 2. 독립적인 테스트가 가능해짐
추출기를 개발하거나 테스트할 때, 반드시 실제 분쇄기를 기다릴 필요가 없어진다.
그냥 "2cm 이하로 잘 갈린 커피 가루"라는 샘플(given) 데이터를 넣어서 추출기만 단독으로 테스트할 수 있다. 즉, 단위 테스트(Unit Test)가 가능해지고 개발 속도도 빨라진다.
(단위 테스트를 하게 되면 전체 코드가 아무리 길더라도 일부분만 테스트 가능하기 때문에 실행하는데 몇시간씩 걸리는 코드 중 한 줄 수정 했다고 다시 몇 시간씩 기다릴 필요 X)
장점 3. 재사용성과 유지보수성 향상
Grinder는 다른 기계에서도 재사용 가능하고, Extractor도 별도 환경에서 활용할 수 있다. 각자의 책임이 명확하니, 한쪽을 수정해도 다른 쪽에 영향을 주지 않는다.

2. 전략 패턴
구체적인 것을 추상화해서 의존하는 기법
⇒ 구체적인 것에 의존하지 말고 추상적인 것에 의존한다.
1. 전략 패턴이란
전략 패턴
SOLID (객체지향 5원칙) (O, D)
1. OCP (Open(cat) Close(doorman) 원칙)
2. DIP (Dedendency Inversion 원칙)
3. SRP (Single Responsibility 원칙)
전략 패턴이란 구체적인 것에 의존하지 말고 추상적인 것에 의존하게 해서, 기존 코드를 수정하지 않고도 전략을 바꿀 수 있게 하는 패턴이다. (ex - 누가? doorman이, 추상적인 것? Animal)
추상적인 것에 의존하게 되면 기존의 코드를 수정하지 않더라도 새로운 코드를 작성하여 해결할 수 있게 되고 이 경우 OCP가 지켜지게 된다. 즉, OCP는 전략 패턴만 지키면 지켜진다. 처음에는 구체적인 것에 의존하고 있었지만 추상적인 것에 의존하도록 수정되면서 DIP 또한 만족하게 된다.
2. 문지기와 동물로 이해해보기 — OCP, DIP, SRP까지
전략 패턴(Strategy Pattern)은 객체지향 설계에서 자주 쓰이는 디자인 패턴이다. 핵심은 구체적인 구현이 아닌 추상적인 것에 의존하라는 것.
문지기와 동물들 이야기를 통해 이 개념을 정리해보자.
1. 처음엔 단순했다: 문지기와 쥐
처음엔 성문 앞을 지키는 문지기가 있었다. 이 문지기의 책임은 단 하나, 쥐를 쫓아내는 것.
Mouse m1 = new Mouse();
Doorman d1 = new Doorman();
d1.쫓아내(m1); // 출력: 쥐 나가!
public class Doorman {
public void 쫓아내(Mouse m) {
System.out.println(m.getName() + " 나가!");
}
}
단순하고 문제 없어 보인다. 근데, 쥐만 있을 때 얘기다.
2. 그런데 호랑이와 고양이가 등장했다?
새로운 동물들이 나타난다.
호랑이도 고양이도 문 앞에 얼쩡거리니 문지기가 쫓아내야 한다.
Tiger t1 = new Tiger();
d1.쫓아내(t1); // 에러! 쫓아내(Tiger) 없음
이걸 해결하려면?
public class Doorman {
public void 쫓아내(Mouse m) { ... }
public void 쫓아내(Tiger t) { ... }
public void 쫓아내(Cat c) { ... }
}
이때부터 조지기 시작한다… 동물 하나 추가할 때마다
Doorman
수정해야 한다. 수정의 늪이다.3. 뭘 잘못한 걸까?
이 구조는 객체지향 원칙을 깨뜨리고 있다.
- OCP (Open-Closed Principle): 확장에는 열려 있고, 수정에는 닫혀 있어야 한다 → 실패
- DIP (Dependency Inversion Principle): 구체적인 구현 말고 추상적인 것에 의존해야 한다 → 실패
4. 추상화로 해결하자 — 전략 패턴의 등장
이 상황을 해결하려면 추상화가 필요하다. 추상화를 통해 전략 패턴을 적용하면 구조가 깔끔해진다.
(1) Animal이라는 공통 부모 만들기
public abstract class Animal {
public abstract String getName();
}
그리고 각 동물들은
Animal
을 상속한다.public class Mouse extends Animal {
private String name = "쥐";
public String getName() { return name; }
}
public class Tiger extends Animal {
private String name = "호랑이";
public String getName() { return name; }
}
public class Cat extends Animal {
private String name = "고양이";
public String getName() { return name; }
}
(2) 문지기는 이제 Animal만 알면 된다
public class Doorman {
public void 쫓아내(Animal a) {
System.out.println(a.getName() + " 나가!");
}
}
(3) App만 수정하면 된다
public class App {
public static void main(String[] args) {
Doorman d1 = new Doorman();
Mouse m1 = new Mouse();
d1.쫓아내(m1);
Tiger t1 = new Tiger();
d1.쫓아내(t1);
Cat c1 = new Cat();
d1.쫓아내(c1);
}
}
Doorman
은 그대로 두고 App
에서만 수정하면 된다.새로운 동물이 생기면
Animal
을 상속한 클래스 하나 만들고 끝.5. 이 예시가 알려주는 객체 지향 원칙들
1. OCP (개방-폐쇄 원칙)
Doorman
은 더 이상 수정할 필요 없다. 동물 추가만 하면 끝. ⇒ OCP를 지킴.
2. DIP (의존 역전 원칙)
Doorman
은 이제 Mouse
, Tiger
같은 구체 클래스가 아니라 Animal
이라는 추상 클래스에 의존한다. ⇒ DIP 지킴.
3. SRP (단일 책임 원칙)
Doorman
의 책임은 하나뿐이다. “동물을 쫓아내는 것.” ⇒ SRP도 지킴.
6. 결론: 전략 패턴이 필요한 이유
전략 패턴은 특정 행위를 추상화하고(구체적인 객체에 의존하지 않고), 다양한 전략(여기선 동물)을 갈아끼우는 방식이다. 이 덕분에 쫓아내라는 로직을 수정하지 않고도 확장이 가능해진다.
7. 핵심 한 줄 요약
구체적인 게 아니라 추상적인 거에 의존하라.
3. 그림 예시

4. 전체 소스 코드
public class App {
public static void main(String[] args) {
Mouse m1 = new Mouse();
Doorman d1 = new Doorman();
d1.쫓아내(m1);
Tiger t1 = new Tiger();
d1.쫓아내(t1);
Cat c1 = new Cat();
d1.쫓아내(c1);
}
}
public class Doorman {
// 객체의 책임 = 메서드
public void 쫓아내(Animal a) {
System.out.println(a.getName() + " 나가!");
}
public abstract class Animal {
public abstract String getName();
}
public class Mouse extends Animal {
private String name = "쥐";
public String getName() {
return name;
}
}
public class Tiger extends Animal {
private String name = "호랑이";
public String getName() {
return name;
}
}
public class Cat extends Animal {
private String name = "고양이";
public String getName() {
return name;
}
}
+) SRP에서 하나의 클래스에는 하나의 책임만 주는게 베스트지만 여러 개 줘야 하는 경우도 있고 이 경우에는 묶어야 된다. 그렇게 되면 하나의 클래스가 하나의 책임만 가지는 것은 아니지만 이 또한 잘 만든 클래스 설계가 될 수 있다.
+) Anmial을 가리키고 Animal과 Mouse가 heap 영역에 떠있다.
// Doorman
public void 쫓아내(Animal a){}
// App
Doorman d1 = new Doorman();
Mouse m1 = new Mouser();
d1.쫓아내(m1);
위의 코드에서 d1.쫓아내(m1)은 다음과 같다.

Share article