[디자인패턴] 생성 패턴 - 프로토타입(Prototype) 패턴

 

 

 

*프로토타입 패턴

- 기존 인스턴스를 복제하여 새로운 인스턴스를 만드는 방법

  • 복제 기능을 갖추고 있는 기존 인스턴스를 프로토타입으로 사용해 새 인스턴스를 만들 수 있다.

 

 

 

 

- Prototype, ConcretePrototypeA, ConcretePrototypeB

package me.whiteship.designpatterns._01_creational_patterns._05_prototype.practice;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class DatabaseInfo {
    private String db;
    private String host;
    private int port;
}
package me.whiteship.designpatterns._01_creational_patterns._05_prototype.practice;

import lombok.Getter;
import lombok.Setter;

import java.util.Objects;

@Getter @Setter
public class JdbcConnectionInfo implements Cloneable{
    private int id;
    private String title;
    private DatabaseInfo databaseInfo;

    public JdbcConnectionInfo(DatabaseInfo databaseInfo) {
        this.databaseInfo = databaseInfo;
    }

    public String getConnection() {
        return String.format("jdbc:%s://%s/%d",
                this.databaseInfo.getDb(),
                this.databaseInfo.getHost(),
                this.databaseInfo.getPort());
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Prototype 인터페이스는 java에서 제공하는 Object 클래스에 있는 clone 메소드를 사용한다. 그러기 위해선 Cloneable 인터페이스를 구현해야 하는데 Clonable 인터페이스를 구현하면 clone 메소드를 오버라이딩 해서 사용할 수 있다. 기본 오버라이딩을 하게되면 부모 클래스에 있는 clone을 가져온다.

 

 

- Client

package me.whiteship.designpatterns._01_creational_patterns._05_prototype.practice;

public class App {
    public static void main(String[] args) throws CloneNotSupportedException {
        DatabaseInfo databaseInfo = new DatabaseInfo();
        databaseInfo.setDb("mysql");
        databaseInfo.setHost("localhost");
        databaseInfo.setPort(3306);

        JdbcConnectionInfo jdbcConnection = new JdbcConnectionInfo(databaseInfo);
        jdbcConnection.setId(1);
        jdbcConnection.setTitle("first connection");

        String connection = jdbcConnection.getConnection();
        System.out.println(connection); // jdbc:mysql://localhost/3306

        JdbcConnectionInfo clone = (JdbcConnectionInfo) jdbcConnection.clone();
        
        databaseInfo.setDb("oracle");
        
        System.out.println(clone != jdbcConnection); // true
        System.out.println(clone.equals(jdbcConnection)); // false
        System.out.println(clone.getClass() == jdbcConnection.getClass()); // true
        System.out.println(clone.getDatabaseInfo() == jdbcConnection.getDatabaseInfo()); // true

        System.out.println(jdbcConnection.getConnection()); 
        // jdbc:oracle://localhost/3306
        System.out.println(clone.getConnection()); 
        // jdbc:oracle://localhost/3306
    }
}

Object의 clone 메소드를 사용하면 복제하려고 하는 인스턴스의 정보를 가져올 수 있으며, 이 방법은 싱글톤이 아니다. 객체는 다른 객체의 레퍼런스를 참조하지만 값은 그대로 복제 할 수 있다.

여기서, clone 메소드는 기본적으로 깊은 복사(deep copy)가 아닌 얕은 복사(shallow copy)를 한다. 따라서 JdbcConnectionInfo(ConcretePrototypeA)에서 사용하고 있는 DataBaseInfo(ConcretePrototypeB)는 기존의 인스턴스와 clone 인스턴스가 같은 레퍼런스를 참조한다.

 

 

- ConcreatePrototypeA(equals, hashCode 재정의)

package me.whiteship.designpatterns._01_creational_patterns._05_prototype.practice;

import lombok.Getter;
import lombok.Setter;

import java.util.Objects;

@Getter @Setter
public class JdbcConnectionInfo implements Cloneable{
    ...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        JdbcConnectionInfo that = (JdbcConnectionInfo) o;
        return id == that.id && Objects.equals(title, that.title) && Objects.equals(databaseInfo, that.databaseInfo);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, title, databaseInfo);
    }

