"복잡한 서브시스템을 간단한 인터페이스로 제공하자"
// 문제 1: 복잡한 서브시스템 호출
public class VideoConverter {
public void convertVideo(String fileName) {
// 1. 비디오 파일 읽기
VideoFile video = new VideoFile(fileName);
// 2. 코덱 확인
Codec codec = CodecFactory.extract(video);
// 3. 비디오 버퍼 생성
VideoBuffer buffer;
if (codec instanceof MPEG4Codec) {
buffer = new MPEG4Buffer(video);
} else {
buffer = new OggBuffer(video);
}
// 4. 오디오 믹서 설정
AudioMixer mixer = new AudioMixer();
mixer.fix(buffer);
// 5. 비트레이트 계산
BitrateCalculator calculator = new BitrateCalculator();
int bitrate = calculator.calculate(buffer);
// 6. 압축
Compressor compressor = new Compressor();
buffer = compressor.compress(buffer, bitrate);
// 7. 파일 저장
FileWriter writer = new FileWriter();
writer.write(buffer, "output.mp4");
// 너무 복잡! 클라이언트가 알아야 할 게 너무 많음!
}
}
// 문제 2: 여러 서브시스템의 복잡한 초기화
public class Application {
public void start() {
// 데이터베이스 초기화
Database db = new Database();
db.connect("localhost", 3306);
db.authenticate("user", "pass");
db.selectDatabase("mydb");
// 캐시 초기화
Cache cache = new Cache();
cache.setMaxSize(1000);
cache.setEvictionPolicy("LRU");
cache.connect();
// 로거 초기화
Logger logger = new Logger();
logger.setLevel("INFO");
logger.setOutput("file");
logger.setPath("/var/log/app.log");
logger.initialize();
// 모니터링 초기화
Monitor monitor = new Monitor();
monitor.setInterval(60);
monitor.setMetrics("cpu", "memory", "disk");
monitor.start();
// 초기화 코드가 너무 복잡!
}
}
// 문제 3: 의존성이 클라이언트에 노출
public class OrderService {
public void createOrder(Order order) {
// 재고 확인
InventoryService inventory = new InventoryService();
if (!inventory.checkStock(order.getItems())) {
throw new Exception("재고 부족");
}
// 결제 처리
PaymentGateway gateway = new PaymentGateway();
gateway.initialize();
gateway.setMerchantId("MERCHANT123");
Transaction tx = gateway.createTransaction(order.getAmount());
tx.process();
// 배송 시작
ShippingService shipping = new ShippingService();
shipping.calculateShippingFee(order);
shipping.createShipment(order);
// 알림 발송
NotificationService notifier = new NotificationService();
notifier.sendEmail(order.getCustomer());
notifier.sendSMS(order.getCustomer());
// 클라이언트가 너무 많은 클래스를 알아야 함!
}
}- 복잡한 호출: 여러 서브시스템 조합 필요
- 높은 결합도: 클라이언트가 서브시스템에 강결합
- 어려운 사용: 사용법이 복잡하고 어려움
- 변경 취약: 서브시스템 변경 시 클라이언트도 수정
서브시스템의 인터페이스 집합에 대해 통합된 하나의 인터페이스를 제공하는 패턴. Facade는 서브시스템을 더 쉽게 사용할 수 있도록 상위 수준의 인터페이스를 정의한다.
- 단순화: 복잡한 서브시스템을 간단히
- 낮은 결합도: 클라이언트와 서브시스템 분리
- 사용 편의성: 고수준 인터페이스 제공
- 계층화: 서브시스템 계층 구조화
// Before: 직접 서브시스템 호출
SubsystemA a = new SubsystemA();
SubsystemB b = new SubsystemB();
SubsystemC c = new SubsystemC();
a.init();
b.configure();
c.setup();
a.process();
b.execute();
c.finalize();
// After: Facade로 간단히
Facade facade = new Facade();
facade.simpleOperation(); // 내부에서 모든 처리!┌─────────────────┐
│ Client │
└─────────────────┘
│
│ uses
▼
┌─────────────────┐
│ Facade │ ← 통합 인터페이스
├─────────────────┤
│ + operation() │
└─────────────────┘
│
│ delegates to
▼
┌────┴────┬────────┬────────┐
│ │ │ │
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│Subsys A│ │Subsys B│ │Subsys C│ │Subsys D│
└────────┘ └────────┘ └────────┘ └────────┘
| 요소 | 역할 | 예시 |
|---|---|---|
| Facade | 통합 인터페이스 제공 | HomeTheaterFacade |
| Subsystems | 실제 기능 구현 | Amplifier, DVD |
| Client | Facade 사용 | User |
/**
* Subsystem 1: 앰프
*/
public class Amplifier {
public void on() {
System.out.println("🔊 앰프 켜기");
}
public void off() {
System.out.println("🔊 앰프 끄기");
}
public void setVolume(int level) {
System.out.println("🔊 볼륨 설정: " + level);
}
public void setSurroundSound() {
System.out.println("🔊 서라운드 사운드 모드");
}
}
/**
* Subsystem 2: DVD 플레이어
*/
public class DvdPlayer {
public void on() {
System.out.println("📀 DVD 플레이어 켜기");
}
public void off() {
System.out.println("📀 DVD 플레이어 끄기");
}
public void play(String movie) {
System.out.println("📀 영화 재생: " + movie);
}
public void stop() {
System.out.println("📀 재생 정지");
}
public void eject() {
System.out.println("📀 디스크 꺼내기");
}
}
/**
* Subsystem 3: 프로젝터
*/
public class Projector {
public void on() {
System.out.println("📽️ 프로젝터 켜기");
}
public void off() {
System.out.println("📽️ 프로젝터 끄기");
}
public void wideScreenMode() {
System.out.println("📽️ 와이드스크린 모드 (16:9)");
}
}
/**
* Subsystem 4: 조명
*/
public class Lights {
public void dim(int level) {
System.out.println("💡 조명 밝기: " + level + "%");
}
public void on() {
System.out.println("💡 조명 켜기");
}
}
/**
* Subsystem 5: 스크린
*/
public class Screen {
public void down() {
System.out.println("🎬 스크린 내리기");
}
public void up() {
System.out.println("🎬 스크린 올리기");
}
}
/**
* Facade: 홈시어터 파사드
*/
public class HomeTheaterFacade {
private Amplifier amp;
private DvdPlayer dvd;
private Projector projector;
private Lights lights;
private Screen screen;
public HomeTheaterFacade() {
this.amp = new Amplifier();
this.dvd = new DvdPlayer();
this.projector = new Projector();
this.lights = new Lights();
this.screen = new Screen();
}
/**
* 영화 시청 시작 - 복잡한 과정을 간단히!
*/
public void watchMovie(String movie) {
System.out.println("\n🍿 영화 감상 모드 시작...\n");
lights.dim(10);
screen.down();
projector.on();
projector.wideScreenMode();
amp.on();
amp.setSurroundSound();
amp.setVolume(5);
dvd.on();
dvd.play(movie);
System.out.println("\n✨ 준비 완료! 영화를 즐기세요!\n");
}
/**
* 영화 시청 종료
*/
public void endMovie() {
System.out.println("\n🛑 영화 감상 모드 종료...\n");
dvd.stop();
dvd.eject();
dvd.off();
amp.off();
projector.off();
screen.up();
lights.on();
System.out.println("\n✅ 모든 장비 꺼짐\n");
}
/**
* 음악 듣기 모드
*/
public void listenToMusic(String song) {
System.out.println("\n🎵 음악 감상 모드 시작...\n");
lights.dim(30);
amp.on();
amp.setVolume(7);
dvd.on();
dvd.play(song);
System.out.println("\n✨ 음악을 즐기세요!\n");
}
}
/**
* 사용 예제
*/
public class FacadeExample {
public static void main(String[] args) {
HomeTheaterFacade homeTheater = new HomeTheaterFacade();
// Before Facade: 복잡한 과정
System.out.println("### Before Facade ###");
System.out.println("클라이언트가 각 시스템을 일일이 제어해야 함...");
// After Facade: 간단한 호출
System.out.println("\n### After Facade ###");
homeTheater.watchMovie("Inception");
System.out.println("\n" + "=".repeat(50));
homeTheater.endMovie();
System.out.println("\n" + "=".repeat(50));
homeTheater.listenToMusic("Bohemian Rhapsody");
}
}실행 결과:
### Before Facade ###
클라이언트가 각 시스템을 일일이 제어해야 함...
### After Facade ###
🍿 영화 감상 모드 시작...
💡 조명 밝기: 10%
🎬 스크린 내리기
📽️ 프로젝터 켜기
📽️ 와이드스크린 모드 (16:9)
🔊 앰프 켜기
🔊 서라운드 사운드 모드
🔊 볼륨 설정: 5
📀 DVD 플레이어 켜기
📀 영화 재생: Inception
✨ 준비 완료! 영화를 즐기세요!
==================================================
🛑 영화 감상 모드 종료...
📀 재생 정지
📀 디스크 꺼내기
📀 DVD 플레이어 끄기
🔊 앰프 끄기
📽️ 프로젝터 끄기
🎬 스크린 올리기
💡 조명 켜기
✅ 모든 장비 꺼짐
==================================================
🎵 음악 감상 모드 시작...
💡 조명 밝기: 30%
🔊 앰프 켜기
🔊 볼륨 설정: 7
📀 DVD 플레이어 켜기
📀 영화 재생: Bohemian Rhapsody
✨ 음악을 즐기세요!
/**
* Subsystem: CPU
*/
public class CPU {
public void freeze() {
System.out.println("💻 CPU: Freezing...");
}
public void jump(long position) {
System.out.println("💻 CPU: Jumping to " + position);
}
public void execute() {
System.out.println("💻 CPU: Executing...");
}
}
/**
* Subsystem: Memory
*/
public class Memory {
public void load(long position, byte[] data) {
System.out.println("💾 Memory: Loading data to position " + position);
}
}
/**
* Subsystem: HardDrive
*/
public class HardDrive {
public byte[] read(long lba, int size) {
System.out.println("💿 HardDrive: Reading " + size + " bytes from sector " + lba);
return new byte[size];
}
}
/**
* Facade: 컴퓨터
*/
public class ComputerFacade {
private static final long BOOT_ADDRESS = 0x00;
private static final long BOOT_SECTOR = 0x00;
private static final int SECTOR_SIZE = 512;
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
public ComputerFacade() {
this.cpu = new CPU();
this.memory = new Memory();
this.hardDrive = new HardDrive();
}
public void start() {
System.out.println("\n🖥️ 컴퓨터 부팅 시작...\n");
cpu.freeze();
memory.load(BOOT_ADDRESS, hardDrive.read(BOOT_SECTOR, SECTOR_SIZE));
cpu.jump(BOOT_ADDRESS);
cpu.execute();
System.out.println("\n✅ 부팅 완료!\n");
}
}
/**
* 사용 예제
*/
public class ComputerExample {
public static void main(String[] args) {
ComputerFacade computer = new ComputerFacade();
// 간단한 한 줄 호출!
computer.start();
System.out.println("Facade 없이는 CPU, Memory, HardDrive를 직접 제어해야 함!");
}
}/**
* Subsystem: 재고 관리
*/
public class InventorySystem {
public boolean checkStock(String productId, int quantity) {
System.out.println("📦 재고 확인: " + productId + " x " + quantity);
return true; // 간단히 항상 true
}
public void reserveStock(String productId, int quantity) {
System.out.println("📦 재고 예약: " + productId + " x " + quantity);
}
public void releaseStock(String productId, int quantity) {
System.out.println("📦 재고 해제: " + productId + " x " + quantity);
}
}
/**
* Subsystem: 결제 시스템
*/
public class PaymentSystem {
public boolean processPayment(String cardNumber, double amount) {
System.out.println("💳 결제 처리: " + amount + "원 (카드: " + cardNumber + ")");
return true;
}
public void refund(String transactionId, double amount) {
System.out.println("💸 환불 처리: " + amount + "원 (거래: " + transactionId + ")");
}
}
/**
* Subsystem: 배송 시스템
*/
public class ShippingSystem {
public String createShipment(String address, String productId) {
System.out.println("🚚 배송 생성: " + productId + " → " + address);
return "SHIP-" + System.currentTimeMillis();
}
public void trackShipment(String shipmentId) {
System.out.println("🔍 배송 추적: " + shipmentId);
}
}
/**
* Subsystem: 알림 시스템
*/
public class NotificationSystem {
public void sendEmail(String email, String message) {
System.out.println("📧 이메일 발송: " + email);
System.out.println(" 내용: " + message);
}
public void sendSMS(String phone, String message) {
System.out.println("📱 SMS 발송: " + phone);
System.out.println(" 내용: " + message);
}
}
/**
* Facade: 주문 시스템
*/
public class OrderFacade {
private InventorySystem inventory;
private PaymentSystem payment;
private ShippingSystem shipping;
private NotificationSystem notification;
public OrderFacade() {
this.inventory = new InventorySystem();
this.payment = new PaymentSystem();
this.shipping = new ShippingSystem();
this.notification = new NotificationSystem();
}
/**
* 주문 처리 - 복잡한 프로세스를 한 번에!
*/
public boolean placeOrder(String productId, int quantity,
String cardNumber, double amount,
String address, String email, String phone) {
System.out.println("\n🛒 주문 처리 시작...\n");
try {
// 1. 재고 확인 및 예약
if (!inventory.checkStock(productId, quantity)) {
System.out.println("❌ 재고 부족!");
return false;
}
inventory.reserveStock(productId, quantity);
// 2. 결제 처리
if (!payment.processPayment(cardNumber, amount)) {
System.out.println("❌ 결제 실패!");
inventory.releaseStock(productId, quantity);
return false;
}
// 3. 배송 생성
String shipmentId = shipping.createShipment(address, productId);
// 4. 알림 발송
notification.sendEmail(email, "주문이 완료되었습니다. 배송번호: " + shipmentId);
notification.sendSMS(phone, "주문 완료! 배송번호: " + shipmentId);
System.out.println("\n✅ 주문 성공!\n");
return true;
} catch (Exception e) {
System.out.println("❌ 주문 실패: " + e.getMessage());
return false;
}
}
/**
* 주문 취소
*/
public void cancelOrder(String productId, int quantity,
String transactionId, double amount) {
System.out.println("\n🚫 주문 취소 처리...\n");
inventory.releaseStock(productId, quantity);
payment.refund(transactionId, amount);
System.out.println("\n✅ 취소 완료!\n");
}
}
/**
* 사용 예제
*/
public class OrderExample {
public static void main(String[] args) {
OrderFacade orderSystem = new OrderFacade();
// 복잡한 주문 프로세스를 간단한 한 줄로!
boolean success = orderSystem.placeOrder(
"PROD-001", // 상품 ID
2, // 수량
"1234-5678-9012-3456", // 카드 번호
50000, // 금액
"서울시 강남구", // 주소
"[email protected]", // 이메일
"010-1234-5678" // 전화번호
);
if (success) {
System.out.println("주문이 성공적으로 완료되었습니다!");
}
// 취소도 간단히
System.out.println("\n" + "=".repeat(50));
orderSystem.cancelOrder("PROD-001", 2, "TXN-123", 50000);
}
}실행 결과:
🛒 주문 처리 시작...
📦 재고 확인: PROD-001 x 2
📦 재고 예약: PROD-001 x 2
💳 결제 처리: 50000.0원 (카드: 1234-5678-9012-3456)
🚚 배송 생성: PROD-001 → 서울시 강남구
📧 이메일 발송: [email protected]
내용: 주문이 완료되었습니다. 배송번호: SHIP-1234567890
📱 SMS 발송: 010-1234-5678
내용: 주문 완료! 배송번호: SHIP-1234567890
✅ 주문 성공!
주문이 성공적으로 완료되었습니다!
==================================================
🚫 주문 취소 처리...
📦 재고 해제: PROD-001 x 2
💸 환불 처리: 50000.0원 (거래: TXN-123)
✅ 취소 완료!
| 장점 | 설명 | 예시 |
|---|---|---|
| 단순화 | 복잡한 시스템을 간단히 | 홈시어터 제어 |
| 낮은 결합도 | 클라이언트와 서브시스템 분리 | 주문 시스템 |
| 사용 편의성 | 고수준 인터페이스 | watchMovie() |
| 계층화 | 서브시스템 조직화 | MVC 패턴 |
| 단점 | 설명 | 해결책 |
|---|---|---|
| God Object 가능성 | Facade가 너무 커질 수 있음 | 책임 분리 |
| 유연성 감소 | 고급 기능 접근 어려움 | 직접 접근도 허용 |
// 잘못된 예: 모든 기능을 Facade에
public class GodFacade {
public void doEverything() {
// 너무 많은 책임!
}
}해결:
// 여러 Facade로 분리
public class OrderFacade { }
public class UserFacade { }
public class ProductFacade { }✅ 복잡한 서브시스템 파악
✅ Facade 클래스 생성
✅ 고수준 메서드 정의
✅ 서브시스템 호출 캡슐화
✅ 클라이언트는 Facade만 사용
| 상황 | 추천도 | 이유 |
|---|---|---|
| 복잡한 라이브러리 | ⭐⭐⭐ | 사용 간소화 |
| 레이어 분리 | ⭐⭐⭐ | 계층 구조 |
| 레거시 통합 | ⭐⭐⭐ | 인터페이스 단순화 |
- 복잡함을 숨김
- 사용 편의성 극대화
- 서브시스템 독립성 유지
- 계층화 핵심 패턴