디자인패턴 Iteratoer Pattern

이터레이터 패턴은 컬렉션 구현 방법을 노출시키지 않으면서도 그 집합체 안에 들어있는 모든 항목에 접근할 수 있게 해주는 방법을 제공해 줍니다.

 

Iteratoer Pattern 어떻게 구현할까요?


이터레이터 패턴은 Iterator라는 인터페이스에 의존합니다.
이터레이터 패턴은 이용하면 내부적인 구현 방법을 외부로 노출시키지 않으면서도 집합체에 있는 모든 항목에 일일이 접근할 수 있습니다.
또한 각 항목에 일일이 접근할 수 있게 해주는 기능을 집합체가 아닌 반복자 객체에서 책임지게 된다는 것도 장점으로 작용합니다.
그러면 집합체 인터페이스 및 구현이 간단해지고, 각자 중요한 일만 처리하면 됩니다.

hasNext와 next라는 함수만 노출시킵니다.
이 함수를 사용하는 사용자는 hasNext가 boolean으로 되어있으니 다음 데이터가 있는지 여부를 반환하는 함수라고 예측 가능하고, Next는 MenuItem이라는 객체를 반환하니 객체에 담겨있는 다음 데이터를 보여주는구나라고 예측할 수 있습니다.
하지만 내부적인 구현 방법은 모릅니다.
이렇게 구현 방법을 외부로 노출시키지 않고 기능을 제공할 수 있습니다.

 

구현

Iterator

package cg.park.designpattern.iterator;

public interface Iterator {
   boolean hasNext();
   MenuItem next();
}

 

WashMenuIterator

package cg.park.designpattern.iterator;

public class WashMenuIterator implements Iterator {
    MenuItem[] items;
    int position = 0;

    public WashMenuIterator(MenuItem[] items) {this.items = items;}

    public MenuItem next() {return items[position++];}
    public boolean hasNext() {return items.length > position;}

}

 

Menu

package cg.park.designpattern.iterator;

public interface Menu {
   public Iterator createIterator();
}

 

WashMenu

package cg.park.designpattern.iterator;

public class WashMenu implements Menu {
    static final int MAX_ITEMS = 6;
    int numberOfItems = 0;
    MenuItem[] menuItems;

    public WashMenu() {
        menuItems = new MenuItem[MAX_ITEMS];
        addItem("카 샴푸");
        addItem("세차용 스폰지");
        addItem("휠 브러시 사용");
        addItem("물통에 스폰지 세척");
        addItem("고압수 뿌리기");
        addItem("타월을 이용하여 물기 제거");
    }

    public void addItem(String description) {
        MenuItem washItem = new MenuItem(description);
        if (numberOfItems >= MAX_ITEMS) {
            System.out.println("최대 개수를 초과했습니다.");
            return;
        }
        menuItems[numberOfItems] = washItem;
        numberOfItems++;
    }

    public MenuItem[] getMenuItems() {
        return menuItems;
    }

    public Iterator createIterator() {
        return new WashMenuIterator(menuItems);
    }

}

 

MenuItem

package cg.park.designpattern.iterator;

public class MenuItem {
    String description;
    public MenuItem(String description) {
        this.description = description;
    }
    public String getDescription() {
        return description;
    }
    public String toString() { return description; }
}

 

Cleaner

package cg.park.designpattern.iterator;

public class Cleaner {
    Menu washMenu;

    public Cleaner(Menu washMenu) {
        this.washMenu = washMenu;
    }

    public void cleaning() {
        System.out.println("==========청소 시작==========");
        printCleaning(washMenu.createIterator());
        System.out.println("==========청소 끝==========");
    }

    private void printCleaning(Iterator iterator) {
        while (iterator.hasNext()) {
            MenuItem menuItem = iterator.next();
            System.out.println(menuItem.description);
        }
    }
}

 

IteratorTest

package cg.park.designpattern.iterator;

public class IteratorTest {
    public static void main(String[] args) {
        Cleaner cleaner = new Cleaner(new WashMenu());
        cleaner.cleaning();
    }
}

 

후기

내부적인 구현 방법을 외부로 노출시키지 않으면서도 집합체에 있는 모든 항목에 일일이 접근할 수 있는 방법은 유용한 기술입니다.

Git: https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

디자인패턴 TemplateMethod Pattern

템플릿 메서도 패턴은 메서드에서 알고리즘의 골격을 정의합니다.
알고리즘의 여러 단계 중 일부는 서브클래스에서 구현할 수 있습니다.
템플릿 메소드를 이용하면 알고리즘의 구조는 그대로 유지하면서 서브클래스에서 특정 단계를 재정의 할 수 있습니다.

 

TemplateMethod Pattern 실제로 어떻게 적용할까요?

