2018년 6월 20일 수요일

[디자인 패턴] Adapter, Decorator, Facade, Proxy

특정 클래스를 Wrapping해서 사용하는 디자인 패턴으로 Adapter, Decorator, Facade, Proxy 등이 있다. 각각의 역할과 쓰임에 대해 알아보기로 하자.

Adapter Pattern

Incompatible → Compatible. Convert!!


Adapter 패턴은 클라이언트가 원하는 인터페이스(Target)와 호환되지 않는 클래스(Adaptee)를 소스 코드 변경 없이 클라이언트가 원하는 인터페이스로 동작할 수 있게 해준다. Adapter 클래스가 서로 호환되지 않는 인터페이스를 변환하여 동작할 수 있게 하는 것이다. 이를 이용하면 클라이언트는 Adaptee를 몰라도 되고, Adaptee의 수정 없이 원하는 목적을 달성할 수 있는 이점이 생긴다.

public class AdapteeToClientAdapter implements Adapter {
    private final Adaptee instance;

    public AdapteeToClientAdapter(final Adaptee instance) {
         this.instance = instance;
    }

    @Override
    public void clientMethod() {
       // call Adaptee's method(s) to implement Client's clientMethod
    }
}



Decorator Pattern

대상 Object의 기능을 확장


원래의 코드를 Wrapping하여 동적으로 책임을 인터페이스에 추가하기 위해 사용한다. 전형적인 예로 자바의 I/O가 있다.
BufferedReader br = new BufferedReader(new FileReader(new File("test.txt")));

public interface Coffee {
    public double getCost(); // Returns the cost of the coffee
    public String getIngredients(); // Returns the ingredients of the coffee
}

public abstract class CoffeeDecorator implements Coffee {
    protected final Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee c) {
        this.decoratedCoffee = c;
    }
...
    public String getIngredients() {
        return decoratedCoffee.getIngredients();
    }
}

class WithMilk extends CoffeeDecorator {
    public WithMilk(Coffee c) {
        super(c);
    }
...
    public String getIngredients() {
        return super.getIngredients() + ", Milk";
    }
}

class WithSprinkles extends CoffeeDecorator {
    public WithSprinkles(Coffee c) {
        super(c);
    }
...
    public String getIngredients() {
        return super.getIngredients() + ", Sprinkles";
    }
}



Facade Pattern

복잡한 것을 간단하게, 개별 컴포넌트로의 접근을 하나로.


클래스 라이브러리 같은 어떤 소프트웨어의 다른 커다란 코드 부분에 대한 간략화된 인터페이스를 제공하는 객체이다. (위키피디아)
Wrapper가 특정 인터페이스를 준수해야 하며, 폴리모픽 기능을 지원해야 할 경우에는 Adapter 패턴을 쓰고 단지 쓰기쉬운 인터페이스를 이용하고 싶을 경우에는 Facade를 쓴다.

class CPU {
    public void freeze() { ... }
    public void jump(long position) { ... }
    public void execute() { ... }
}

class HardDrive {
    public byte[] read(long lba, int size) { ... }
}

class Memory {
    public void load(long position, byte[] data) { ... }
}

class ComputerFacade {
    private CPU processor;
    private Memory ram;
    private HardDrive hd;

    public ComputerFacade() {
        this.processor = new CPU();
        this.ram = new Memory();
        this.hd = new HardDrive();
    }

    public void start() {
        processor.freeze();
        ram.load(BOOT_ADDRESS, hd.read(BOOT_SECTOR, SECTOR_SIZE));
        processor.jump(BOOT_ADDRESS);
        processor.execute();
    }
}



Proxy Pattern

Subject에 대한 은닉과 제어


특정 객체에 대한 접근을 제어하고, 객체에 대한 접근에 대해 추가 기능을 제공할 필요가 있을 때 사용한다. 보안, 성능, 네트워킹 등의 이유로 Subject (실제 object)를 숨겨야 하는 경우 쓰인다.
구현 예로 원격 object와 통신하는 Remote proxy, 비용이 드는 object에 접근을 통제하는 Virtual proxy,  Role 기반 object의 접근을 제공하는 Protection proxy, 캐시 object를 반환하는 Caching proxy 등이 있다.

interface ICar
{
    void DriveCar();
}

// Real Object
public class Car : ICar
{
    public void DriveCar()
    {
        Console.WriteLine("Car has been driven!");
    }
}

// Proxy Object
public class ProxyCar : ICar
{
    private Driver driver;
    private ICar realCar;

    public ProxyCar(Driver driver)
    {
        this.driver = driver;
        this.realCar = new Car();
    }

    public void DriveCar()
    {
        if (driver.Age < 16)
            Console.WriteLine("Sorry, the driver is too young to drive.");
        else
            this.realCar.DriveCar();
     }
}

public class Driver
{
    public int Age { get; set; }

    public Driver(int age)
    {
        this.Age = age;
    }
}

댓글 없음:

댓글 쓰기