1. 전략 패턴이란?

전략 패턴은 실행 중에 알고리즘을 선택할 수 있게 하는 행위 소프트웨어 디자인 패턴이다. - 위키 백과

동일 계열의 알고리즘군을 정의하고, 각 알고리즘을 캡슐화하며, 이들을 상호교환이 가능하도록 만든다. 알고리즘을 사용하는 클라이언트와 상관없이 독립적으로 알고리즘을 다양하게 변경할 수 있게 한다. - GoF

특정 기능이 추가될 때, 전략 객체를 생성 후 주입하는 방식으로 유연하게 기능을 확장할 수 있습니다.

전략 패턴 클래스 다이어그램

 

2. 예제 소스

1) Strategy 인터페이스 및 Strategy 클래스

//전략 인터페이스
public interface PrintStrategy {

    public void print();
}

//프린터로 전달하는 전략
public class PrinterStrategy implements PrintStrategy {

    @Override
    public void print() {
        System.out.println("Printer로 전달");
    }

}

//구글 드라이브로 전달하는 전략
public class GoogleDriveStrategy implements PrintStrategy {

    @Override
    public void print() {
        System.out.println("구글 드라이브로 전달");
    }

}

//팩스로 전달하는 전략
public class FaxStrategy implements PrintStrategy {

    @Override
    public void print() {
        System.out.println("Fax로 전달");
    }

}
각 전략 클래스들은 PrintStrategy 인터페이스를 상속하고 print() 메소드를 오버라이딩합니다.

 

2) Context 클래스

import java.util.Objects;

public class Printing {

    private PrintStrategy printStrategy;

    public void print() {
        if (Objects.nonNull(printStrategy)) {
            printStrategy.print();
        }
    }

    public void setPrintStrategy(PrintStrategy printStrategy) {
        this.printStrategy = printStrategy;
    }

}
인쇄 관련 기능을 직접 구현하지 않고 전략을 주입하는 형식으로 인쇄 기능을 수행합니다.
setPrintStrategy(PrintStrategy printStrategy) 메소드를 통해 인쇄 전략을 주입받고, print() 메소드를 통해 주입받은 전략을 수행합니다.

 

3) Client 

public class ChromeBrower extends Printing {

}
public class Main {

    public static void main(String[] args) {
        ChromeBrower chromeBrower = new ChromeBrower();
        PrintStrategy printerStrategy = new PrinterStrategy();
        PrintStrategy googleDriveStrategy = new GoogleDriveStrategy();

        chromeBrower.setPrintStrategy(printerStrategy);
        chromeBrower.print();

        chromeBrower.setPrintStrategy(googleDriveStrategy);
        chromeBrower.print();

    }

}
ChromeBrower 클래스는 Printing 클래스를 상속 받고, 다양한 전략을 주입받아서 인쇄 기능을 수행합니다.
새로운 기능이 추가되어도 PrintStrategy 전략을 생성하고 주입하는 형식으로 유연하게 기능을 확장할 수 있습니다.

참조

https://ko.wikipedia.org/wiki/%EC%A0%84%EB%9E%B5_%ED%8C%A8%ED%84%B4
https://victorydntmd.tistory.com/292
팩토리 메소드 패턴
객체의 생성을 팩토리 클래스로 위임하여 객체를 생성하는 패턴

추상 팩토리 패턴
팩토리를 추상화해서 관련있는 객체의 집합을 생성할 수 있는 팩토리를 만들고 조건에 따라 팩토리를 생성해서 서로 관련된 객체를 생성하는 패턴 

 

JAVA 소스 예제

public abstract class Phone {

    public abstract void power();
}

public class SamsungPhone extends Phone {

    @Override
    public void power() {
        System.out.println("samsung phone power on");
    }

}

public class ApplePhone extends Phone {

    @Override
    public void power() {
        System.out.println("apple phone power on");
    }

}
SamsungPhone 클래스와 ApplePhone 클래스는 Phone 추상클래스를 상속하고 power() 메소드를 오버라이딩한다.

 

public interface PhoneFactory {

    public Phone create();
}

public class ApplePhoneFactory implements PhoneFactory {

    @Override
    public Phone create() {
        return new ApplePhone();
    }

}

