자바에서의 옵저버와 데코레이터 패턴: 예제와 구현 전략

자바에서의 옵저버 패턴

옵저버 패턴은 객체가 변화가 일어날 때 그것을 관찰하고, 이 객체들이 일어나는 변화에 대해 다른 객체들에게 알리는 디자인 패턴입니다. 자바에서는 옵저버 패턴을 구현하기 위해 java.util 패키지에서 제공하는 Observable 클래스와 Observer 인터페이스를 활용합니다.

Observable 클래스는 변화가 일어나는 대상, 즉 Subject라고 불리며, 이 클래스를 상속받은 클래스는 addObserver, deleteObserver, notifyObservers 메서드를 통해 옵저버 객체들을 추가, 삭제, 알림을 보내는 역할을 합니다.

Observer 인터페이스는 변화가 일어났을 때 알림을 받는 객체, 즉 Observer라고 불리며, 이 인터페이스를 구현한 클래스는 update 메서드를 통해 변화에 대해 처리하는 역할을 합니다.

옵저버 패턴은 크게 push 방식과 pull 방식으로 나뉘며, push 방식은 Subject가 Observer에게 변경된 데이터를 직접 전달하는 방식이고, pull 방식은 Observer가 Subject에게 데이터를 요청하는 방식입니다.

옵저버 패턴은 MVC(Model-View-Controller) 패턴에서 Model과 View 간의 의존성을 낮추기 위해 활용됩니다. Model이 변화가 일어나면 Controller를 거쳐 View에게 전달되는데, 이때 Observer 패턴을 활용하면 Model과 View 간의 직접적인 의존성을 없앨 수 있습니다.

import java.util.*;

public class Subject extends Observable{
    private int data;

    public int getData(){
        return data;
    }

    public void setData(int data){
        this.data = data;
        setChanged();
        notifyObservers();
    }
}

public class Observer implements java.util.Observer{
    public void update(Observable o, Object arg) {
        System.out.println("Data is changed to " + ((Subject)o).getData());
    }
}

public class ObserverPatternDemo {
    public static void main(String[] args) {
        Subject subject = new Subject();
        Observer observer = new Observer();

        subject.addObserver(observer);

        subject.setData(10);
    }
}

자바에서의 데코레이터 패턴

데코레이터 패턴은 객체에 동적으로 새로운 기능을 추가할 수 있도록 하는 디자인 패턴입니다. 데코레이터 패턴을 사용하면 객체를 확장하기 위해 서브클래스를 만드는 대신, 런타임에 객체에 새로운 기능을 추가할 수 있습니다.

자바에서는 데코레이터 패턴을 구현하기 위해 java.io 패키지에서 제공하는 InputStream, OutputStream, Reader, Writer 등의 추상 클래스를 활용합니다. 이 추상 클래스들은 데코레이터 패턴을 기반으로 하고 있으며, 다양한 데코레이터 클래스를 제공하여 객체에 새로운 기능을 추가할 수 있도록 합니다.

데코레이터 패턴은 객체의 구조와 기능을 분리시키기 위해 사용됩니다. 객체의 구조는 데코레이터 클래스들이 감싸고 있는데, 이들은 모두 동일한 추상 클래스나 인터페이스를 구현하고 있기 때문에 구조를 일관성 있게 유지할 수 있습니다. 기능은 데코레이터 클래스들이 추가하는데, 이러한 방식으로 기능을 추가하면 객체의 기존 코드를 변경하지 않고도 새로운 기능을 추가할 수 있습니다.

public abstract class Beverage {
    String description = "Unknown Beverage";

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}

public class Espresso extends Beverage{
    public Espresso() {
        description = "Espresso";
    }

    public double cost() {
        return 1.99;
    }
}

public class Mocha extends CondimentDecorator {
    Beverage beverage;

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

    public String getDescription() {
        return beverage.getDescription() + ", Mocha";
    }

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

public class DecoratorPatternDemo {
    public static void main(String[] args) {
        Beverage beverage = new Espresso();
        System.out.println(beverage.getDescription() + " $" + beverage.cost());

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

예제와 구현 전략

옵저버 패턴과 데코레이터 패턴 모두 유용한 디자인 패턴이며, 다양한 예제를 통해 적용 방법을 익힐 수 있습니다.

옵저버 패턴을 활용한 예제로는 주식 시장에서 주식 가격이 변동될 때 이를 감시하고, 변동된 가격을 분석하는 프로그램이 있습니다. 이 프로그램에서는 Subject 클래스에 주식 가격을 저장하고, Observer 클래스에서는 주식 가격이 변동될 때 그 정보를 받아 분석하는 역할을 합니다.

데코레이터 패턴을 활용한 예제로는 커피 주문 시스템이 있습니다. 이 시스템에서는 Beverage 추상 클래스에 커피의 종류와 가격을 저장하고, CondimentDecorator 추상 클래스에서는 커피에 추가할 수 있는 토핑들을 정의합니다. 데코레이터 클래스들은 모두 CondimentDecorator를 상속받으며, 이들은 커피의 종류와 가격을 유지한 채로 새로운 토핑을 추가합니다.

옵저버 패턴과 데코레이터 패턴은 융통성과 확장성을 높여주는 디자인 패턴입니다. 이들을 활용하면 객체 지향 프로그래밍의 핵심인 추상화, 캡슐화, 다형성을 더욱 효율적으로 구현할 수 있습니다.

옵저버 패턴과 데코레이터 패턴