	...
}

먼저 equals 메소드와 같이 객체의 주소 값이 아닌 객체 안에 있는 값들의 비교를 원한다면 equals 메소드를 재정의 할 필요가 있다. 이 때 hashCode 메소드도 같이 변경한다.

 

 

- Client(equals, hashCode 재정의)

package me.whiteship.designpatterns._01_creational_patterns._05_prototype.practice;

public class App {
    public static void main(String[] args) throws CloneNotSupportedException {
        ...
        
        System.out.println(clone != jdbcConnection); // true
        System.out.println(clone.equals(jdbcConnection)); // true
        System.out.println(clone.getClass() == jdbcConnection.getClass()); // true
        System.out.println(clone.getDatabaseInfo() == jdbcConnection.getDatabaseInfo()); // true

        ...
    }
}

clone과 jdbcConnection의 equals 비교값이 바뀌었다.

 

 

- ConcretePrototypeA(clone 메소드 수정)

package me.whiteship.designpatterns._01_creational_patterns._05_prototype.practice;

import lombok.Getter;
import lombok.Setter;

import java.util.Objects;

@Getter @Setter
public class JdbcConnectionInfo implements Cloneable{
    ...

    @Override
    protected Object clone() throws CloneNotSupportedException {
        DatabaseInfo databaseInfo = new DatabaseInfo();
        databaseInfo.setDb(this.databaseInfo.getDb());
        databaseInfo.setHost(this.databaseInfo.getHost());
        databaseInfo.setPort(this.databaseInfo.getPort());

        JdbcConnectionInfo jdbcConnection = new JdbcConnectionInfo(databaseInfo);
        jdbcConnection.setId(this.getId());
        jdbcConnection.setTitle(this.getTitle());
        
        return jdbcConnection;
    }

    ...
}

얕은 복사를 깊은 복사로 만드는 방법은 clone 메소드를 재정의 하는 방법을 사용할 수 있다. ConcretePrototype에서 clone을 통해 복사할 데이터의 범위를 직접 명시하면된다.

 

 

- Client(clone 메소드 수정)

package me.whiteship.designpatterns._01_creational_patterns._05_prototype.practice;

public class App {
    public static void main(String[] args) throws CloneNotSupportedException {
        DatabaseInfo databaseInfo = new DatabaseInfo();
        databaseInfo.setDb("mysql");
        databaseInfo.setHost("localhost");
        databaseInfo.setPort(3306);

        JdbcConnectionInfo jdbcConnection = new JdbcConnectionInfo(databaseInfo);
        jdbcConnection.setId(1);
        jdbcConnection.setTitle("first connection");

        String connection = jdbcConnection.getConnection();
        System.out.println(connection); // jdbc:mysql://localhost/3306

        JdbcConnectionInfo clone = (JdbcConnectionInfo) jdbcConnection.clone();
        
        databaseInfo.setDb("oracle");
        
        System.out.println(clone != jdbcConnection); // true
        System.out.println(clone.equals(jdbcConnection)); // false
        System.out.println(clone.getClass() == jdbcConnection.getClass()); // true
        System.out.println(clone.getDatabaseInfo() == jdbcConnection.getDatabaseInfo()); // false

        System.out.println(jdbcConnection.getConnection()); 
        // jdbc:oracle://localhost/3306
        System.out.println(clone.getConnection()); 
        // jdbc:mysql://localhost/3306
    }
}

얕은 복사에서 깊은 복사로 변경 후에 ConcretePrototypeA(JdbcConnectionInfo)가 참조하는 ConcreatePrototypeB(DatabaseInfo)의 정보가 변경되었을 때 서로 다른 레퍼런스를 참조하기 때문에 equals 메소드나 참조하고 있는 databaseInfo의 값이 다르다는 것을 알수있다. 또한 서로 값 변경에 대해 영향을 주지 않는다.

 

 