버거 레시피를 만들 예정입니다.
버거에 들어가는 재료들을 확인하는 기능을 만들겠습니다.

각각의 레시피에 조금 다른 부분이 있긴 하지만, 만드는 방법은 똑같다고 볼 수 있기 때문에, 다른 부분을 추상화 시켜 공유하기로 했습니다.

 

다른 부분을 찾아서 추상화를 했습니다.
이 클래스는 버거 레시피의 알고리즘 틀입니다.

 

알고리즘 틀

템플릿 메소드 패턴은 간단하게 생각하면 알고리즘의 틀을 만들기 위한 패턴입니다.
틀이란 일련의 단계들로 알고리즘을 정의한 메소드입니다.
일련의 단계들 중 하나 이상이 추상 메소드로 정의되며, 그 추상 메소드는 서브클래스에서 구현됩니다.
이렇게 하면 서브클래스에서 달라지는 부분을 구현할 수 있도록 하면서도 알고리즘의 구조는 바꾸지 않아도 됩니다.

 

달라지는 부분은 어떻게 구현할까요?

템플릿 메소드에서는 알고리즘의 각 단계들을 정의하며, 그 중 한 개 이상의 단계가 서브클래스에 의해  제공될 수 있습니다.

버거 레시피 추상클래스가 있고, 그 레시피를 상속받은 치킨버거와 불고기버거 클래스가 있습니다. 
서브클래스를 구현하여 다른 부분에 대해 정의하면 하겠습니다.

 

구현

BurgerBeverage

package cg.park.designpattern.templateMethod;

public abstract class BurgerBeverage {

    final void makeBurger() {
        menu();
        bread();
        patty();
        onion();
        sauce();
        cheese();
    }

    abstract void menu();//메뉴

    abstract void patty();//패티(돼지,닭,소 등...)

    abstract void sauce();//버거 소스

    void bread() { System.out.println("모닝빵");}

    void onion() { System.out.println("양파");}

    void cheese() { System.out.println("치즈");}

}

 

ChickenBurger

package cg.park.designpattern.templateMethod;

public class ChickenBurger extends BurgerBeverage {
    public void menu() { System.out.println("*치킨 버거");}
    public void patty() { System.out.println("치킨 패티");}
    public void sauce() { System.out.println("양념치킨소스");}
}

 

BulgogiBurger

package cg.park.designpattern.templateMethod;

public class BulgogiBurger extends BurgerBeverage {
    public void menu() { System.out.println("*불고기 버거");}
    public void patty() { System.out.println("불고기 패티");}
    public void sauce() { System.out.println("불고기 소스");}
}

 

TemplateMethodTest

package cg.park.designpattern.templateMethod;

public class TemplateMethodTest {
    public static void main(String[] args) {
        BulgogiBurger bulgogiBurger = new BulgogiBurger();
        ChickenBurger chickenBurger = new ChickenBurger();

        bulgogiBurger.makeBurger();
        System.out.println("==========================");
        chickenBurger.makeBurger();

    }
}

 

후기

템플릿 메서드 패턴은 실무에서도 많이 사용되는 패턴입니다.
디자인 패턴에 대해 학습하고, 신규로 접하는 프로젝트가 디자인 패턴이 적용되어 있다면 코드 확장에 필요한 지식이 될 것입니다.

 

Git: https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

디자인패턴 Facade Pattern

퍼사드 클래스에서는 서브시스템 클래스들을 캡슐화하지 않습니다.
그냥 서브시스템의 기능을 사용할 수 있는 간단한 인터페이스를 제공할 뿐입니다.

 

Facade Pattern은 실제로 어떻게 적용할까요?

어떤 일을 하기 위해서는 작업 순서가 있습니다.
하지만 그 작업 순서를 하나씩 동작하기엔 너무 복잡하지 않을까요?
복잡한 작업 순서들을 하나로 묶어서 사용할 수 있게 된다면 편리하지 않을까요?
이런 경우에 퍼사드 패턴을 사용하면 됩니다.
퍼사드 클래스를 구현함으로써 복잡한 시스템을 훨씬 쉽게 사용할 수 있습니다.
퍼사드는 인터페이스를 단순화시킬 뿐 아니라 클라이언트와 구성요소들로 이루어진 서브시스템을 분리시키는 역할도 합니다.

 

세차하는 시스템을 퍼사드로 구현

세차를 할 때 작업 순서가 있습니다.
 -카 샴푸 사용
 -세차용 스펀지로 닦기
 -물통에 세차용 스펀지 세척하기
 -휠 브러시 사용하기
 -고압수 뿌리기
 -타월을 사용하여 물기 제거하기

 

 