public class SamsungPhoneFactory implements PhoneFactory {

    @Override
    public Phone create() {
        return new SamsungPhone();
    }

}
ApplePhoneFactory 클래스와 SamsungPhoneFactory 클래스는 PhoneFactory를 상속하고 create()메소드를 오버라이딩한다.
create() 메소드는 각각 팩토리 클래스 별로 제조사에 맞는 Phone 클래스를 반환한다.

 

public enum PhoneType {
    SAMSUNG, APPLE
}

public class Main {

    public static void main(String[] args) {
        PhoneFactory phoneFactory = null;
        PhoneType phoneType = PhoneType.SAMSUNG;

        switch (phoneType) {
            case SAMSUNG:
                phoneFactory = new SamsungPhoneFactory();
                break;
            case APPLE:
                phoneFactory = new ApplePhoneFactory();
                break;
            default:
                throw new RuntimeException(phoneType.toString() + " is not existed");
        }

        Phone phone = phoneFactory.create();
        phone.power();

    }

}
enum 타입인 phoneType 별로 PhoneFactory를 생성하고 create() 메소드를 실행한 뒤, phone의 power() 메소드를 실행한다.
phoneType 별로 생성되는 Factory가 바뀌어서 생성되는 phone 또한 바뀌는 것을 볼 수 있다.

 

1. 팩토리 메소드 패턴이란?

객체를 생성하기 위한 인터페이스를 정의하고, 어떤 클래스의 인스턴스를 생성할지에 대한 처리는 서브클래스가 결정하는 디자인 패턴
- GoF

 

참조 : https://johngrib.github.io/wiki/factory-method-pattern/

 

2. 팩토리 메소드 패턴을 사용하는 이유

팩토리 메소드 패턴을 사용하는 이유는 클래스의 생성과 사용의 처리로직을 분리하여 결합도를 낮추기 위한 것 입니다. 
결합도는 간단히 말해서 클래스의 처리 로직에 대한 변경점이 생겼을 때 얼마나 사이드 이펙트를 주는가 인데, 팩토리 메소드 패턴을 사용할 경우 직접 객체를 생성해 사용하는 것을 방지하고 서브 클래스에 생성 로직을 위임함으로써 보다 효율적인 코드 제어를 할 수 있고 의존성을 제거 합니다.

 

3. 팩토리 메소드 패턴 예제

1) 음료수의 추상 클래스

public abstract class Drink {

    abstract public String info();
    
}
음료수의 베이스가 되는 추상클래스입니다. 모든 음료수는 info() 메소드를 오버라이딩해야 합니다.

 

2) 음료수 클래스

public class Coffee extends Drink {

    @Override
    public String info() {
        return "커피 맛";
    }

}
public class Coke extends Drink {

    @Override
    public String info() {
        return "콜라 맛";
    }

}
public class Tejava extends Drink {

    @Override
    public String info() {
        return "크래파스 맛";
    }

}
Drink 추상 클래스를 상속받는 음료수 클래스입니다. 각 클래스는 info() 메소드를 오버라이딩합니다.

 

3) 음료수 타입 (enum)

public enum DrinkType {
    Coke, Coffee, Tejava
}
음료수 타입은 enum으로 지정했습니다.

 

4) Factory Class

public abstract class DrinkFactory {
    abstract Drink create(DrinkType drinkType);
}
//factory class
public class VendingMachine extends DrinkFactory {

    @Override
    public Drink create(DrinkType drinkType) {

        switch (drinkType) {
            case Coffee:
                return new Coffee();
            case Coke:
                return new Coke();
            case Tejava:
                return new Tejava();
            default:
                throw new RuntimeException(drinkType.toString() + " is not existed");
        }
    }
}
DrinkFactory 클래스의 서브 클래스인 VendingMachine 클래스에서 create() 메소드를 오버라이딩합니다.
create 메소드의 파라미터로 drinkType을 전달받아 Drink 객체를 생성합니다.

 

5) Main

public class Main {