- 장점

  • 복잡한 객체를 만드는 과정을 숨길 수 있다.
  • 기존 객체를 복제하는 과정이 새 인스턴스를 만드는 것보다 비용(시간 또는 메모리)적인 면에서 효율적일 수도 있다.
  • 추상적인 타입을 리턴할 수 있다.

- 단점

  • 복제한 객체를 만드는 과정 자체가 복잡할 수 있다. (특히, 순환 참조가 있는 경우)

 

 

 

https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4

 

코딩으로 학습하는 GoF의 디자인 패턴 - 인프런 | 강의

디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할

www.inflearn.com

 

[디자인패턴] 생성 패턴 - 빌더(Builder) 패턴

 

 

 

*빌더 패턴

- 동일한 프로세스를 거쳐 다양한 구성의 인스턴스를 만드는 방법.

  • (복잡한) 객체를 만드는 프로세스를 독립적으로 분리할 수 있다.

 

 

 

 

- Product

package me.whiteship.designpatterns._01_creational_patterns._04_builder.practice;

import lombok.Getter;
import lombok.Setter;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

@Getter @Setter
public class Photograph {
    private String title;
    private String place;
    private LocalDate date;
    private String dress;
    private List<Suit> suits;

    public Photograph() {
        if (this.suits == null) {
            this.suits = new ArrayList<Suit>();
        }
    }

    public Photograph(String title, String place, LocalDate date, String dress, List<Suit> suits) {
        this.title = title;
        this.place = place;
        this.date = date;
        this.dress = dress;
        this.suits = suits;
    }

    public void addSuit(String color, String shape) {
        this.suits.add(new Suit(color, shape));
    }

    @Override
    public String toString() {
        return "Photograph{" +
                "title='" + title + '\'' +
                ", place='" + place + '\'' +
                ", date=" + date +
                ", dress='" + dress + '\'' +
                ", suits=" + suits +
                '}';
    }
}
package me.whiteship.designpatterns._01_creational_patterns._04_builder.practice;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class Suit {
    private String color;
    private String shape;

    public Suit(String color, String shape) {
        this.color = color;
        this.shape = shape;
    }

    @Override
    public String toString() {
        return "Suit{" +
                "color='" + color + '\'' +
                ", shape='" + shape + '\'' +
                '}';
    }
}

추후 빌더 패턴을 통해서 만들어낼 인스턴스의 Product(데이터)이다.

 

 

- Client

package me.whiteship.designpatterns._01_creational_patterns._04_builder.practice;

import java.time.LocalDate;

public class App {
    public static void main(String[] args) {
        Photograph firstPhotograph = new Photograph();
        firstPhotograph.setTitle("wedding photos");
        firstPhotograph.setDate(LocalDate.of(2022, 8, 20));
        firstPhotograph.setPlace("the studio");
        firstPhotograph.setDress("White flowing dress");
        firstPhotograph.addSuit("beige", "basic");
        firstPhotograph.addSuit("navy", "stripe");
        System.out.println("firstPhotograph = " + firstPhotograph);

        Photograph secondPhotograph = new Photograph();
        secondPhotograph.setTitle("wedding photos");
        secondPhotograph.setDate(LocalDate.of(2022, 8, 20));
        secondPhotograph.setPlace("the outside");
        secondPhotograph.setDress("Pink flowing dress");
        secondPhotograph.addSuit("brown", "basic");
        System.out.println("secondPhotograph = " + secondPhotograph);

        Photograph thirdPhotograph = new Photograph();
        thirdPhotograph.setTitle("wedding photos");
        thirdPhotograph.setDate(LocalDate.of(2022, 8, 20));
        thirdPhotograph.setPlace("the outside");
        thirdPhotograph.setDress("White slim dress");
        thirdPhotograph.addSuit("charcoal", "basic");
        System.out.println("thirdPhotograph = " + thirdPhotograph);
    }
}