이 작업 순서를 매번 하나씩 동작한다면 번거롭기도 하고, 복잡하기도 합니다.
더 간편한 방법이 있을지 찾아봅니다.
그래서 위의 작업 순서들을 하나로 묶기로 했습니다.

 

세차를 할 때 복잡하던 작업 순서들을 하나로 묶었습니다.
기존에는 각 클래스를 하나씩 동작해야 했다면, 이제는 CarWash라는 클래스로 기능들을 묶어서 한 번만 사용하면 됩니다.

 

구현

BubbleBomb

package cg.park.designpattern.facade;

public class BubbleBomb {
    String description;
    public BubbleBomb(String description) {
        this.description = description;
    }

    public void on() {System.out.println(description + " 샴푸 on");}

    public void off() {
        System.out.println(description + " 샴푸 off");
    }

}

 

WashMitt

package cg.park.designpattern.facade;

public class WashMitt {
    String description;

    public WashMitt(String description) {
        this.description = description;
    }

    public void on() {
        System.out.println(description + " 세차용 스폰지 on");
    }

    public void off() {
        System.out.println(description + " 세차용 스폰지 off");
    }

}

 

WheelBrush

package cg.park.designpattern.facade;

public class WheelBrush {
    String description;

    public WheelBrush(String description) {
        this.description = description;
    }

    public void on() {
        System.out.println(description + " 휠 브러시 on");
    }

    public void off() {
        System.out.println(description + " 휠 브러시 off");
    }

}

 

Bucket

package cg.park.designpattern.facade;

public class Bucket {
    String description;

    public Bucket(String description) {
        this.description = description;
    }

    public void on() {
        System.out.println(description + " 물통에 스폰지 세척 on");
    }

    public void off() {
        System.out.println(description + " 물통에 스폰지 세척 off");
    }
}


PressureWasher

package cg.park.designpattern.facade;

public class PressureWasher {
    String description;

    public PressureWasher(String description) {
        this.description = description;
    }

    public void on() {
        System.out.println(description + " 고압수 뿌리기 on");
    }

    public void off() {
        System.out.println(description + " 고압수 뿌리기 off");
    }

}

 

Towel

package cg.park.designpattern.facade;

public class Towel {
    String description;

    public Towel(String description) {
        this.description = description;
    }

    public void on() {
        System.out.println(description + " 타월로 물기 제거 on");
    }

    public void off() {
        System.out.println(description + " 타월로 물기 제거 off");
    }

}

 

CarWash

package cg.park.designpattern.facade;

public class CarWash {
    BubbleBomb bubbleBomb;//카 샴푸
    Bucket bucket;//물통
    PressureWasher pressureWasher;//고압 세척기
    Towel towel;//타월
    WashMitt washMitt;//세차용 스폰지
    WheelBrush wheelBrush;//휠 브러시

    public CarWash(
           BubbleBomb bubbleBomb
         , Bucket bucket
         , PressureWasher pressureWasher
         , Towel towel
         , WashMitt washMitt
         , WheelBrush wheelBrush) {

        this.bucket = bucket;
        this.washMitt = washMitt;
        this.wheelBrush = wheelBrush;
        this.bubbleBomb = bubbleBomb;
        this.towel = towel;
        this.pressureWasher = pressureWasher;
    }

    public void start() {
        bubbleBomb.on();//카 샴푸
        washMitt.on();//세차용 스폰지
        wheelBrush.on();//휠 브러시
        bucket.on();//물통
        pressureWasher.on();//고압수 뿌리기
        towel.on();//타월을 이용하여 물기 제거
    }

    public void end() {
        bubbleBomb.off();
        wheelBrush.off();
        washMitt.off();
        bucket.off();
        pressureWasher.off();
        towel.off();
    }

}

 

FacadeTest

package cg.park.designpattern.facade;

public class FacadeTest {

    public static void main(String[] args) {
        BubbleBomb bubbleBomb = new BubbleBomb("A사 제품");
        Bucket bucket = new Bucket("A사 제품");
        PressureWasher pressureWasher = new PressureWasher("A사 제품");
        Towel towel = new Towel("A사 제품");
        WashMitt washMitt = new WashMitt("A사 제품");
        WheelBrush wheelBrush = new WheelBrush("A사 제품");

        CarWash carWash = new CarWash(
                bubbleBomb
                , bucket
                , pressureWasher
                , towel
                , washMitt
                , wheelBrush);

        carWash.start();//세차 시작
        System.out.println("=================================");
        carWash.end();//세차 종료

    }

}

 

후기

