CS/디자인 패턴

최종 정리

Ynghan 2023. 12. 15. 10:58

1. Singleton 패턴

기본 구조
public class Singleton {
    private static Singleton uniqueInstance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if(uniqueInstance == null) {
                uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
    ...
}

해당 코드는 멀티 스레드 환경에서 문제가 발생할 수 있다.

해결 1.
public class Singleton {
    private static Singleton uniqueInstance;
    
    private Singleton() {}
    
    public static synchronized Singleton getInstance() {
        if(uniqueInstance == null) {
                uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

getInstance() 메소드에 synchronized 키워드를 사용한다.

해결 2.
public class Singleton {
    private static Singleton uniqueInstance = new Singleton();
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        return uniqueInstance;
    }
}

정적으로 Singleton 객체를 생성한다. → 리소스 낭비 문제 발생!

해결 3?
public class Singleton {
    private static Singleton uniqueInstance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if(uniqueInstance == null) {
            synchronized (Singleton.class) {
                if(uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

1. 변수가 초기화 되었는지 확인한다.

2. lock을 획득한다.

3. 변수가 이미 초기화 되었는지 다시 확인한다. 다른 스레드가 먼저 잠금을 획득한 경우, 이미 초기화를 수행했을 수 있다. 그렇다면 초기화된 변수를 반환하자.

4. 초기화되지 않았다면 변수를 초기화하고 반환하자.

- 언뜻보면 이전 알고리즘이 문제에 대한 효율적인 해결책으로 보인다. 그러나 이 기술에는 미묘한 문제가 많이 있기때문에 피해야 한다.
- 어떤 경우에는 스레드 A가 값이 초기화되지  않았음을 알아채고 lock을 획득하고 값 초기화를 시작한다.
- 일부 프로그래밍 언어의 의미로 인해 컴파일러에서 생성된 코드는 A가 초기화 수행을 완료하기 전에 부분적으로 생성된 객체를 가리키도록 공유 변수를 업데이트할 수 있다.
- 스레드 B는 공유 변수가 촉기화되었음을 확인하고 해당 값을 반환한다. 스레드 B는 값이 이미 초기화되었다고 생각하기 때문에 잠금을 획득하지 않는다. 스레드 A가 초기화를 완료하기 전에 변수를 사용하면 충돌할 가능성이 높다.
- J2SE 1.4 및 이전 버전에서 이중 확인 잠금을 사용할 때의 위험 중 하나는 작동하는 것처럼 보일 수 있다는 것이다.
- 컴파일러, 스케줄러에 의한 스레드 인터리브 및 기타 동시 시스템 활동의 특성에 따라 이중 확인 잠금의 잘못된 구현으로 인한 실패는 간헐적으로만 발생할 수 있다.
- 오류를 재현하는 것은 어려울 수 있다.
해결 3. 옳은 이중 확인 잠금
public class Singleton {
    private valotile static Singleton uniqueInstance = null;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if(uniqueInstance == null) {
            synchronized (Singleton.class) {
                uniqueInstance = new Singleton();
            }
        }
        return uniqueInstance;
    }
}

Singleton 인스턴스에 volatile 키워드를 사용한다.

- 성능을 고려한 완벽한 솔루션이다. 그러나 성능 문제가 없는 경우, 이중 확인 잠금은 과잉일 수 있다. 또한 최소한 Java5를 실행하고 있는지 확인해야 한다.


2. Command 패턴

public class TestDrive {
    public static void main(String[] args) {
        Lamp lamp = new Lamp();
        Command lampOnCommand = new LampOnCommand(lamp);
        
        Button button1 = new Button(lampOnCommand);
        button1.pressed();
        
        Alarm alarm = new Alarm();
        Command alramOnCommand = new AlramOnCommand(alarm);
        
        Button button2 = new Button(alarmOnCommand);
        button2.pressed();
        
        button2.setCommand(lampOnCommand);
        button2.pressed();
    }
}

3. Adaptor 패턴

public class EnumerationIterator implements Iterator {
    Enumeration enum;
    public EnumerationIterator(Enumeration enum) {
        this.enum = enum;
    }
    public boolean hasNext() {
        return enum.hasMoreElements();
    }
    public Object next() {
        return enum.nextElement();
    }
    public void remove() {
        throw new UnsupportedOperationException();
    }
}

4. Facade 패턴

public class HomeTheaterFacade {
    Amplifier amp;
    Tuner tuner;
    DvdPlayer dvd;
    CdPlayer cd;
    Projector projector;
    TheaterLights lights;
    Screen screen;
    PopcornPopper popper;

    public HomeTheaterFacade(Amplifier amp, Tunner tuner,
                             DvdPlayer dvd, CdPlayer cd, Projectoer projector,
                             Screen screen, TheaterLights lights,
                             PopcornPopper popper) {
        this.amp = amp;
        this.tuner = tuner;
        ...
    }
    public void watchMovie(String movie) {
        System.out.println(“Get ready to watch a movie...”);
        poper.on();
        popper.pop();
        lights.dim(10);
        screen.down();
        projector.on();
        projector.setInput(dvd);
        projector.wideScreenMode();
        amp.on();
        amp.setDvd(dvd);
        amp.setSurroundSound();
        amp.setVolume(5);
        dvd.on();
        dvd.play(movie);
    }
}


5. Template 패턴

public abstract class CaffeinBeverage {
    final void prepareRecipe() {
        boilwater();
        brew();
        pourInCup();
        addCondiments();
    }

    abstract void brew();
    abstract void addCondiments();

    public void boilwater() {
        System.out.println("Boiling water");
    }

    public void pourInCup() {
        System.out.println("Pouring into cup");
    }
}

prepareRecipe()이 Template Method !!

CaffeubeBeverage  클래스는 추상 클래스이다.

public class Tea extends CaffeineBeverage {
    public void brew() {
        System.out.println("Steeping the tea");
    }
    public void addCondiments() {
        System.out.println("Adding Lemon");
    }
}
Hook Method
public class CoffeeWithHook extends CaffeineBeverage {
    public void brew() {
        System.out.println("Dripping Coffee through filter");
    }
    public void addCondiments() {
        System.out.println("Adding Sugar and Milk");
    }
    public boolean customerWantsCondiments() throws IOException {
        String answer = getUserInput();
        if (answer.toLowerCase().startsWith("y")) return true;
        else return false;
    }
    private String getUserInput() throws IOException {
        String answer = null;
        System.out.println("Would you like milk and sugar with your coffee (y/n)? ");
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        try {
            answer = in.readLine();
        } catch(IOException ioe) {
            System.out.println("IO error trying to read your answer");
        }
        if(answer == null) { return "n"; }
        return answer;
    }
}

6. Iterator 패턴

 

 

Aggregate 개체의 기본 표현을 노출하지 않고 Aggregate 개체의 요소에 순차적으로 액세스하는 방법을 제공합니다.


7. Composite 패턴

public class MenuTestDrive {
    public static void main(String[] args) {
    
        //중간 레벨 메뉴
        MenuComponent pancakeHouseMenu = new Menu("PANCAKE HOUSE MENU", "Breakfast");
        MenuComponent dinerMenu = new Menu("DINER MENU", "Lunch");
        MenuComponent cafeMenu = new Menu("CAFE MENU", "Dinner");

        //루트 레벨 메뉴에 중간 레벨 메뉴를 더함
        MenuComponent allMenus = new Menu("ALL MENUS", "All menus combined");
        allMenus.add(pancakeHouseMenu);
        allMenus.add(dinerMenu);
        allMenus.add(cafeMenu);

        // 중간 레벨 메뉴에 하위 레벨 메뉴를 더함
        MenuComponent dessertMenu = new Menu("DESSERT MENU", "Dessert of course!");
        dinerMenu.add(dessertMenu);

        //Menu에 MenuItem을 더함
        pancakeHouseMenu.add(new MenuItem("K&B's Pancake Breakfast", "Pancakes with scrambled eggs, and toast", true, 2.99));
        pancakeHouseMenu.add(new MenuItem("Regular Pancake Breakfast", "Pancakes with fried eggs, sausage", false, 2.99));
        pancakeHouseMenu.add(new MenuItem("Blueberry Pancakes", "Pancakes made with fresh blueberries, and blueberry syrup", true, 3.49));
        pancakeHouseMenu.add(new MenuItem("Waffles", "Waffles, with your choice of blueberries or strawberries", true, 3.59));

        dinerMenu.add(new MenuItem("Vegetarian BLT", "(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99));
        dinerMenu.add(new MenuItem("BLT", "Bacon with lettuce & tomato on whole wheat", false, 2.99));
        dinerMenu.add(new MenuItem("Soup of the day", "A bowl of the soup of the day, with a side of potato salad", false, 3.29));
        dinerMenu.add(new MenuItem("Hotdog", "A hot dog, with saurkraut, relish, onions, topped with cheese", false, 3.05));
        dinerMenu.add(new MenuItem("Steamed Veggies and Brown Rice", "A medly of steamed vegetables over brown rice", true, 3.99));
        dinerMenu.add(new MenuItem("Pasta", "Spaghetti with Marinara Sauce, and a slice of sourdough bread", true, 3.89));

        dessertMenu.add(new MenuItem("Apple Pie", "Apple pie with a flakey crust, topped with vanilla icecream", true, 1.59));
        dessertMenu.add(new MenuItem("Cheesecake", "Creamy New York cheesecake, with a chocolate graham crust", true, 1.99));
        dessertMenu.add(new MenuItem("Sorbet", "A scoop of raspberry and a scoop of lime", true, 1.89));

        cafeMenu.add(new MenuItem("Veggie Burger and Air Fries", "Veggie burger on a whole wheat bun, lettuce, tomato, and fries", true, 3.99));
        cafeMenu.add(new MenuItem("Soup of the day", "A cup of the soup of the day, with a side salad", false, 3.69));
        cafeMenu.add(new MenuItem("Burrito", "A large burrito, with whole pinto beans, salsa, guacamole", true, 4.29));

        Waitress waitress = new Waitress(allMenus);
        waitress.printVegetarianMenu();

    }
}

 

For Uniformity / For Type Safty

Composite 패턴은 2가지 형태의 방식으로 나눌 수 있다.

Composite 패턴에서 Composite 클래스는 자식들을 관리하기 위해 추가적인 메소드가 필요하다.

이러한 메소드들이 어떻게 작성되느냐에 따라, Composite 패턴은 다른 목적을 추구할 수 있다.

  • For Uniformity
    • 일관성을 추구하는 방식
    • 자식을 다루는 메소드들을 Component에서 정의한다.
  • Type Safty
    • Client는 Leaf와 Composite를 다르게 취급하고 있다.
    • Client에서 Leaf 객체가 자식을 다루는 메소드를 호출할 수 없기 때문에 타입에 대한 안정성을 얻게 된다.

8. State 패턴


9. Proxy 패턴

  • remote proxy : 서로 다른 주소 공간에 존재하는 객체를 가리키는 로컬 환경에 존재하는 proxy
  • virtual proxy : 요청이 있을 때, 필요한 고비용 객체를 생성하는 proxy
  • protection proxy : 대상 객체의 접근을 제어하기 위한 proxy
Proxy Pattern

Virtual Proxy
Protection Proxy

 

Proxy 패턴과 Decorator 패턴은 프록시 객체를 활용하기 때문에 구현 방법이 비슷하나 사용 목적이 다르다.

  • Proxy 패턴은 객체에 대한 접근을 제어하는 목적으로 사용된다.
  • Decorator 패턴은 동적으로 책임을 추가하기 위한 목적으로 사용된다.
  • Decorator 패턴과 Proxy 패턴은 대상 객체에 대한 참조자를 관리하므로 구조가 비슷하다. 하지만 목적은 다르다.
    • Proxy는 Decorator와 달리 동적으로 어떤 기능을 추가 및 제거하지 않는다.
    • Proxy 패턴의 목적은 대상 객체에 대한 접근을 제어하는 것이다.
    • Proxy 패턴은 참조하는 Target이 실제 기능을 제공하지만 Decorator 패턴은 Decorator 자체에서도 기능적인 일을 담당한다.
    • 그러므로 Decorator는 재귀적 합성이 중요하지만 Proxy에서는 단순이 메시지를 전달하므로 재귀적 합성은 전혀 의미가 없다.