#5 SOLID - 커피메이커v.1

지우·2026년 1월 4일

java2

목록 보기
7/13

SOLID 원칙

결합도 맞게 응집도 높게

1) SRP 단일 책임의 원칙 : 기능별로 클래스 분리
2) OCP 개방-폐쇄 원칙 : 기존 코드를 변경하지 새로운 기능 추가할 수 있어야함 -> 추상화!!
3) LSP 리스코프 치환 원칙 : 하위 타입은 언제나 상위 타입으로 대체 가능해야함 -> 상속 잘 사용
4) ISP 인터페이스 분리 원칙 : 클래스가 불필요한 메서드를 구현하지 않도록 강제 -> 여러 개의 구체적인 인터페이스 구현
5) DIP 의존성 역전 원칙 : 구체적인 구현 클래스에 직접 의존 X -> 인터페이스나 추상 클래스에 의존
제어의 역전(IoC) -> 의존성 주입(DI)
: 객체의 생성부터 생명주기 관리까지 모든 것을 프레임워크가 실행 -> 개발자는 어떤 부품이 필요한지만 알려줌
: 구현방법 1) 생성자 주입 2) Setter 주입 3) 필드 주입

커피메이커 v.1 리팩토링

interface CoffeeMachine {
	String brew(); 
}

CoffeeMachine 인터페이스

class EspressoMachine implements CoffeeMachine {

	@Override
	public String brew() { //ISP, 추상화
		// TODO Auto-generated method stub
		return "Extracting Espresso";
	}
}
class DripCoffeeMachine implements CoffeeMachine {

	@Override
	public String brew() { //ISP, 추상화
		// TODO Auto-generated method stub
		return "Dripping Coffee";
	}
}

EspressoMachine / DripCoffeeMachine 클래스


ISP 원칙 + 추상화 : 클래스가 불필요한 메소드 구현 X, 구체적 인터페이스 구현
CoffeeMachine 인터페이스를 구현한 클래스

interface Coffee {
	String prepare(); 
}
Coffee 인터페이스
SRP, OCP 기반 다형성 : 행동 중심 관점 인터페이스 -> 캡슐화
=> 커피 음료 추가 시 기존 코드 변경 없이 확장 가능 (OCP)
class Espresso implements Coffee { //DI + SRP
	private final CoffeeMachine espressoMachine;
	
	public Espresso(CoffeeMachine espressoMachine) {
		this.espressoMachine = espressoMachine;
	}
	public String prepare() {
		return espressoMachine.brew();
	}
}
class Americano  implements  Coffee {
	private final CoffeeMachine espressoMachine;
	
	public Americano(CoffeeMachine espressoMachine) {
		this.espressoMachine = espressoMachine;
	}
	public String prepare() {
		return espressoMachine.brew() + " + hot water";
	}
}
Espresso, Americano 클래스
SRP : 각 클래스는 한 가지 음료에 대한 한 가지 기능 책임만 지고 있음 -> prepare() 만!
DI : this.espressoMachine = espressoMachine -> 스스로 의존 객체 생성 안하고 외부에서 주입받고 있음
class Latte implements Coffee { //합성 + OCP
	private final Coffee espresso;
	private final MilkFrother milkFrother;
	
	public Latte(Coffee espresso, MilkFrother milkFrother) {
		this.espresso = espresso;
		this.milkFrother = milkFrother;
	}
	public String prepare() { //얘만 왜 다른지 이해 못함
		return espresso.prepare() + " + " + milkFrother.frothMilk();
	}
}
Latte 클래스
합성 : 이미 만들어진 Espresso + 우유
OCP : 기존의 Latte나 Espresso 코드 수정 필요 없이 새로운 기능 추가 가능
ex) 바닐라 라떼를 새롭게 만들고 싶다면 Latte 클래스 그대로 활용하거나 Coffee 주입받아 시럽을 추가하는 VanillaLatte 클래스 새로 만들면 됨 => 코드의 유연성
class CoffeeMaker {
	private Coffee coffee;
	
	public void setCoffee(Coffee coffee) { 
		this.coffee = coffee;
	}
	
	public void makeCoffee() {
		System.out.println(coffee.prepare());
	}

}
CoffeeMaker 클래스
내부에 커피 객체 주입받아서 prepare( ) 동작 수행
DI + IoC : setter DI 사용 -> 외부에서 필요한 Coffee 주입 (DIP)
class MilkFrother { //SRP
	public String frothMilk() {
		return "Frothing Milk";
	}
}
MilkFrother 클래스
SRP : 우유 만드는 클래스 분리
public class Main {
	public static void main(String[] args) {
		EspressoMachine espressoMachine = new EspressoMachine();
		MilkFrother milkFrother = new MilkFrother();
		
		Coffee espresso = new Espresso(espressoMachine);
		System.out.println(espresso.prepare());
		
		Coffee latte = new Latte(espresso, milkFrother);
		System.out.println(latte.prepare());
		
		CoffeeMaker maker = new CoffeeMaker(); //DI 적용해서 라떼 생성
		maker.setCoffee(latte);
		maker.makeCoffee();
	}
}
Main
라떼 생성하는 두 가지 방법
1) 직접 호출 방식 : Latte 객체 직접 사용 -> prepare( ) 호출
2) DI 방식 : CoffeeMaker에 실행 권한 넘김 (DIP, LSP, 전략패턴)

0개의 댓글