처음에는 퍼사드패턴과 커맨드 패턴이 헷갈렸습니다.
퍼사드 패턴과 커맨드 패턴 둘 다 공부하니 개념적인 차이를 이해했습니다.
퍼사드 패턴: 서브클래스들을 캡슐화하지 않고 구현,  훨씬 쉽게 사용할 수 있는 인터페이스를 제공함으로써 복잡한 시스템을 쉽게 사용.
커맨드 패턴: 요구사항을 객체로 캡슐화, 매개변수로 다른 여러 가지 요구사항 추가 가능.

커맨드 패턴 보러가기

 

Git: https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

디자인패턴 Adapter Pattern

클래스의 인터페이스를 클라이언트에서 요구하는 다른 인터페이스로 변환합니다.
인터페이스가 호환되지 않아 쓸 수 없었던 클래스들을 같이 사용할 수 있게 해줍니다.

 

어댑터란?

어댑터는 그리 어렵지 않게 이해할 수 있습니다.
우리 주변에서 어댑터를 쉽게 볼 수 있습니다.
예를 들어, 한국에서 쓰던 220볼트 어댑터를 해외에 가서 사용하려면 어떻게 해야할까요?
같은 220볼트를 사용하는 국가라면 괜찮지만, 그렇지 않다면?

다들 어댑터가 어떻게 사용되고 있는지 알고 있습니다.
다른 부분을 변환하여 연결시키는 중간역할입니다.

 

 

어댑터 패턴은 실제로 어떻게 적용할까요?

기존 시스템과 신규 시스템이 호환되지 않는다면?

 

어댑터의 역할을 여기서 확인할 수 있습니다.
그림만 봐도 느낌이 오지 않나요?

 

어댑터가 있다면 다른 부분을 변환하여 연결시키는 중간역할을 할 수 있습니다.

 

구현

한국에서 사용하는 220볼트 플러그와 일본에서 사용하는 110볼트 플러그가 있습니다.
220볼트를 어댑터를 사용하여 110볼트를 사용할 수 있게 만들고,
110볼트도 어댑터를 사용하여 220볼트를 사용할 수 있게 만들 예정입니다.

 

Volt220

package cg.park.designpattern.adapter;

public interface Volt220 {
    public void plugIn220();
}

 

usedInKorea

package cg.park.designpattern.adapter;

public class usedInKorea implements Volt220 {
    public void plugIn220() { System.out.println("220볼트 사용");}
}

 

Volt110

package cg.park.designpattern.adapter;

public interface Volt110 {
    public void plugIn110();
}

 

usedInJapan

package cg.park.designpattern.adapter;

public class usedInJapan implements Volt110 {
    public void plugIn110() { System.out.println("110볼트 사용");}
}

 

VoltAdapter220

package cg.park.designpattern.adapter;

public class VoltAdapter220 implements Volt110 {
    Volt220 volt220;

    public VoltAdapter220(Volt220 volt220) {
        this.volt220 = volt220;
    }

    public void plugIn110() {
        volt220.plugIn220();
    }
}

 

VoltAdapter110

package cg.park.designpattern.adapter;

public class VoltAdapter110  implements Volt220 {
    Volt110 volt110;

    public VoltAdapter110(Volt110 volt110) {
        this.volt110 = volt110;
    }

    public void plugIn220() {
        volt110.plugIn110();
    }
}

 

AdapterTest

package cg.park.designpattern.adapter;

public class AdapterTest {
    public static void main(String[] args) {
        Volt110 volt110 = new VoltAdapter220(new usedInKorea());
        Volt220 volt220 = new VoltAdapter110(new usedInJapan());

        volt110.plugIn110();//volt110 사용
        volt220.plugIn220();//volt220 사용

    }

}

 

110볼트를 220볼트 어댑터로 감싸고, 

220볼트를 110볼트 어댑터로 감싼 후

실행하게 되면 위와 같이 결과가 나옵니다.

220어댑터(110볼트): 220볼트 사용

110어댑터(220볼트): 110볼트 사용

 

후기

어댑터의 개념은 많이 사용된다.

코드를 고치지 않고 새로운 라이브러리를 적용할 수 있다는 것은 큰 장점이다.

 

Git: https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

디자인패턴 Command Pattern

커맨드 패턴은 요구사항을 객체로 캡슐화 할 수 있으며, 매개변수를 써서 여러 가지 다른 요구 사항을 집어넣을 수도 있습니다.

또한 요청 내역을 큐에 저장하거나 로그로 기록할 수도 있으며, 작업취소 기능도 지원 가능합니다.

 

커맨드 패턴은 실제로 어떻게 적용할까요?

커맨드 객체는 일련의 행동을 특정 리시버하고 연결시킴으로써 요구 사항을 캡슐화한 것입니다.

리시버를 한 객체에 넣어주고, 실행시키기 위한 메서드 하나만 외부에 공개하는 방법을 사용합니다.

