ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Decorator Pattern(데코레이터 패턴)
    STUDY/디자인패턴 2024. 12. 25. 15:57

    데코레이터 패턴이란?

    객체들을 새로운 행동들을 포함한 특수 래퍼 객체들 내에 넣어서 위 행동들을 해당 객체들에 연결시키는 구조 패턴으로, 상속을 통해 클래스를 확장하는 대신, 객체를 감싸는 방식을 사용하여 기능을 추가하거나 변경한다.

     

    구조

    • Component

    래퍼들과 래핑된 객체들 모두에 대한 공통 인터페이스를 선언

    • Concrete Component

    래핑되는 객체들의 클래스이며, 기본 행동들을 정의하고 해당 기본 행동들은 데코레이터들이 변경할 수 있다.

    • Base Decorator

    래핑된 객체를 참조하기 위한 필드가 있고, 필드의 유형은 구상 컴포넌트들과 구상 데코레이터들을 모두 포함할 수 있도록 컴포넌트 인터페이스로 선언되어야 한다. 그 후 기초 데코레이터는 모든 작업들을 래핑된 객체에 위임한다.

    • Concrete Decorators

    컴포넌트들에 동적으로 추가될 수 있는 추가 행동들을 정의한다. 기초 데코레이터의 메서드를 오버라이드(재정의)하고 해당 행동을 부모 메서드를 호출하기 전이나 후에 실행한다.

    • Client

    데코레이터들이 컴포넌트 인터페이스를 통해 모든 객체와 작동하는 한 컴포넌트들을 여러 계층의 데코레이터들로 래핑할 수 있다.

     

    예시

    Component

    public interface DataSource {
        void writeData(String data);
        String readData();
    }

     

    Concrete Component

    public class FileDataSource implements DataSource {
        private String name;
    
        public FileDataSource(String name) {
            this.name = name;
        }
    
        @Override
        public void writeData(String data) {
            File file = new File(name);
            try (OutputStream fos = new FileOutputStream(file)) {
                fos.write(data.getBytes(), 0, data.length());
            } catch (IOException ex) {
                System.out.println(ex.getMessage());
            }
        }
    
        @Override
        public String readData() {
            char[] buffer = null;
            File file = new File(name);
            try (FileReader reader = new FileReader(file)) {
                buffer = new char[(int) file.length()];
                reader.read(buffer);
            } catch (IOException ex) {
                System.out.println(ex.getMessage());
            }
            return new String(buffer);
        }
    }

     

    Base Decorator

    public abstract class DataSourceDecorator implements DataSource {
        private DataSource wrappee;
    
        DataSourceDecorator(DataSource source) {
            this.wrappee = source;
        }
    
        @Override
        public void writeData(String data) {
            wrappee.writeData(data);
        }
    
        @Override
        public String readData() {
            return wrappee.readData();
        }
    }

     

    Concrete Decorators

    public class EncryptionDecorator extends DataSourceDecorator {
        public EncryptionDecorator(DataSource source) {
            super(source);
        }
    
        @Override
        public void writeData(String data) {
            super.writeData(encode(data));
        }
    
        @Override
        public String readData() {
            return decode(super.readData());
        }
    
        private String encode(String data) {
            byte[] result = data.getBytes();
            for (int i = 0; i < result.length; i++) {
                result[i] += (byte) 1;
            }
            return Base64.getEncoder().encodeToString(result);
        }
    
        private String decode(String data) {
            byte[] result = Base64.getDecoder().decode(data);
            for (int i = 0; i < result.length; i++) {
                result[i] -= (byte) 1;
            }
            return new String(result);
        }
    }
    
    
    public class CompressionDecorator extends DataSourceDecorator {
        private int compLevel = 6;
    
        public CompressionDecorator(DataSource source) {
            super(source);
        }
    
        public int getCompressionLevel() {
            return compLevel;
        }
    
        public void setCompressionLevel(int value) {
            compLevel = value;
        }
    
        @Override
        public void writeData(String data) {
            super.writeData(compress(data));
        }
    
        @Override
        public String readData() {
            return decompress(super.readData());
        }
    
        private String compress(String stringData) {
            byte[] data = stringData.getBytes();
            try {
                ByteArrayOutputStream bout = new ByteArrayOutputStream(512);
                DeflaterOutputStream dos = new DeflaterOutputStream(bout, new Deflater(compLevel));
                dos.write(data);
                dos.close();
                bout.close();
                return Base64.getEncoder().encodeToString(bout.toByteArray());
            } catch (IOException ex) {
                return null;
            }
        }
    
        private String decompress(String stringData) {
            byte[] data = Base64.getDecoder().decode(stringData);
            try {
                InputStream in = new ByteArrayInputStream(data);
                InflaterInputStream iin = new InflaterInputStream(in);
                ByteArrayOutputStream bout = new ByteArrayOutputStream(512);
                int b;
                while ((b = iin.read()) != -1) {
                    bout.write(b);
                }
                in.close();
                iin.close();
                bout.close();
                return new String(bout.toByteArray());
            } catch (IOException ex) {
                return null;
            }
        }
    }

     

    Client

    public class Client {
        public static void main(String[] args) {
            String salaryRecords = "Name,Salary\nJohn Smith,100000\nSteven Jobs,912000";
    
            DataSource dataSource = new FileDataSource("out/Output.txt");
    
            DataSourceDecorator encoded = new EncryptionDecorator(dataSource);
            encoded.writeData(salaryRecords);
    
            System.out.println(dataSource.readData());
            System.out.println(encoded.readData());
    
            DataSourceDecorator compression = new CompressionDecorator(dataSource);
            compression.writeData(salaryRecords);
    
            System.out.println(dataSource.readData());
            System.out.println(compression.readData());
        }
    }
    
    
    # 결과
    T2JuZi1UYm1ic3oLS3BpbyFUbmp1aS0yMTExMTELVHVmd2ZvIUtwY3QtOjIzMTEx
    Name,Salary
    John Smith,100000
    Steven Jobs,912000
    eJzzS8xN1QlOzEksquTyys/IUwjOzSzJ0DE0AAGu4JLUstQ8Ba/8pGIdS0MjoBAAfpEOqg==
    Name,Salary
    John Smith,100000
    Steven Jobs,912000

     

    패턴적용

    1. 객체를 사용하는 코드를 손상시키지 않고 런타임에 객체에 추가 동작을 할당할 수 있어야 하는 경우
    2. 상속을 사용하여 객체의 행동을 확장하는 것이 어색하거나 불가능한 경우

     

    장단점

    장점

    • 새 자식 클래스를 만들지 않고도 객체의 행동을 확장할 수 있다.
    • 런타임에 객체들에서부터 책임들을 추가하거나 제거할 수 있다.
    • 객체를 여러 데코레이터로 래핑하여 여러 행동들을 합성할 수 있다.
    • 단일 책임 원칙 : 다양한 행동들의 여러 변형들을 구현하는 모놀리식 클래스를 여러 개의 작은 클래스들로 나눌 수 있다.

    단점

    • 래퍼들의 스택에서 특정 래퍼를 제거하기 어렵다.
    • 어느 테코레이터를 먼저 설정하느냐에 따라 스택 순서가 결정되기 때문에, 데코레이터 스택 내의 순서에 의존하지 않는 방식으로 데코레이터를 구현하기 어렵다.
    • 계층들의 초기 설정 코드가 보기 안좋을 수 있다.(ex. new A(new B(new C(new D()))))

     

Designed by Tistory.