커피 음료 주문? 조합? 시스템으로 데코레이터 패턴을
예시로 들고 있다.
데코레이터의 슈퍼클래스는 자신이 장식하고있는 객체의 슈퍼클래스와같다?
데코 레이터는 자신이 장식하고있는 객체에 어떤 행동을 위임하는 일 말고 추가 작업 수행가능
객체는 언제든지 감쌀 수 있으므로 실행중에 필요한 데코레이터를 마음대로 적용가능
abstract class Coffee {
String description = "Unknown Coffee";
public String getDescription() {
return description;
}
public abstract double cost();
}
class Espresso extends Coffee {
public Espresso() {
description = "에스프레소";
}
public double cost() {
return 1.99;
}
}
abstract class CondimentDecorator extends Coffee {
public abstract String getDescription();
}
class Milk extends CondimentDecorator {
Coffee coffee;
public Milk(Coffee coffee) {
this.coffee = coffee;
}
public String getDescription() {
return coffee.getDescription() + ", 우유";
}
public double cost() {
return .20 + coffee.cost();
}
}
class Mocha extends CondimentDecorator {
Coffee coffee;
public Mocha(Coffee coffee) {
this.coffee = coffee;
}
public String getDescription() {
return coffee.getDescription() + ", 모카";
}
public double cost() {
return .30 + coffee.cost();
}
}
public class Main {
public static void main(String[] args) {
Coffee espresso = new Espresso();
System.out.println(espresso.getDescription() + " $" + espresso.cost());
Coffee espressoWithMilk = new Milk(espresso);
System.out.println(espressoWithMilk.getDescription() + " $" + espressoWithMilk.cost());
Coffee espressoWithMilkAndMocha = new Mocha(espressoWithMilk);
System.out.println(espressoWithMilkAndMocha.getDescription() + " $" + espressoWithMilkAndMocha.cost());
}
}
기본 컴포넌트(위 예시에서는 Coffee 추상 클래스)를 정의합니다. 이 컴포넌트는 데코레이터와 실제 객체가 공유하는 인터페이스를 제공합니다.
구체적인 컴포넌트(위 예시에서는 Espresso 클래스)를 기본 컴포넌트를 상속받아 구현합니다. 이는 데코레이터로 감쌀 실제 객체가 됩니다.
추상 데코레이터(CondimentDecorator 추상 클래스)를 정의합니다. 이 데코레이터는 기본 컴포넌트와 동일한 인터페이스를 가지며, 감쌀 객체에 대한 참조를 유지합니다.
구체적인 데코레이터(Milk, Mocha 클래스)를 추상 데코레이터를 상속받아 구현합니다. 각 데코레이터는 감싸고 있는 객체의 기능을 호출하고, 자신만의 추가 기능을 수행합니다.
클라이언트 코드에서는 기본 객체를 생성하고, 필요에 따라 다양한 데코레이터로 감쌉니다. 이렇게 하면 런타임에 객체의 행동을 동적으로 확장할 수 있습니다.
위 예시에서는 Espresso라는 기본 커피 객체를 생성하고, 이를 Milk와 Mocha 데코레이터로 차례로 감쌌습니다. 각 데코레이터는 커피 객체에 자신만의 설명(description)과 가격(cost)을 추가하였죠.
이처럼 데코레이터 패턴을 사용하면 객체에 추가 책임을 동적으로 부여할 수 있습니다. 새로운 클래스를 정의하지 않고도 조합을 통해 기능을 유연하게 확장할 수 있어 효과적입니다. 특히 런타임에 객체의 행동을 변경해야 하는 경우에 유용하게 활용될 수 있습니다.
데코레이터패턴으로 객체의 추가 요소를 동적으로 더 할 수 있따. 데코레이터를 사용하면 서브클래스를 만들 때 보다 훨씬 더 유연하게 기능 확장가능
최상위 부모 클래스(기본컴포턴트)를 두개가 상속받는다
구현해야될 구현체라 표현해야되나?
구체적 컴포넌트라 하자.
그리고 추상 데코레이터와 구체적 데코레이터를 만든다.
추상 데코레이터는 기본컴포넌트를 참조하고 있다.
인상적인건 데코레이터는 새로운 메소드 추가할 수 있지만
대신 원래 컴포넌트에있떤 메소드를 별도의 작업으로 처리한다
뒤에 코드에서도 보통 그런식으로 예제를 구현함.
책에서는 인터페이스를 써도되지만
기존 코드 고치는일없이 그냥 추상클래스를써서 데코레이터 패턴을 구현함.
데코레이터 구현체 (첨가물 코드)
감싸고자 하는 음료 저장하는 인스턴스 변수
인스턴스 변수를 감싸고자 하는 객체로 설정하는 생성자
(데코레이터의 생성자에 감싸고자하는 음료 객체를 전달 방식 사용)
설명에 데코레이터 추가! 설명을 추가 여기가 실질적인 데코?
코스트도 마찬가지
책을보고 코드를 보니 어떻게 구조가 됬는지 대략적으로 이해되는게 신기하다
근데 이런 식의 패턴의 단점은
잡다한 클래스가 너무많아짐.. 그래두 이제 알고하니
클래스가 어떻게 구성되어있는지 파악은 좀 더 쉬워질듯
한번 살펴본결과 여러가지를 조합해서 유연하게 조합할수 있지만
단점 그대로 클래스 관계가 너무복잡하다.
이게 어떻게 유용한 패턴일까?
책에선 상속을 사용했지만 인터페이스로 구현하는게 더 좋은 거같다.
interface Calculator {
int add(int a, int b);
}
class BasicCalculator implements Calculator {
public int add(int a, int b) {
return a + b;
}
}
class LoggingDecorator implements Calculator {
private Calculator calculator;
public LoggingDecorator(Calculator calculator) {
this.calculator = calculator;
}
public int add(int a, int b) {
System.out.println("Before add(): " + a + ", " + b);
int result = calculator.add(a, b);
System.out.println("After add(): " + result);
return result;
}
}
public class Main {
public static void main(String[] args) {
Calculator calculator = new BasicCalculator();
Calculator loggingCalculator = new LoggingDecorator(calculator);
int result1 = calculator.add(3, 4);
int result2 = loggingCalculator.add(3, 4);
}
}
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class TransactionInterceptor implements InvocationHandler {
private PlatformTransactionManager transactionManager;
private TransactionDefinition transactionDefinition;
private Object target;
public TransactionInterceptor(PlatformTransactionManager transactionManager, TransactionDefinition transactionDefinition, Object target) {
this.transactionManager = transactionManager;
this.transactionDefinition = transactionDefinition;
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
TransactionStatus status = transactionManager.getTransaction(transactionDefinition);
try {
Object result = method.invoke(target, args);
transactionManager.commit(status);
return result;
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
public class TransactionAspectSupport {
private PlatformTransactionManager transactionManager;
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@SuppressWarnings("unchecked")
public <T> T wrap(T target) {
TransactionDefinition definition = new DefaultTransactionDefinition();
TransactionInterceptor interceptor = new TransactionInterceptor(transactionManager, definition, target);
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), interceptor);
}
}
// 애플리케이션 설정
PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
TransactionAspectSupport aspectSupport = new TransactionAspectSupport();
aspectSupport.setTransactionManager(transactionManager);
UserService userService = new UserServiceImpl(userRepository);
UserService proxiedUserService = aspectSupport.wrap(userService);
import org.springframework.transaction.annotation.Transactional;
public interface UserService {
void createUser(User user);
}
public class UserServiceImpl implements UserService {
private UserRepository userRepository;
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
@Transactional
public void createUser(User user) {
userRepository.save(user);
// 추가 로직
}
}
public interface UserRepository {
User findById(Long id);
void save(User user);
}
public class SimpleUserRepository implements UserRepository {
private Map<Long, User> database = new HashMap<>();
@Override
public User findById(Long id) {
// 데이터베이스에서 사용자 조회 로직
return database.get(id);
}
@Override
public void save(User user) {
// 데이터베이스에 사용자 저장 로직
database.put(user.getId(), user);
}
}
public class CachingDecorator implements UserRepository {
private UserRepository userRepository;
private Map<Long, User> cache = new HashMap<>();
public CachingDecorator(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public User findById(Long id) {
// 캐시에서 사용자 조회
User user = cache.get(id);
if (user != null) {
System.out.println("Returning user from cache: " + user);
return user;
}
// 캐시에 없으면 데이터베이스에서 조회
user = userRepository.findById(id);
if (user != null) {
// 조회한 사용자 정보를 캐시에 저장
cache.put(id, user);
System.out.println("Returning user from database: " + user);
}
return user;
}
@Override
public void save(User user) {
// 데이터베이스에 사용자 저장
userRepository.save(user);
// 캐시에 사용자 정보 업데이트
cache.put(user.getId(), user);
System.out.println("User saved: " + user);
}
}
public class User {
private Long id;
private String firstName;
private String lastName;
// 생성자, 게터, 세터 생략
}
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new SimpleUserRepository();
UserRepository cachingUserRepository = new CachingDecorator(userRepository);
// 새로운 사용자 저장
User user1 = new User(1L, "John", "Doe");
cachingUserRepository.save(user1);
// 저장된 사용자 조회
User retrievedUser1 = cachingUserRepository.findById(1L);
// 저장되지 않은 사용자 조회
User retrievedUser2 = cachingUserRepository.findById(2L);
// 이미 조회한 사용자 다시 조회
User retrievedUser3 = cachingUserRepository.findById(1L);
}
}