이 메소드 호출에 의해 리시버에서 일련의 작업이 처리됩니다.

외부에서는 어떤 객체가 리시버 역할을 하는지 모르고 실제로 어떤 일을 하는지 알 수 없습니다.

실행을 위한 메서드만 호출하면 요구 사항이 처리된다는 것만 알 수 있습니다.

 

Command

package cg.park.designpattern.command;

public interface Command {
    public void execute();
}

 

 

Light ON/OFF

불을 켜고 끄는 Light ON과 Light OFF 라는 기능이 있습니다.

사용자 입장에서는 버튼만 누르면 기능은 실행됩니다.

사용자는 버튼을 눌렀을 때 내부적으로 어떤 일이 일어나는지 모릅니다.

이렇게 요청 내역을 객체로 캡슐화하고, 클라이언트는 버튼 하나로 기능을 제공받을 수 있습니다.

 

구현

Command

package cg.park.designpattern.command;

public interface Command {
    public void execute();
}

 

Light

package cg.park.designpattern.command;

public class Light {

    public Light() {}

    public void on() {System.out.println("Light: ON");}

    public void off() {System.out.println("Light: OFF");}

}

 

LightOff

package cg.park.designpattern.command;

public class LightOff implements Command {
    Light light;

    public LightOff(Light light) {
        this.light = light;
    }

    public void execute() {
        light.off();
    }
}

 

LightOn

package cg.park.designpattern.command;

public class LightOn implements Command {
    Light light;

    public LightOn(Light light) {
        this.light = light;
    }

    public void execute() {
        light.on();
    }
}

 

RemoteControl

package cg.park.designpattern.command;

public class RemoteControl {

    public static void main(String[] args) {
        Light light = new Light();
        LightOn lightOn = new LightOn(light);
        LightOff lightOff = new LightOff(light);

        lightOn.execute();//ON
        lightOff.execute();//OFF
    }

}

위의 main 클래스에서 execute를 사용하면 LightON, LightOFF가 실행됩니다.

 

후기

처음에는 Facade Pattern과 뭐가 다른지 궁금했었는데, 직접 자료를 찾아보고 구현해보면서 알게되었습니다.
감사합니다.

퍼사드 패턴 보러가기

 

Git: https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

디자인 패턴 Singleton Pattern 

 

싱글턴 패턴은 클래스 인스턴스가 하나만 만들어지도록 하고, 그 인스턴스에 대한 전역 접근을 제공합니다.

 

인스턴스 생성을 위해서 getInstance()를 호출해야 함.

 

싱글턴 패턴은 실제로 어떻게 적용할까요?

클래스에서 단 하나만 생성할 수 있게 관리합니다.

다른 클래스에서도 자신의 인스턴스를 추가로 만들지 못하도록 해야 합니다.

인스턴스를 생성하길 원하면 반드시 Singleton 클래스의 getInstance()를 호출해야 합니다.

 

인스턴스 접근을 어디에서도 가능하게 만들어야 합니다.

다른 객체에서 인스턴스가 필요할 때 언제든지 Singleton 클래스에 요청을 할 수 있게 만들고, 요청이 들어오면 getInstance()에서 만들어진 유일한 인스턴스를 반환해줘야 합니다.

 

Singleton Pattern 인스턴스를 이해하려면?

Singleton 인스턴스 사용

Singleton 인스턴스 사용을 유료 서비스 구독이라고 가정해봅니다.

유료 서비스를 이용할 때 구독이 안되어 있다면 최초 이용 시 구독 신청을 하게 됩니다.

구독 신청이 된 후 서비스를 제공받을 수 있습니다.

그리고 다시 서비스를 이용할 경우, 구독이 되어있다면 구독 신청 없이 바로 서비스를 제공받게 됩니다.

이미 구독 중이라면 다시 구독 신청을 할 필요는 없습니다.

 

 

Instance로 바꿔서 생각해보면?

위의 내용들을 Singleton Pattern의 인스턴스로 바꿔서 생각해보면 Singleton은 쉽게 느껴질 것입니다.

유료 서비스: Instance 호출

구독 중: Instance 존재 여부

구독 신청: Instance 생성

서비스 제공: Instance 제공

 

 

Singleton Class

package cg.park.designpattern.singleton;

public class Singleton {
    private volatile static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                    return singleton;
                }
            }
        }
        return singleton;
    }

}

 

Singleton 인스턴스를 호출 시 getInstance()를 호출해야 합니다.

객체가 Null 인지(생성된 적이 없는지) 확인을 합니다.

그리고 동시에 이 영역에 접근하지 못하게 synchronized를 선언합니다.

그리고 이 찰나에 null이 되었는지 체크를 다시 한 후에

Singleton 인스턴스를 생성 후 반환해줍니다.

