티스토리 뷰
1. SOLID 원칙 : 변화에 강하고 재사용에 유리한 클래스 구조를 만드는 원칙
결합도(본드로 붙여놓은 듯이 분리가 어려운 정도)는 낮추고,
응집도(응집도가 낮은 건 잡동사니 박스, 하나의 박스에 있는 요소들을 묶을 수 있는 성격)는 높이도록 하자.
- Single responsibility : 단일 책임 원칙
- Open / closed : 개방 - 폐쇄 원칙
- Liscov Substitution : 리스코프 치환 원칙
- Interface segregation : 인터페이스 분리 원칙
- Dependency Inversion : 의존성 역전 원칙
1. Single responsibility : 단일 책임 원칙
클래스는 단 하나의 책임에 집중한다.
2. Open/Closed : 개방 폐쇄 원칙
추상화(인터페이스, 추상클래스)에 의존하고(확장에 열려있고(Open)), 구체적인 구현 클래스에 의존하지 않는다.
기존 코드를 변경하지 않고도(Closed) 새로운 기능을 추가할 수 있어야 한다.
해야 할 일 명시하는 역할. 인터페이스만 보면 알게끔.
3. Liscov Substitution : 리스코프 치환 원칙
하위 타입은 언제나 상위 타입으로 대체될 수 있어야 한다.
업캐스팅하는 것. 이런 저런 뱀, 호랑이, 토끼 있을 때 여러분 이라고 부를 수 있기에 편리하다.
자식 클래스는 부모 클래스의 행동 규약을 위반해서는 안된다.
IS-A 관계
4. Interface Segregation : 인터페이스 분리 원칙
하나의 거대한 인터페이스보다, 여러 개의 구체적인 인터페이스가 낫다.
인터페이스 역할 분리 해놓자.
5. Dependency Inversion : 의존성 역전 원칙
인터페이스나 추상 클래스(추상화)에 의존해라
해당 클래스에서 생성했던 것을 외부에 맡기자.
외부에서 생성한 후, 인자로 주입 받자.
ex) A가 B를 사용한다. A가 B에 의존한다. 같은 말.
주입 : 1. 생성자로 받기, 2. setter로 받기, 3. 필드 변수로 받기
2. 커피메이커 ver 1. 코드
★plantUML