    public static void main(String[] args) {
        
        DrinkFactory drinkFactory = new VendingMachine();

        Drink drink1 = drinkFactory.create(DrinkType.Coffee);
        Drink drink2 = drinkFactory.create(DrinkType.Tejava);

        System.out.println(drink1.info());
        System.out.println(drink2.info());

    }

}
create 메소드에 DrinkType enum을 파라미터로 전달해 Drink 객체를 생성합니다.

 

6) 결과

커피 맛
크래파스 맛

참조 :
jdm.kr/blog/180
johngrib.github.io/wiki/factory-method-pattern/

1. 템플릿 메소드 패턴이란?

알고리즘의 구조를 메소드에 정의하고, 하위 클래스에서 알고리즘 구조의 변경없이 알고리즘을 재정의 하는 패턴이다. 알고리즘이 단계별로 나누어 지거나, 같은 역할을 하는 메소드이지만 여러곳에서 다른형태로 사용이 필요한 경우 유용한 패턴이다. - GoF Design Patterns

참조 : https://yaboong.github.io/design-pattern/2018/09/27/template-method-pattern/
템플릿 메소드 패턴은 알고리즘의 흐름이 정해져 있지만, 특정 부분은 필요에 따라 변경해야 할 때, 사용합니다. 

 

2. 템플릿 메소드 패턴 예제

1) Template.java

public abstract class Template {

    final public void excute() {
        method1();
        //... 무언가 처리
        method2();
    }

    protected abstract void method1();

    protected abstract void method2();
}
excute 메소드에 고정된 처리 과정과 필요에 따라 처리해주어야할 메소드들(method1, method2)을 배치해줍니다.

2)  Concrete.java

public class Concrete extends Template {

    @Override
    protected void method1() {
        System.out.println("Hi !!");
    }

    @Override
    protected void method2() {
        System.out.println("Hello !!");
    }

}
method1과 method2를 오버라이딩합니다.
만약 또 다른 기능이 필요하다면, Template 추상클래스를 상속받아서 각 메소드들을 오버라이딩 해주면 됩니다.

3) Main.java

public class Main {

    public static void main(String[] args) {
        Concrete concrete = new Concrete();
        concrete.excute();

    }

}
excute 메소드를 실행하면, 정의해두었던 메소드의 흐름에 따라 처리가 진행됩니다.

1. 프록시 패턴이란?

실제 객체를 바로 이용하는 것 대신 가상 객체에 실제 객체를 선언하여 실제 객체의 기능과 추가적인 기능을 사용함으로 써 기능의 흐름을 제어하는 디자인 패턴입니다.

Proxy 패턴 구조

2. 프록시 패턴 장점

1) 실제 객체를 수행하기 전에 전처리를 하거나 기본 객체를 캐싱할 수 있다.
2) 실제 객체를 수정하지 않고 추가적인 기능을 삽입할 수 있다.

3. 프록시 패턴 예제

1) MainSender.java

public interface MailSender {
    public void send(String message);
}

 

2) RealMailSender.java

public class RealMailSender implements MailSender {

    @Override
    public void send(String message) {
        System.out.println("[SEND] : " + message);

    }
}
실제 MailSender를 구현하는 부분이다. 

3) ProxyMailSender.java

import java.util.Objects;

public class ProxyMailSender implements MailSender {

    private MailSender mailSender = null;

    public ProxyMailSender() {
    }

    @Override
    public void send(String message) {
        if (Objects.isNull(mailSender)) {
            mailSender = new RealMailSender();
        }

        System.out.println("proxy before process");
        mailSender.send(message);
        System.out.println("proxy after process");
    }

}
ProxyMailSender 클래스는 MailSender 인터페이스의 send 메소드를 구현하지만 실제로는 RealMailSender의 send메소드를 실행시킨다. send 메소드를 수행하기 이전에 필요한 기능을 추가할 수 있다.

4) Main.java

public class Main {
    public static void main(String[] args) {
        MailSender mailSender = new ProxyMailSender();
        mailSender.send("Hello world!");
    }
}
ProxyMailSender를 생성해서 send메소드를 수행한다.
( MailSender에는 ProxyMailSender 또는 RealMailSender를 삽입해도 아무 문제 없이 send 메소드를 수행할 수 있다. )

 

5) 결과

proxy before process
[SEND] : Hello world!
proxy after process

+ Recent posts