Singleton 인스턴스가 이미 생성되었다면 바로 반환해줍니다.

 

synchronized를 제일 앞에 선언하지 않은 이유

synchronized가 있다면 이미 생성된 Singleton 인스턴스라도 모든 접근이 동기화되기 때문입니다.

동기화가 된다는 것은 인스턴스를 사용하기 위해 접근하려면 줄을 서야 하는 것입니다.

점심시간에 인기가 많은 식당을 가게 되면 줄을 서는 것처럼 Singleton 인스턴스를 사용하기 위해서는 줄을 서야 합니다.

하지만 이미 인스턴스가 있고 반환만 해주면 되는 상태라면 굳이 줄을 설 필요가 없습니다.

그래서 제일 위해 null 체크를 한 후, 인스턴스가 없는 경우에만 synchronized를 선언합니다.

synchronized가 선언되었기 때문에 1개의 인스턴스만 만들어지고, 그 후에는 이미 생성된 인스턴스를 반환할 것입니다.

 

구현

Paid라는 클래스를 만들어서 Singleton 인스턴스를 호출합니다.

Paid

package cg.park.designpattern.singleton;

public class Paid {

    public void perpare(String name) {
        System.out.println("유료 서비스 ========== START ==========");
        System.out.println(name +" 접속");
        Singleton.getInstance();
        System.out.println("유료 서비스 ========== E N D ==========");
    }
}

 

Singleton에 접근하면 로그를 남기도록 합니다.

Singleton

package cg.park.designpattern.singleton;

public class Singleton {
    private volatile static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    System.out.println("결제 후 서비스 제공");
                    singleton = new Singleton();
                    return singleton;
                }
            }
        }
        System.out.println("서비스 제공");
        return singleton;
    }

}

 

Paid를 호출할 Main 클래스를 만듭니다.

Main

package cg.park.designpattern.singleton;

public class Main {

    public static void main(String[] args) {
        Paid paid = new Paid();
        paid.perpare("1년 전");
        paid.perpare("반년 전");
        paid.perpare("현재");
    }

}

 

위의 로그를 보면 첫 번째 접근 시에만 결제 후 서비스를 제공하고 그다음부터는 바로 서비스를 제공합니다.

 

후기

Singleton Pattern은 꼭 필요한 경우가 아니라면 사용하지 않는 것을 추천합니다.

 

Git: https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

Factory Method Pattern

 

팩토리 메소드 패턴에서는 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만듭니다.

팩토리 메소드 패턴을 이용하면 클래스의 인스턴스를 만드는 일을 서브클래스에게 맡기는 것입니다.

 


 

설명

 

버거 주문 과정을 만드려고 합니다.

버거 주문을 받아서 준비 완료까지 만들 예정입니다.

Burger라는 추상 클래스를 만든 다음 Burger를 만들어줄 구상 클래스를 만듭니다.

 

public void orderBurger(String type) {
    Burger burger;
    if (type.equals("cheese")) {
        //burger 객체에 치즈버거 인스턴스 삽입
    }
    else if (type.equals("bulgogi")) {
        //burger 객체에 불고기버거 인스턴스 삽입
    }
    // 버거 종류만큼 else if ...
    
    burger.order();//주문
    burger.makeBurger();//준비 중
    burger.done();//준비 완료
}

 

type의 값에 따라서 인스턴스를 반환합니다.

저렇게 코딩을 하게 되면 메뉴가 늘어날 때마다 주문을 받는 기능에 메뉴를 계속 생성해줍니다.

그래서 인스턴스를 생성해주는 기능을 서브클래스로 분리합니다.

 

public Burger createBurger(String type) {
    if (type.equals("cheese")) return new CheeseBurger();
    if (type.equals("bulgogi")) return new BulgogiBurger();
    if (type.equals("chicken")) return new ChickenBurger();
    return null;
}

 

type에 따라서 인스턴스를 반환해주는 기능만 따로 분리한 것입니다.

이렇게 되면 위에 주문받는 기능은 간단해집니다.

 

 

type에 따라서 인스턴스 생성하던 부분을 서브클래스에 위임하여 코드가 간결해졌습니다.

 

 

Burger: 버거 만드는 과정 추상화.

CheeseBurger: 치즈버거 구현.

ChickenBurger: 치킨버거 구현.

BulgogiBurger: 불고기버거 구현.

BurgerFactory: 구현된 버거 클래스 호출.

BurgerStore: 주문받는 클래스.

Order: 주문하는 클래스.

 


 

구현

 

Burger

package cg.park.designpattern.factory.burgers;

import java.util.ArrayList;
import java.util.List;

abstract public class Burger {
    String name;
    String bun;
    String sauce;
    List<String> toppings = new ArrayList<>();