빌더 패턴을 사용하지 않고 인스턴스를 만들게 되면 위와 같이 setter나 생성자를 통해서 인스턴스를 만들 수 있지만 입력해야 하는 값이 많아질수록 setter와 생성자는 입력해야 하는 값이 많아지고 또한 생성자는 값을 입력하지 않으려면 null 값이나 기본값을 입력해야 한다.

 

 

- Builder

package me.whiteship.designpatterns._01_creational_patterns._04_builder.practice;

import java.time.LocalDate;

public interface PhotographBuilder {
    PhotographBuilder title(String title);

    PhotographBuilder place(String place);

    PhotographBuilder startDate(LocalDate date);

    PhotographBuilder choiceDress(String dress);

    PhotographBuilder addSuit(String color, String shape);

    Photograph getPlan();
}

Builder 인터페이스를 만들고 필요한 메소드를 만든다. 각 메소드들은 Builder 인터페이스를 타입으로 갖게되어 Builder 인터페이스를 구현하는 구현체로 반환된다. 따라서 메소드 체인처럼 사용하고자 하는 메소드를 사용할 수 있다. 마지막에 Product를 반환하는 메소드를 만든다.

 

 

- ConcreteBuilder

package me.whiteship.designpatterns._01_creational_patterns._04_builder.practice;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

public class DefaultPhotographBuilder implements PhotographBuilder{

    private String title;
    private String place;
    private LocalDate date;
    private String dress;
    private List<Suit> suits;

    @Override
    public PhotographBuilder title(String title) {
        this.title = title;
        return this;
    }

    @Override
    public PhotographBuilder place(String place) {
        this.place = place;
        return this;
    }

    @Override
    public PhotographBuilder startDate(LocalDate date) {
        this.date = date;
        return this;
    }

    @Override
    public PhotographBuilder choiceDress(String dress) {
        this.dress = dress;
        return this;
    }

    @Override
    public PhotographBuilder addSuit(String color, String shape) {
        if (this.suits == null) {
            this.suits = new ArrayList<>();
        }
        this.suits.add(new Suit(color, shape));
        return this;
    }

    @Override
    public Photograph getPlan() {
        return new Photograph(this.title, this.place, this.date, this.dress, this.suits);
    }

    @Override
    public String toString() {
        return "DefaultPhotographBuilder{" +
                "title='" + title + '\'' +
                ", place='" + place + '\'' +
                ", date=" + date +
                ", dress='" + dress + '\'' +
                ", suits=" + suits +
                '}';
    }
}

추상화된 빌더 인터페이스를 구현하는 클래스이다. 각 메소드마다 Builder 인터페이스를 구현하는 구현체를 반환 해야하기 때문에 this를 리턴하고 마지막에 모든 값들을 반환하는 Product를 리턴한다.

 

 

- Client

package me.whiteship.designpatterns._01_creational_patterns._04_builder.practice;

import java.time.LocalDate;

public class BuilderApp {
    public static void main(String[] args) {
        PhotographBuilder photographBuilder = new DefaultPhotographBuilder();
        Photograph firstPhotograph = photographBuilder
                .title("wedding photos")
                .startDate(LocalDate.of(2022, 8, 20))
                .place("the studio")
                .choiceDress("White flowing dress")
                .addSuit("beige", "basic")
                .addSuit("navy", "stripe")
                .getPlan();
        System.out.println("firstPhotograph = " + firstPhotograph);

        Photograph secondPhotograph = photographBuilder
                .title("wedding photos")
                .startDate(LocalDate.of(2022, 8, 20))
                .place("the outside")
                .choiceDress("Pink flowing dress")
                .addSuit("brown", "basic")
                .getPlan();
        System.out.println("secondPhotograph = " + secondPhotograph);

        Photograph thirdPhotograph = photographBuilder
                .title("wedding photos")
                .startDate(LocalDate.of(2022, 8, 20))
                .place("the outside")
                .choiceDress("White slim dress")
                .addSuit("charcoal", "basic")
                .getPlan();
        System.out.println("thirdPhotograph = " + thirdPhotograph);

    }
}

