데코레이터 패

존스노우·2024년 4월 8일
0

디자인패턴

목록 보기
2/4

  • 커피 음료 주문? 조합? 시스템으로 데코레이터 패턴을

  • 예시로 들고 있다.

  • 데코레이터의 슈퍼클래스는 자신이 장식하고있는 객체의 슈퍼클래스와같다?

    • 코드로봐야 좀더 이해가 될거같다.
  • 데코 레이터는 자신이 장식하고있는 객체에 어떤 행동을 위임하는 일 말고 추가 작업 수행가능

    • 키포인트? 흐음
  • 객체는 언제든지 감쌀 수 있으므로 실행중에 필요한 데코레이터를 마음대로 적용가능

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());
    }
}
  • 코드 예시.
  1. 기본 컴포넌트(위 예시에서는 Coffee 추상 클래스)를 정의합니다. 이 컴포넌트는 데코레이터와 실제 객체가 공유하는 인터페이스를 제공합니다.

  2. 구체적인 컴포넌트(위 예시에서는 Espresso 클래스)를 기본 컴포넌트를 상속받아 구현합니다. 이는 데코레이터로 감쌀 실제 객체가 됩니다.

  3. 추상 데코레이터(CondimentDecorator 추상 클래스)를 정의합니다. 이 데코레이터는 기본 컴포넌트와 동일한 인터페이스를 가지며, 감쌀 객체에 대한 참조를 유지합니다.

  4. 구체적인 데코레이터(Milk, Mocha 클래스)를 추상 데코레이터를 상속받아 구현합니다. 각 데코레이터는 감싸고 있는 객체의 기능을 호출하고, 자신만의 추가 기능을 수행합니다.

  5. 클라이언트 코드에서는 기본 객체를 생성하고, 필요에 따라 다양한 데코레이터로 감쌉니다. 이렇게 하면 런타임에 객체의 행동을 동적으로 확장할 수 있습니다.

  6. 위 예시에서는 Espresso라는 기본 커피 객체를 생성하고, 이를 Milk와 Mocha 데코레이터로 차례로 감쌌습니다. 각 데코레이터는 커피 객체에 자신만의 설명(description)과 가격(cost)을 추가하였죠.

이처럼 데코레이터 패턴을 사용하면 객체에 추가 책임을 동적으로 부여할 수 있습니다. 새로운 클래스를 정의하지 않고도 조합을 통해 기능을 유연하게 확장할 수 있어 효과적입니다. 특히 런타임에 객체의 행동을 변경해야 하는 경우에 유용하게 활용될 수 있습니다.

  • 따로 궁금해서 물어본 예시.

데코레이터 패턴의 정의

데코레이터패턴으로 객체의 추가 요소를 동적으로 더 할 수 있따. 데코레이터를 사용하면 서브클래스를 만들 때 보다 훨씬 더 유연하게 기능 확장가능

  • 최상위 부모 클래스(기본컴포턴트)를 두개가 상속받는다

  • 구현해야될 구현체라 표현해야되나?

  • 구체적 컴포넌트라 하자.

  • 그리고 추상 데코레이터와 구체적 데코레이터를 만든다.

  • 추상 데코레이터는 기본컴포넌트를 참조하고 있다.

  • 인상적인건 데코레이터는 새로운 메소드 추가할 수 있지만

  • 대신 원래 컴포넌트에있떤 메소드를 별도의 작업으로 처리한다

  • 뒤에 코드에서도 보통 그런식으로 예제를 구현함.

  • 책에서는 인터페이스를 써도되지만

  • 기존 코드 고치는일없이 그냥 추상클래스를써서 데코레이터 패턴을 구현함.

코드

  • 기본 컴포넌트 .

  • 추상 데코레이터 클래스
  • 각 데코레이터가 감쌀 음료를 나타내는 Beberage객체 지정
  • 어떤 음료든 감쌀수 있지!


  • 구체적 컴포넌트 예시

  • 데코레이터 구현체 (첨가물 코드)

  • 감싸고자 하는 음료 저장하는 인스턴스 변수

  • 인스턴스 변수를 감싸고자 하는 객체로 설정하는 생성자
    (데코레이터의 생성자에 감싸고자하는 음료 객체를 전달 방식 사용)

  • 설명에 데코레이터 추가! 설명을 추가 여기가 실질적인 데코?

  • 코스트도 마찬가지

  • 실행 코드

  • 구상 구성요소(Concrete Component)는 데코레이터 패턴에서 실제로 기능을 구현하는 클래스
    • HouseBlend, DarkRoast 등이 구상 구성요소
  • 읽어보면서 이해가 안돼는 것들을 머리속에 조금씩 정리하는대 단어가 번역을해서그런가 모호한 것도 있따

자바 I/O (데코레이터 적용)

  • 앞선예제와 글을읽으니 조금씩이해되는데 더알아보자

  • FilterInputStream을 주로도 구상 데코레이터들




  • 책을보고 코드를 보니 어떻게 구조가 됬는지 대략적으로 이해되는게 신기하다

  • 근데 이런 식의 패턴의 단점은

  • 잡다한 클래스가 너무많아짐.. 그래두 이제 알고하니

  • 클래스가 어떻게 구성되어있는지 파악은 좀 더 쉬워질듯

  • 한번 살펴본결과 여러가지를 조합해서 유연하게 조합할수 있지만

  • 단점 그대로 클래스 관계가 너무복잡하다.

  • 이게 어떻게 유용한 패턴일까?

  • 책에선 상속을 사용했지만 인터페이스로 구현하는게 더 좋은 거같다.

개인적인 고민시간


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);
    }
}
  • 캐싱? 이거 괜찬은거같은데..
profile
어제의 나보다 한걸음 더

0개의 댓글