    public String getName() {
        return name;
    }

    public void order() {
        System.out.println("===========START===========");
        System.out.println("[주문]");
        System.out.println(name);
    }

    public void makeBurger() {
        System.out.println("[재료]");
        System.out.println(bun);
        System.out.println(sauce);
        toppings.forEach(str -> System.out.println(str));
    }

    public void done() {
        System.out.println("===========E N D===========");
    }

}

 

CheeseBurger

package cg.park.designpattern.factory.burgers;

public class CheeseBurger extends Burger {
    public CheeseBurger() {
        name = "Cheese Burger";
        bun = "Cheese bun";
        sauce = "Cheese sauce";
        toppings.add("Cheese");
        toppings.add("Patty");
    }
}

 

BurgerFactory

package cg.park.designpattern.factory.burgers;

public class BurgerFactory {

    public Burger createBurger(String type) {
        if (type.equals("cheese")) return new CheeseBurger();
        if (type.equals("bulgogi")) return new BulgogiBurger();
        if (type.equals("chicken")) return new ChickenBurger();
        return null;
    }
}

 

BurgerStore

package cg.park.designpattern.factory.burgers;

public class BurgerStore {
    BurgerFactory factory;

    public BurgerStore(BurgerFactory factory) {
        this.factory = factory;
    }

    public void orderBurger(String type) {
        Burger burger = factory.createBurger(type);
        burger.order();//주문
        burger.makeBurger();//준비 중
        burger.done();//준비 완료
    }

}

 

Order

package cg.park.designpattern.factory.burgers;

public class Order {

    public static void main(String[] args) {
        BurgerStore store = new BurgerStore(new BurgerFactory());
        store.orderBurger("cheese");//치즈버거 주문
        store.orderBurger("bulgogi");//불고기버거 주문
        store.orderBurger("chicken");//치킨버거 주문
    }

}

 


 

후기

 

팩토리 메서드 패턴에 대해 알아봤습니다.

객체를 생성하기 위한 인터페이스를 정의하고, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만드는 방법입니다. 

감사합니다.

 

Git: https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

 

Decorator Pattern

 

객체에 추가 요소를 동적으로 더할 수 있습니다.

데코레이터를 사용하면 서브클래스를 만드는 경우에 비해 훨씬 유연하게 기능을 확장할 수 있습니다.

 


 

설명

 

버거 가게 메뉴를 만드려고 합니다.

버거 단품을 주문할 수 있지만 그 단품에 패티, 치즈 등 재료를 추가할 수도 있습니다.

이렇게 추가 기능을 만들 때 각각의 버거 클래스에 패티 추가, 치즈 추가 등 옵션 기능을 추가해주는 것이 맞을까요?

 

Burger

package cg.park.designpattern.decorator.buger;

public class CheeseBurger extends Burger {
    public CheeseBurger() {
        description = "Basic Burger";
    }//버거 이름
    public double cost() {
        return 6000;
    }//버거 가격
}

 

치즈버거 객체입니다.

현재 구현된 것은 버거 이름과 버거 가격입니다.

옵션 추가 기능을 클래스에 부여한다고 가정해보겠습니다.

 

Burger

package cg.park.designpattern.decorator.buger;

public class CheeseBurger extends Burger {
    public CheeseBurger() {
        description = "Basic Burger";
    }//버거 이름
    public double cost() {
        return 6000;
    }//버거 가격

    //------추가 부분------
    public void addCheese() {};//치즈 추가
    public void addPatty() {};//패티 추가
}

 

치즈 추가와 패티 추가내용을 하단에 만들었습니다.

예를 들어 100개의 버거 클래스가 있다면 모든 클래스에 추가 내용을 만들어줘야 합니다.

그리고 추가하는 옵션이 달라질 때마다 100개의 버거 클래스를 수정해야합니다.

과연 이것이 유지보수에 용이하게 만들어진 것일까요?

이번 Decorator Pattern에서는 옵션의 기능을 따로 분리하여 만들 것입니다.

만들어진 객체 코드를 수정하지 않고 추가 기능을 더할 수 있는 패턴입니다.

 

BurgerOption

package cg.park.designpattern.decorator.bugger;

public abstract class BurgerOption extends Burgger {
    public Burgger burgger;
    public abstract String getDescription();

    public Burgger.Size getSize() {return burgger.getSize();}
}

 

버거의 옵션을 담당할 클래스를 따로 만들어줍니다.

 

 

전체 버거의 구성을 잡아줄 추상 클래스 Burger를 만듭니다.

Burger를 상속받아서 각 버거 클래스를 구현합니다.

BurgerOption 클래스도 만들어줍니다.