빌더 패턴을 적용하고 인스턴스를 적용했다. 기존에 setter나 생성자를 사용하는 방식은 장황하게 만들어지는 데이터를 입력하는 부분에 수 많은 null값이나 기본 값을 넣었어야 한다. 빌더 패턴은 사용하고자 하는 부분만 입력하고 반환하면 된다.

 

 

- Director

package me.whiteship.designpatterns._01_creational_patterns._04_builder.practice;

import java.time.LocalDate;

public class PhotographDirector {

    private PhotographBuilder photographBuilder;

    public PhotographDirector(PhotographBuilder photographBuilder) {
        this.photographBuilder = photographBuilder;
    }

    public Photograph firstPhotograph() {
        return this.photographBuilder
                .title("wedding photos")
                .startDate(LocalDate.of(2022, 8, 20))
                .place("the studio")
                .choiceDress("White flowing dress")
                .addSuit("beige", "basic")
                .addSuit("navy", "stripe")
                .getPlan();
    }

    public Photograph secondPhotograph() {
        return this.photographBuilder
                .title("wedding photos")
                .startDate(LocalDate.of(2022, 8, 20))
                .place("the outside")
                .choiceDress("Pink flowing dress")
                .addSuit("brown", "basic")
                .getPlan();
    }

    public Photograph thirdPhotograph() {
        return this.photographBuilder
                .title("wedding photos")
                .startDate(LocalDate.of(2022, 8, 20))
                .place("the outside")
                .choiceDress("White slim dress")
                .addSuit("charcoal", "basic")
                .getPlan();
    }
}

또한 빌더 패턴에서 만들어지는 프로세스 등이 자주 반복이 된다면 미리 만들어져있는 부분을 Director에 넣어놓고 재사용할 수 있다.

 

 

- Client

package me.whiteship.designpatterns._01_creational_patterns._04_builder.practice;

public class DirectorApp {
    public static void main(String[] args) {
        PhotographDirector photographDirector = new PhotographDirector(new DefaultPhotographBuilder());
        Photograph FirstPhotograph = photographDirector.firstPhotograph();
        System.out.println("FirstPhotograph = " + FirstPhotograph);
        Photograph secondPhotograph = photographDirector.secondPhotograph();
        System.out.println("secondPhotograph = " + secondPhotograph);
        Photograph thirdPhotograph = photographDirector.thirdPhotograph();
        System.out.println("thirdPhotograph = " + thirdPhotograph);
    }
}

Client는 Builder가 아닌 Director를 통해서 자주 사용되거나 반복되는 Photograph에 관한 인스턴스를 구현 할 수 있다.

 

 

 

- 장점

  • 만들기 복잡한 객체를 순차적으로 만들 수 있다.
  • 복잡한 객체를 만드는 구체적인 과정을 숨길 수 있다.
  • 동일한 프로세스를 통해 각기 다르게 구성된 객체를 만들 수도 있다.
  • 불완전한 객체를 사용하지 못하도록 방지할 수 있다.

- 단점

  • 원하는 객체를 만들려면 빌더부터 만들어야 한다.
  • 구조가 복잡해 진다. (트레이드 오프)

 

빌더 패턴은 만드는 순서가 복잡한 인스턴스 같은 경우는 빌더 패턴의 메소드를 사용해야 하는 순서를 통해서만 만들어지도록 사용하는 방식을 강제할 수도 있다.

생성자 하나만으로 사용하게 되어 복잡해질 수 있는 부분을 분산할 수도 있다. 예를 들어, 입력 받은 값들을 검증하는 코드 등을 추가하는 등의 작업을 할 수 있다.

클라이언트 코드가 깔끔하게 만들 수 있고 같은 프로세스에 하위클래스 등을 만들어서 추가적이고 세부적인 것들이 추가 될 수 있다. (VIP용 등)

getPlan()과 같은 빌더패턴을 종결하는 메소드를 호출하기 전까진 메소드를 사용할 수 없기 때문에 에러를 방지할 수 있다.

 

 

 

