본문 바로가기

카테고리 없음

Decorator 패턴

Decorator

attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.

참고자료
refactoring.guru
head first design patterns 서적

UML

  • Client 여러 데코레이터로 wrapping하여 추가 작업(concrete decorator에서 정의한) 실행 가능
  • Component <<interface>> wrappers and wrapped objects를 위한 인터페이스나 추상 클래스
    • Concrete Component 실제로 wrapped 되는 subclass로, 기본 기능에 해당
    • Base Decorator <<interface or abstract class>> wrapping 시킬 object의 상위 인터페이스 또는 추상 클래스 타입을 구현 또는 상속하여 형식을 맞춤
      • Concrete Decorator 실제로 추가 기능을 정의

 

  • Beverage Component
    • Espresso DarkRoast ... Concrete Component
    • CondimentDecorator Base Decorator
      • Mocha Whip ... Concrete Decorator
  • 코드
/* Client */
public class DecoratorApplication {
    public static void main(String[] args) {
        Beverage beverage = new Espresso();
        System.out.println(beverage.getDescription() + " $" + beverage.cost());

        Beverage beverage2 = new DarkRoast();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
    }
}


/* Component */
public abstract class Beverage {
    String description = "no content";

    public String getDescription() {
        return this.description;
    }

    public abstract double cost();
}

// Concrete Components
public class DarkRoast extends Beverage {

    public DarkRoast() {
        description = "다크로스트";
    }

    @Override
    public double cost() {
        return .99;
    }
}

public class Espresso extends Beverage {

    public Espresso() {
        description = "에스프레소";
    }

    @Override
    public double cost() {
        return 1.99;
    }
}


/* Base Decorator */
public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}

// Concrete Decorator
public class Mocha extends CondimentDecorator {

    Beverage beverage;

    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", mocha";
    }

    @Override
    public double cost() {
        return .20 + beverage.cost();
    }

}

public class Whip extends CondimentDecorator {

    Beverage beverage;

    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", whip";
    }

    @Override
    public double cost() {
        return .10 + beverage.cost();
    }
}

실제 활용

java.io <- FilterInputStream, FilterOutputStream, FilterReader, FilterWriter...

/* Client */
public class LowerCaseInputStreamApplication {
    public static void main(String[] args) throws FileNotFoundException {
        int c;
        try {
            InputStream in = new LowerCaseInputStream(
                                new BufferedInputStream(
                                new FileInputStream("test.txt")));

            while((c = in.read()) >= 0) {
                System.out.print((char)c);
            }

            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

/* Component */
public abstract class InputStream implements Closeable {
  
    private static final int MAX_SKIP_BUFFER_SIZE = 2048;
    private static final int DEFAULT_BUFFER_SIZE = 8192;
  
    public abstract int read() throws IOException;
  
    //...
}

// Concrete Component
class FileInputStream extends InputStream {
    public int read() throws IOException {
        return read0();
    }
  
    //...
}

/* Base Decorator */
class FilterInputStream extends InputStream {

    protected volatile InputStream in;

    protected FilterInputStream(InputStream in) {
        this.in = in;
    }

    public int read() throws IOException {
        return in.read();
    }
    
    //...
}

// Decorator
public class BufferedInputStream extends FilterInputStream {

    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }
  
    public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }
  
    //...
}

public class LowerCaseInputStream extends FilterInputStream {

    protected LowerCaseInputStream(InputStream in) {
        super(in);
    }

    public int read() throws IOException {
        int c = super.read();
        return (c == -1 ? c : Character.toLowerCase((char)c));
    }

    public int read(byte[] b, int offset, int len) throws IOException {
        int result = super.read(b, offset, len);
        for (int i = offset; i < offset+result; i++) {
            b[i] = (byte)Character.toLowerCase((char)b[i]);
        }
        return result;
    }
}
  • InputStream Component
    • FileInputStream ... Concrete Component
    • FilterInputStream Base Decorator
      • BufferedInputStream DataInputStream LowerCaseInputStream(customized)... Concrete Decorator

 

Pros and Cons

  • Pros
    • You can extend an objects' behavior without making a new subclass.
    • You can add or remove responsibilities from an object at runtime.
    • You can combine several behaviors by wrapping an object into multiple decorators.
    • Single Responsibility Principle. You can divide a monolithic class that implements many possible variants of behavior into several smaller classes.
  • Cons
    • It's hard to remove a specific wrapper from the wrappers stack.
    • It's hard to implement a decorator in such a way that its behavior doesn't depend on the order in the decorators stack.
    • The initial configuration code of layers might look pretty ugly.