BurgerOption 클래스는 각 옵션들의 부모 클래스가 될 것이기 때문에 추상 클래스로 구현합니다.

BurgerOption을 상속받은 각 옵션 클래스들을 구현합니다.

 

구현

 

Burger

package cg.park.designpattern.decorator.bugger;

public abstract class Burger {
    public enum Size { BASIC, LARGE };//버거 사이즈
    Burger.Size size = Size.BASIC;//기본 제공 사이즈
    String description = "Unknown Burger";//버거 설명

    //버거 설명
    public String getDescription() {
        return description;
    }

    //버거 사이즈 변경
    public void setSize(Burger.Size size) {
        this.size = size;
    }

    //버거 사이즈 조회
    public Size getSize() {
        return this.size;
    }

    //버거 가격
    public abstract double cost();

}

 

BasicBurger

package cg.park.designpattern.decorator.bugger;

public class BasicBurger extends Burger {
    
    //버거 설명: 기본 버거
    public BasicBurger() {
        description = "Basic Burger";
    }

    //가격: 5000
    public double cost() {
        return 5000;
    }
}

버거 클래스에 사이즈 변경 및 옵션 기능을 넣지 않았습니다.

버거 외 부가적인 기능은 옵션 클래스로 분리하였습니다.

 

BurgerOption

package cg.park.designpattern.decorator.bugger;

public abstract class BurgerOption extends Burger {
    public Burger burger;//버거 객체 상속
    public abstract String getDescription();//버거 설명

    //버거 사이즈
    public Size getSize() {return burger.getSize();}
}

 

Cheese

package cg.park.designpattern.decorator.bugger;

public class Cheese extends BurgerOption {
    //버거 객체
    public Cheese(Burger burger) {
        this.burger = burger;
    }

    //버거 설명에 Cheese 추가
    public String getDescription() {return burger.getDescription() + ", Cheese";}

    //버거 가격에 Cheese 가격 추가
    public double cost() {return burger.cost() + 500;}
}

 

PcgBurger

package cg.park.designpattern.decorator.bugger;

public class PcgBurger {

    public static void main(String args[]) {
        Burger burger = new BasicBurger();//기본 버거
        System.out.println("[첫 번째 주문] " +
                "\n버거: "+ burger.getDescription()
                +"\n가격: "+ burger.cost()
        );

    }
}

버거 주문 시 결과입니다.

기본 버거뿐 아니라 치즈버거, 치킨버거, 불고기버거와 옵션도 추가해보겠습니다.

 

package cg.park.designpattern.decorator.bugger;

import cg.park.designpattern.decorator.bugger.Burger.Size;

public class PcgBurger {

    public static void main(String args[]) {
        Burger burger = new BasicBurger();//기본 버거
        System.out.println("[첫 번째 주문] " +
                "\n버거: "+ burger.getDescription()
                +"\n가격: "+ burger.cost()
        );

        Burger cheeseBurger = new CheeseBurger();//치즈 버거
        cheeseBurger = new Cheese(cheeseBurger);//치즈 추가
        System.out.println("[두 번째 주문] " +
                "\n버거: "+cheeseBurger.getDescription()
                +"\n가격: "+cheeseBurger.cost()
        );

        Burger bulgogiBurger = new BulgogiBurger();//불고기 버거
        bulgogiBurger.setSize(Size.LARGE);//라지로 변경
        System.out.println("[세 번째 주문] " +
                "\n버거: "+bulgogiBurger.getDescription()
                +"\n가격: "+bulgogiBurger.cost()
        );

        Burger chickenBurger = new ChickenBurger();//치킨 버거
        chickenBurger = new Chicken(chickenBurger);//치킨 추가
        System.out.println("[네 번째 주문] " +
                "\n버거: "+chickenBurger.getDescription()
                +"\n가격: "+chickenBurger.cost()
        );
    }
}

각 주문의 결과를 확인할 수 있습니다.

 


 

후기

 

실무에서 클래스에 기능을 추가하는 일은 많습니다.

기능을 추가할 때 단순하게 생각하면 해당 클래스나 메서드 밑에 기능을 추가하면 됩니다.

하지만 이렇게 만든다면 전체 프로젝트 중 방금 수정한 1개의 클래스를 위해 작업이 진행된 것입니다.

해당 추가 기능에 대해 분석, 설계가 잘 되었다면 다행이지만, 임시 방안으로 만든 내용이라면 추후에 문제가 발생할 수 있습니다.

이런 상황에서 프로젝트에 대한 이해와 기본기가 탄탄하면 보다 좋은 방향으로 기능을 추가할 수 있습니다.

감사합니다.

 

Git: https://github.com/qkrcksrbs8/designpattern

참조 자료: Head First Design Patterns

+ Recent posts