https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4

 

코딩으로 학습하는 GoF의 디자인 패턴 - 인프런 | 강의

디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할

www.inflearn.com

 

[디자인패턴] 생성 패턴 - 추상 팩토리(Abstract Factory) 패턴

 

 

 

*추상 팩토리 패턴

- 서로 관련있는 여러 객체를 만들어주는 인터페이스

  • 구체적으로 어떤 클래스의 인스턴스를(concrete product)를 사용하는지 감출 수 있다. (클라이언트에서 사용하는 인스턴스를 만들어 쓰는 코드를 인터페이스로 추상화한다)

- 구체적인 팩토리에서 구체적인 인스턴스를 만드는 부분은 팩토리 메소드 패턴과 비슷하지만 초점은 팩토리를 사용하는 클라이언트에 맞춰져 있다. 

 

 

 

 

- Product(Factory Method)

package me.whiteship.designpatterns._01_creational_patterns._02_factory_method.practice;

import lombok.Getter;
import lombok.Setter;
import me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice.Oxidizer;
import me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice.Tonic;

@Getter @Setter
public class HairDye {
    ...
    
    private DarkOxidizer oxidizer;
    private DarkTonic tonic;

    ...
}

기존의 구현체에 필요한 데이터 필드를 추가한다.

 

 

- ConcreateProductA, ConcreateProductB

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice;

public class DarkOxidizer{
}
package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice;

public class DarkTonic{
}

추가된 구현체에 대한 클래스를 만든다.

 

 

- ConcreteCreator(Factory Method)

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice;

import me.whiteship.designpatterns._01_creational_patterns._02_factory_method.practice.DarkHairDye;
import me.whiteship.designpatterns._01_creational_patterns._02_factory_method.practice.DefaultHairDesigner;
import me.whiteship.designpatterns._01_creational_patterns._02_factory_method.practice.HairDye;

public class DarkHairDesigner extends DefaultHairDesigner {
    @Override
    public HairDye dyingService() {
        HairDye hairDye = new DarkHairDye();
        hairDye.setOxidizer(new DarkOxidizer());
        hairDye.setTonic(new DarkTonic());
        return hairDye;
    }
}

팩토리(creator)에 구현체에 추가된 Oxidizer나 Tonic를 구현하기 위해서 현재는 new 키워드를 통해 직접적으로 구현하는데, 추상 팩토리 패턴으로 클라이언트 측에서 사용하는 구현체에 따라 인스턴스가 정해지도록 한다.

 

 

- Abstract Factory

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice;

public interface HairDyeProducts {

    Oxidizer useOxidizer();

    Tonic useTonic();
}

추상 팩토리 패턴으로 만들 인터페이스를 정의한다. 이 부분엔 추가되는 구현체(Product)를 정의한다.

 

 

- ProductA, ProductB

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice;

public interface Oxidizer {
}
package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice;

public interface Tonic {
}

추상 팩토리에서 사용될 구현체(Product)의 인터페이스를 정의한다.

 

 

- ConcreteProductA, ConcreteProductB

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice;

public class DarkOxidizer implements Oxidizer{
}
package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice;

public class DarkTonic implements Tonic{
}

인터페이스로 정의된 구현체를 구체적인 클래스를 정의한다.

 

 

- ConcreteFactory

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice;

public class DarkHairDyeProducts implements HairDyeProducts{
    @Override
    public Oxidizer useOxidizer() {
        return new DarkOxidizer();
    }

    @Override
    public Tonic useTonic() {
        return new DarkTonic();
    }
}

추상 팩토리를 구현할 구현 클래스를 정의한다.

 

 

- ConcreteCreator(Factory Method)

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice;

import me.whiteship.designpatterns._01_creational_patterns._02_factory_method.practice.DarkHairDye;
import me.whiteship.designpatterns._01_creational_patterns._02_factory_method.practice.DefaultHairDesigner;
import me.whiteship.designpatterns._01_creational_patterns._02_factory_method.practice.HairDye;

