객체의 결합을 통해 기능을 동적으로 유연하게 확장할 수 있게 하는 패턴
자주 사용되는 경우 : 데이터 압축 라이브러리 (Data Compression Library), 데이터베이스 접근 라이브러리 (Database Access Library), 결제 처리 시스템 (Payment Processing System), 로깅 라이브러리 (Logging Library), UI 테마 변경 (UI Theme Switching), AI 알고리즘 선택 (AI Algorithm Selection), 비밀번호 해싱 (Password Hashing), 라우팅 알고리즘 (Routing Algorithm), 캐싱 전략 (Caching Strategy), 문서 포맷 변환 (Document Format Conversion)
데코레이터(Decorator) 장점
데코레이터(Decorator) 단점
데코레이터 패턴 예제) 작업 진행 기록을 관리하는 인터페이스와 오브젝트 구성
1. public interface TaskService { : 서비스 기능들을 하는 함수들 인터페이스로 구성
2. public abstract class TaskServiceDecorator implements TaskService { : TaskService를 상속받고 추상 클래스로 선어
3. public TaskServiceDecorator(TaskService decoratedTaskService) { : 생성자 생성 및 인터페이스를 활용할 오브젝트에 값 할당
4. @Override public void assignTask(String task, String assignee) { : 서비스 기능들을 상속하고 호출하는 구조의 함수package Decorator; // TaskService 인터페이스: 작업 관리 서비스의 공통 인터페이스를 정의합니다. public interface TaskService { void assignTask(String task, String assignee); void updateTaskStatus(String task, String status); } package Decorator; // TaskServiceDecorator 추상 클래스: 작업 관리 서비스 데코레이터의 기본 구조를 정의합니다. public abstract class TaskServiceDecorator implements TaskService { protected TaskService decoratedTaskService; public TaskServiceDecorator(TaskService decoratedTaskService) { this.decoratedTaskService = decoratedTaskService; } @Override public void assignTask(String task, String assignee) { decoratedTaskService.assignTask(task, assignee); } @Override public void updateTaskStatus(String task, String status) { decoratedTaskService.updateTaskStatus(task, status); } }
데코레이터 패턴 예제) 작업 진행 기록들을 하고 이를 추적하는 클래스 구성
5. public class BasicTaskService implements TaskService { : TaskService 인터페이스를 상속하고 서비스가 작동하는 함수들을 구성하는 클래스 생성package Decorator; // BasicTaskService 클래스: 기본 작업 관리 서비스를 구현합니다. public class BasicTaskService implements TaskService { @Override public void assignTask(String task, String assignee) { // 실제 작업 할당 로직 System.out.println("Task '" + task + "' assigned to " + assignee); } @Override public void updateTaskStatus(String task, String status) { // 실제 작업 상태 업데이트 로직 System.out.println("Task '" + task + "' status updated to " + status); } }
- public class LoggingDecorator extends TaskServiceDecorator { : TaskServiceDecorator 클래스를 상속하고 따로 로그들을 남기는 기능들을 구성하는 클래스 생성
package Decorator; // LoggingDecorator 클래스: 작업 관리 서비스에 로깅 기능을 추가하는 데코레이터를 구현합니다. public class LoggingDecorator extends TaskServiceDecorator { public LoggingDecorator(TaskService decoratedTaskService) { super(decoratedTaskService); } @Override public void assignTask(String task, String assignee) { System.out.println("Logging: Assigning task '" + task + "' to " + assignee); super.assignTask(task, assignee); System.out.println("Logging: Task '" + task + "' assigned to " + assignee); } @Override public void updateTaskStatus(String task, String status) { System.out.println("Logging: Updating task '" + task + "' status to " + status); super.updateTaskStatus(task, status); System.out.println("Logging: Task '" + task + "' status updated to " + status); } }
- public class NotificationDecorator extends TaskServiceDecorator { : TaskServiceDecorator 클래스를 상속하고 할당되거나 업데이트 된 작업을 알리는 기능들로 구성된 클래스 생성
- package Decorator;
// NotificationDecorator 클래스: 작업 상태 업데이트 시 알림 기능을 추가하는 데코레이터를 구현합니다.- @Override public void assignTask(String task, String assignee) { : 작업 기능들을 상속 후, 알람을 울리는 기능들을 구성
- private void notifyAssignee(String task, String message) { : 알람시에 구성하는 메세지 포맷을 구성
public class NotificationDecorator extends TaskServiceDecorator { public NotificationDecorator(TaskService decoratedTaskService) { super(decoratedTaskService); } @Override public void assignTask(String task, String assignee) { super.assignTask(task, assignee); notifyAssignee(task, assignee); } @Override public void updateTaskStatus(String task, String status) { super.updateTaskStatus(task, status); notifyAssignee(task, status); } private void notifyAssignee(String task, String message) { // 간단한 알림 로직 (예: 콘솔 출력) System.out.println("Notification: Task '" + task + "' - " + message); } }
데코레이터 패턴 예제) 모든기능들을 이행하는 메인 클래스 구성
import Decorator.BasicTaskService; import Decorator.LoggingDecorator; import Decorator.NotificationDecorator; import Decorator.TaskService; public class MainByDecorator { public static void main(String[] args) { TaskService taskService = new BasicTaskService(); // 작업 관리 서비스에 로깅 데코레이터 추가 taskService = new LoggingDecorator(taskService); // 작업 관리 서비스에 상태 알림 데코레이터 추가 taskService = new NotificationDecorator(taskService); // 작업 할당 taskService.assignTask("Design Database Schema", "Alice"); System.out.println(); // 작업 상태 업데이트 taskService.updateTaskStatus("Design Database Schema", "In Progress"); } }
전체적 구조 :
특정 객체를 직접 참조하지 않고 해당 객체를 대행(프락시)하는 객체를 통해 접근하는 패턴
자주 사용되는 경우 : 지연 로딩 (Lazy Loading), 액세스 제어 (Access Control), 원격 프록시 (Remote Proxy), 캐싱 (Caching), 로깅 및 모니터링 (Logging and Monitoring), 트랜잭션 관리 (Transaction Management), 외부 서비스 연결 (External Service Connection), 원격 서비스 접근 (Remote Service Access)
프록시(Proxy) 장점
프록시(Proxy) 단점
프록시 종류: 기본형, 가상형, 보호, 로깅, 원격, 캐싱
https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%94%84%EB%A1%9D%EC%8B%9CProxy-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90
캐싱 프록시
캐싱 프록시 예제) 캐싱 프록시로 데이터를 캐싱에저장
1.public interface DatabaseService {: 쿼리를 캐시에 저장하는 인터페이스 사용.package CachingProxy; public interface DatabaseService { String queryDatabase(String query); }
2.class RealDatabaseService implements DatabaseService{ : 데이터베이스 서비스 기능을 하는 클래스 구성.
3.public String queryDatabase(String query) { : 데이터베이스 서비스 기능을 하는 클래스 구성이며 지연시뮬레이션을 넣어 캐싱의 성능 테스트가 용이해짐.package CachingProxy; class RealDatabaseService implements DatabaseService{ @Override public String queryDatabase(String query) { // Simulate a costly database query try { Thread.sleep(3000); // Simulate delay } catch (InterruptedException e) { e.printStackTrace(); } return "Result for query: " + query; } }
4.public class CachingDatabaseProxy implements DatabaseService{ : 데이터베이스 서비스 기능을 하는 클래스 구성이며 지연시뮬레이션을 넣어 캐싱의 성능 테스트가 용이해짐.
5.String result = realService.queryDatabase(query); : 지연시뮬레이션을 시행하여 캐시에 데이터를 삽입.package CachingProxy; import java.util.HashMap; import java.util.Map; public class CachingDatabaseProxy implements DatabaseService{ private RealDatabaseService realService = new RealDatabaseService(); private Map<String, String> cache = new HashMap<>(); @Override public String queryDatabase(String query) { if (!cache.containsKey(query)){ // 실제 서비스에서 쿼리 결과를 가져와 캐시에 저장 String result = realService.queryDatabase(query); cache.put(query, result); } else{ // 캐시에 저장된 결과 반환 System.out.println("Returning cached result for query: " + query); } return cache.get(query); } }
캐싱 패턴 예제) 모든 기능들을 이행하는 메인 클래스 구성
import CachingProxy.CachingDatabaseProxy; import CachingProxy.DatabaseService; public class MainByCachingProxy { public static void main(String[] args) { DatabaseService service = new CachingDatabaseProxy(); // First call - result is not cached System.out.println(service.queryDatabase("SELECT * FROM users")); // Second call - result should be cached System.out.println(service.queryDatabase("SELECT name FROM users")); // Third call - different query, result is not cached System.out.println(service.queryDatabase("SELECT type FROM orders")); } }
전체적 구조 :
원격 프록시
원격 프록시 예제) 원격 프록시로 데이터를 패칭
1.public interface DatabaseService {: 쿼리를 캐시에 저장하는 인터페이스 사용.package RemoteProxy; import java.rmi.Remote; import java.rmi.RemoteException; // Remote Interface public interface RemoteService extends Remote { String fetchData(String param) throws RemoteException; }
2.public class RealRemoteService extends UnicastRemoteObject implements RemoteService { : 리모트서비스 인터페이스 상속 및 UnicastRemoteObject 상속.
3.@Override public String fetchData(String param) throws RemoteException {: fetchData의 기능을 Override 및 활용.package RemoteProxy; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; // RealSubject public class RealRemoteService extends UnicastRemoteObject implements RemoteService { protected RealRemoteService() throws RemoteException { super(); } @Override public String fetchData(String param) throws RemoteException { // Simulate fetching data from a remote server return "Data from server for " + param; } }
4.public class RemoteServiceProxy implements RemoteService { : RemoteService를 상속 및 생성 자와 서비스 기능 추가.
5.@Override public String fetchData(String param) throws RemoteException { : fetchData로 재귀 형태로 서비스 기능을 활용package RemoteProxy; import java.rmi.RemoteException; // Proxy public class RemoteServiceProxy implements RemoteService { private final RemoteService realService; public RemoteServiceProxy(RemoteService realService) { this.realService = realService; } @Override public String fetchData(String param) throws RemoteException { System.out.println("Proxy: Fetching data for " + param); return realService.fetchData(param); } }
원격 프록시 예제) 서버 기능을 실행하여, 원격이 가능하게함
package RemoteProxy; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; // Server public class Server { public static void main(String[] args) { try { RealRemoteService realService = new RealRemoteService(); Registry registry = LocateRegistry.createRegistry(1099); registry.bind("RemoteService", realService); System.out.println("Server started"); } catch (Exception e) { e.printStackTrace(); } } }
원격 프록시 예제) 모든 기능들을 이행하는 메인 클래스 구성
import RemoteProxy.RemoteService; import RemoteProxy.RemoteServiceProxy; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class MainByRemoteProxy { public static void main(String[] args) { try { Registry registry = LocateRegistry.getRegistry("localhost", 1099); RemoteService realService = (RemoteService) registry.lookup("RemoteService"); RemoteServiceProxy proxy = new RemoteServiceProxy(realService); // Fetch data through the proxy System.out.println(proxy.fetchData("test1")); System.out.println(proxy.fetchData("test2")); } catch (Exception e) { e.printStackTrace(); } } }
전체적 구조 :
서브시스템의 인터페이스 집합들에 하나의 통합된 인터페이스를 제공하는 패턴
자주 사용되는 경우 : 서브시스템의 단순화 (Simplification of Subsystems), 외부 API와의 통합 (Integration with External APIs), 크로스커팅 관심사 처리 (Handling Cross-Cutting Concerns), 레거시 코드와의 통합 (Integration with Legacy Code), 테스트 용이성 향상 (Improving Testability), 사용자 인증 (User Authentication), 결제 시스템 (Payment System)
퍼사드(Facade) 장점
퍼사드(Facade) 단점
퍼사드 예제) DBMS 시스템 재구성 프로그램
퍼사드 예제) 클래스들의 구성 Row,Cache,DBMS,Message
1. class Row { : 데이터에 저장되는 형식 정보package Facade; class Row { private String name; private String birthday; private String email; public Row(String name, String birthday, String email) { this.name = name; this.birthday = birthday; this.email = email; } public String getName() { return name; } public String getBirthday() { return birthday; } public String getEmail() { return email; } }
- class DBMS { : 데이터베이스 저장 및 호출.
package Facade; import java.util.HashMap; // 데이터베이스 역할을 하는 클래스 class DBMS { private HashMap<String, Row> db = new HashMap<>(); public void put(String name, Row row) { db.put(name, row); } // 데이터베이스에 쿼리를 날려 결과를 받아오는 메소드 public Row query(String name) { try { Thread.sleep(500); // DB 조회 시간을 비유하여 0.5초대기로 구현 } catch (InterruptedException e) { } return db.get(name.toLowerCase()); } }
- class Cache { : 캐시에 저장 및 호출.
package Facade; import java.util.HashMap; // 데이터베이스 역할을 하는 클래스 class DBMS { private HashMap<String, Row> db = new HashMap<>(); public void put(String name, Row row) { db.put(name, row); } // 데이터베이스에 쿼리를 날려 결과를 받아오는 메소드 public Row query(String name) { try { Thread.sleep(500); // DB 조회 시간을 비유하여 0.5초대기로 구현 } catch (InterruptedException e) { } return db.get(name.toLowerCase()); } }
- class Message { : 데이터를 출력하는 구성.
package Facade; // Row 클래스를 보기좋게 출력하는 클래스 class Message { private Row row; public Message(Row row) { this.row = row; } public String makeName() { return "Name : \"" + row.getName() + "\""; } public String makeBirthday() { return "Birthday : " + row.getBirthday(); } public String makeEmail() { return "Email : " + row.getEmail(); } }
- public class Facade { : 파사드 패턴으로 클래스들 호출 구성.
- public void insert() { : 데이터 입력.
- public void run(String name) { : 데이터 결과 출력.
package Facade; public class Facade { private DBMS dbms = new DBMS(); private Cache cache = new Cache(); public void insert() { dbms.put("홍길동", new Row("홍길동", "1890-02-14", "honggildong@naver.com")); dbms.put("임꺽정", new Row("임꺽정", "1820-11-02", "imgguckjong@naver.com")); dbms.put("주몽", new Row("주몽", "710-08-27", "jumong@naver.com")); } public void run(String name) { Row row = cache.get(name); // 1. 만약 캐시에 없다면 if (row == null){ row = dbms.query(name); // DB에 해당 데이터를 조회해서 row에 저장하고 if(row != null) { cache.put(row); // 캐시에 저장 } } // 2. dbms.query(name)에서 조회된 값이 있으면 if(row != null) { Message message = new Message(row); System.out.println(message.makeName()); System.out.println(message.makeBirthday()); System.out.println(message.makeEmail()); } // 3. 조회된 값이 없으면 else { System.out.println(name + " 가 데이터베이스에 존재하지 않습니다."); } } }
파사드 패턴 예제) 모든 기능들을 이행하는 메인 클래스 구성
import Facade.*; public class MainByFacade { public static void main(String[] args) { // 1. 퍼사드 객체 생성 Facade facade = new Facade(); // 2. db 값 insert facade.insert(); // 3. 퍼사드로 데이터베이스 & 캐싱 & 메세징 로직을 한번에 조회 String name = "홍길동"; facade.run(name); } }
전체적 구조 :
https://inpa.tistory.com/entry/GOF-💠-템플릿-메소드Template-Method-패턴-제대로-배워보자
https://dev-coco.tistory.com/177