★기본 골격
Coffee Maker -> Coffee -> CoffeeMachine
커피메이커가 커피의 prepare()를 부르고, 그 prepare()가 CoffeeMachine의 brew()를 부른다.
★코드 보기
1. Coffee Maker
package coffeemaker;
public class CoffeeMaker {
private Coffee coffee;
public void setCoffee(Coffee coffee){
this.coffee = coffee;//coffee를 주입. setter를 이용해서 주입했다.
}
public void makeCoffee(){
System.out.println(coffee.prepare());
}
}
- DI / IOC : 의존성 주입, 제어의 역전 원칙 사용
coffee를 외부에서 인자로 받아서 주입 받는다. 이때 주입 받는 방법으로는 setter 메소드인 setCoffee(Coffee coffee) 를 사용한다.
2. Coffee 인터페이스 및 하위 클래스들(Americano, Espresso, Latte)
package coffeemaker;
public interface Coffee {
public String prepare();
}
---------------------------------
package coffeemaker;
public class Americano implements Coffee {
private CoffeeMachine machine;
public Americano(CoffeeMachine machine) {
this.machine = machine;
}
public String prepare(){
return machine.brew()+"+Hot WATER!";
}
}
----------------------------------
package coffeemaker;
public class Espresso implements Coffee {
private CoffeeMachine machine;
public Espresso(CoffeeMachine machine) {
this.machine = machine;
}
@Override
public String prepare() {
// TODO Auto-generated method stub
return machine.brew();
}
}
------------------------------------
package coffeemaker;
public class Latte implements Coffee {
private CoffeeMachine machine;
private MilkFrother milkFrother;
public Latte(CoffeeMachine machine, MilkFrother frother) {
// TODO Auto-generated constructor stub
this.machine = machine;
this.milkFrother = frother;
}
@Override
public String prepare() {
// TODO Auto-generated method stub
return machine.brew() + milkFrother.frothMilk();
}
}
- 개방 폐쇄 원칙 : 추상화에 의존해야 하므로 Coffee 인터페이스를 만들어놨다. Coffee 인터페이스에서 주요 기능으로 prepare()를 명시해놓는다. 이후 coffee 인터페이스를 물려받은 하위 Americano, Latte, Espresso 클래스들에서 구체적으로 구현한다.
- 의존성 역전 원칙 : 각각의 prepare() 함수 내부에서 machine.brew()를 부른다. 여기서 machine은 외부에서 생성 받았다.(의존성을 주입 받았다.) 주입 방법 중 생성자를 통한 방법을 사용한 것이다.
3. CoffeeMachine 인터페이스 및 하위 클래스들
public interface CoffeeMachine {
public String brew();
}
--------------------------------
public class DripCoffeeMachine implements CoffeeMachine {
public String brew(){
return "Dripping... 커피를 내립니다.☕☕☕";
}
}
--------------------------------
public class EspressoCoffeeMachine implements CoffeeMachine {
public String brew(){
return "Extracting...Espresso를 추출합니다! 🥤";
}
}
--------------------------------
- 개방 폐쇄 원칙 : 추상화에 의존하므로 CoffeeMachine 인터페이스가 있다. 이 인터페이스에는 brew() 메소드가 단일하게 있다. 각각의 하위 클래스에는 brew()를 오버라이딩해서 구체적으로 구현해놨다.
4. MilkFrother 클래스
public class MilkFrother {
public String frothMilk(){
return "우유 거품을 내립니다...🥛🥛";
}
}
- 리스코프 치환 법칙 : MilkFrother가 커피 머신을 implements 받지 않았다. Why? milkFrother는 frothMilk()라는 고유한 메소드가 있기에 커피 머신을 상속받으면 안된다. 커피 머신은 brew()라는 메소드 하나에 집중돼있기 때문에 서로 역할이 다르다. 그렇기에 상위로 인터페이스를 물려받을 수 없다.
- 단일 책임 원칙 : frothMilk() 기능만을 가진다.
5. Main 클래스
public class Main {
public static void main(String[] args){
CoffeeMachine machine1 = new EspressoCoffeeMachine();
CoffeeMachine machine2 = new DripCoffeeMachine();
MilkFrother frother = new MilkFrother();
//에스프레소 만들기!
Coffee espresso = new Espresso(machine1);
System.out.println(espresso.prepare());
//아메리카노 만들기!
Coffee americano = new Americano(machine2);
//System.out.println(americano.getPrice());
System.out.println(americano.prepare());
//라떼만들기 - DI 의존성 주입 적용
CoffeeMaker maker = new CoffeeMaker();
maker.setCoffee(new Latte(machine1, frother));
maker.makeCoffee();
}
}
- 의존성 역전 원칙 : 의존성을 외부에서 주입받는다. 그렇기에 coffeeMaker에서 maker라는 객체를 생성한 후, setCoffee라는 setter 함수를 통해 Latte의 객체를 주입받는다. 이후 maker가 makeCoffee()를 호출하면 연쇄적으로 커피의 prepare()를 부르고, prepare()에서는 머신의 brew()를 부른다. 즉, coffeeMaker는 coffee를 사용(의존)하고, coffee는 coffeeMachine을 사용(의존)하게 된다.
- 리스코프 치환 법칙 : 만드는 과정에서 Coffee 상위 타입, 하위 커피 클래스들로 생성하는 업캐스팅을 했다.
3. 정리
1. Single Responsibility 단일 책임 원칙
- 각각의 클래스들은 주요한 책임 하나씩만을 가진다.
- 예) 인터페이스 Coffee : prepare() 명시, Latte, Americano, Espresso 각각의 하위 클래스들 prepare() 주요 기능
- 예) 인터페이스 CoffeeMachine : brew() 명시, EspressoMachine, DripCoffeeMachine 각각의 하위 클래스들 brew()하는 주요 기능
2. Open/Closed 개방/폐쇄 원칙
- 추상화에 의존하여 확장에 열려있어야 하므로, Coffee 인터페이스와 CoffeeMachine 인터페이스를 사용하여 변경에 용이하게 했다. 기존 코드를 많이 변경 않고도 새로운 기능을 추가할 수 있다.
3. Liscov Substitution 리스코프 치환 원칙
- coffeeMachine 인터페이스는 다른 기능(brew())을 하므로 milkFrother(기능 : frothMilk())가 커피 머신을 implements 하지 않았다.
- 메인 함수에서 업캐스팅을 통해 Americano, Latte, Espresso를 생성했다.
4. Interface Segregation 인터페이스 분리 원칙
- 인터페이스 Coffee와 CoffeeMachine으로 나누어 놓았다. coffee는 prepare를 하고 coffeemachine은 brew를 하므로, 기능이 다르기에 나누어 놓았다.
5. Dependency Inversion 의존성 역전 원칙
- 의존성을 외부에서 주입받는다. 그렇기에 coffeemaker에서 maker라는 객체 생성 후, setCoffee라는 setter 함수를 통해 Latte의 객체를 주입받는다. 이후 maker가 makeCoffee()를 호출하면 연쇄적으로 커피의 prepare()를 부르고, prepare()에서 머신의 brew()를 부른다. 즉, coffee maker는 coffee를 사용(의존)하고, coffee는 coffeemachine을 사용(의존)하게 된다.

4. 느낀 점/알게 된 점
사실 수업을 들으며 처음 SOLID 원칙에 대해 배울 때만 해도, 원칙들 간의 차이가 모호하게 느껴졌다. 어떤 BEFORE/AFTER 예시를 두고 어떤 원칙을 만족하는 것이냐, 물으면 제대로 답을 하지 못할 것 같았다. 그러나 이번 실습 코드를 쳐보고, 분석하면서 각각의 원칙들이 어떤 점을 염두에 두고 세워진 것인지 알게 되었다. 그리고 이후 VER2를 위해 가격 명시 기능을 추가해 보고 있는데, 더더욱 SOLID 원칙을 지키는 것이 어렵다는 것을 느끼고 있다. 기존에는 콘솔에 출력되는 것만을 목표로 했다면, 지금은 수정에 용이하고 재사용에 편하도록 SOLID 원칙을 지키려하니 생각할 것들이 많아졌다. 교수님 노션에 있는 BEFORE/AFTER 예제를 다시 여러번 보며 더 공부해야겠다.
'학교 강의 > Java프로그래밍및실습2' 카테고리의 다른 글
| [SOLID원칙] 코드 리팩토링 (1) | 2025.10.11 |
|---|---|
| [SOLID원칙] 문제 만들기 (0) | 2025.10.04 |
| [Thread] 문제 만들기 (0) | 2025.09.30 |
| [Thread] JAVA에서의 Thread (0) | 2025.09.25 |
| 제네릭과 컬렉션 5 문제 만들기 (0) | 2025.09.20 |