public class DarkHairDesigner extends DefaultHairDesigner {

    private HairDyeProducts hairDyeProducts;

    public DarkHairDesigner(HairDyeProducts hairDyeProducts) {
        this.hairDyeProducts = hairDyeProducts;
    }

    @Override
    public HairDye dyingService() {
        HairDye hairDye = new DarkHairDye();
        hairDye.setOxidizer(hairDyeProducts.useOxidizer());
        hairDye.setTonic(hairDyeProducts.useTonic());
        return hairDye;
    }
}

기존 DarkHariDesigner에서 Oxidizer나 Tonic을 정의 하려면 new 인스턴스를 사용하기 때문에 의존성이 강하게 결합되어 있었지만, HairDyeProducts 인터페이스를 통해서 의존성의 결합을 느슨하게 변경했다.

 

 

추가적으로 형태는 같지만 다른 기능을 하는 클래스를 추가하는 경우,

 

- ConcreteProductC, - ConcreteProductD

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice;

public class DarkExOxidizer implements Oxidizer{
}
package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice;

public class DarkExTonic implements Tonic{
}

다른 구현체(Product)를 추가한다.

 

 

- ConcreteFactory

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice;

public class DarkHairDyeExProducts implements HairDyeProducts{
    @Override
    public Oxidizer useOxidizer() {
        return new DarkExOxidizer();
    }

    @Override
    public Tonic useTonic() {
        return new DarkExTonic();
    }
}

추상 팩토리를 구현하는 또 다른 구현체를 정의한다.

 

 

- Product(Factory Method)

package me.whiteship.designpatterns._01_creational_patterns._02_factory_method.practice;

import lombok.Getter;
import lombok.Setter;
import me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice.Oxidizer;
import me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice.Tonic;

@Getter @Setter
public class HairDye {
    ...
    
    private Oxidizer oxidizer;
    private Tonic tonic;

    ...
}

기존에 DarkOxidizer, DarkTonic 등의 클래스 타입으로 되어있는 부분을 느슨한 결합을 위해 인터페이스 타입으로 수정한다.

 

 

- Client

package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice;

import me.whiteship.designpatterns._01_creational_patterns._02_factory_method.practice.HairDesigner;
import me.whiteship.designpatterns._01_creational_patterns._02_factory_method.practice.HairDye;

public class Client {
    public static void main(String[] args) {
        HairDesigner hairDesigner = new DarkHairDesigner(new DarkHairDyeProducts());
        HairDesigner hairDesigner = new DarkHairDesigner(new DarkHairDyeExProducts());

        HairDye hairDye = hairDesigner.dyingService();
        System.out.println(hairDye.getOxidizer().getClass());
        System.out.println(hairDye.getTonic().getClass());
        
//      class me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice.DarkOxidizer
//      class me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice.DarkTonic

//      class me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice.DarkExOxidizer
//      class me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory.practice.DarkExTonic        
    }
}

클라이언트에서 구조는 같지만 비슷한 형태의 인스턴스를 변경하기만 하면 다른 결과 값을 얻을 수 있다.

 

 

 

 

팩토리 메소드 패턴과 추상 팩토리 패턴은 비슷하다. 이 두가지는 관점의 차이이다. 팩토리 메소드 패턴은 객체 또는 인스턴스를 만드는(구현하는) 과정에 관점이 집중이 되어있고, 추상 팩토리 패턴은 팩토리를 사용하는 쪽(클라이언트)의 관점에 집중하여 보고있다.

  • 팩토리 메소드 패턴: 구체적인 객체 생성 과정을 하위 또는 구체적인 클래스로 옮기는게 목적
  • 추상 팩토리 패턴: 관련있는 여러 객체를 구체적인 클래스에 의존하지 않고 만들 수 있게 하는것이 목적

 

 

 

https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4

 

코딩으로 학습하는 GoF의 디자인 패턴 - 인프런 | 강의

디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할

www.inflearn.com

+ Recent posts