[디자인패턴] 생성 패턴 - 프로토타입(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

[디자인패턴] 생성 패턴 - 팩토리 메소드(Factory method) 패턴

 

 

 

*팩토리 메소드 패턴

- 구체적으로 어떤 인스턴스를 만들지 서브 클래스에서 정하게 한다.

  • 다양한 구현체(Product)가 있고, 그 중에서 특정한 구현체를 만들 수 있는 다양한 팩토리(Creator)를 제공할 수 있다.

- 확장엔 열려있고 변경엔 닫혀있는 OCP(Open Closed Principle)에 부합한다. 

 

 

 

 

 

예를 들어

 

HairDesigner(Creator)와 HairDye(Product)로 팩토리 메소드 패턴을 구현한다면

 

 

- Product

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

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class HairDye {
    private String colorNameKr;
    private String colorNameEn;
    private String colorCode;

    @Override
    public String toString() {
        return "사용된 염색약은 " +
                colorNameKr +
                "(" + colorNameEn + "), " +
                "code: " + colorCode +
                " 입니다";
    }
}

팩토리 메소드 패턴에서 Product, 팩토리에서 만들어 낼 데이터 오브젝트이다.

 

 

- concreteProductor

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

public class DarkHairDye extends HairDye{
    public DarkHairDye() {
        setColorCode("2N");
        setColorNameEn("Dark");
        setColorNameKr("흑색");
    }
}
package me.whiteship.designpatterns._01_creational_patterns._02_factory_method.practice;

public class BrownHairDye extends HairDye{
    public BrownHairDye() {
        setColorCode("5N");
        setColorNameEn("Brown");
        setColorNameKr("자연갈색");
    }
}

Product의 규격은 같으면서 다른 값들의 오브젝트를 구체화한다.

 

 

- Creator

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

public interface HairDesigner {
    default HairDye dying(String color, int time) {
        validate(color);
        selectedDesigner();
        HairDye hairDye = dyingService();
        spreadHairDye(color);
        waitFor(time);
        return hairDye;
    }

    void selectedDesigner();

    HairDye dyingService();

    private void validate(String color) {
        if (color == null || color.isBlank()) {
            throw new IllegalArgumentException("색상이 선택되지 않았습니다");
        }
    }

    private void spreadHairDye(String color) {
        System.out.println(color + " 염색약을 바릅니다");
    }

    private void waitFor(int time) {
        System.out.println(time + "분간 기다립니다");
    }
}
package me.whiteship.designpatterns._01_creational_patterns._02_factory_method.practice;

public abstract class DefaultHairDesigner implements HairDesigner {
    public void selectedDesigner() {
        System.out.println("일반 디자이너 선생님이 작업합니다.");
    }
}

Product를 사용할 팩토리(Creator)의 기본적인 규격을 정해놓은 추상화된 객체이다. DefaultHairDesigner 같은 추상 클래스의 경우는 사용해도 되고 사용하지 않아도 된다. 추가적으로 디자이너를 선택할 수 있는 기능이 추가되면 파라미터로 변경할 수 있게 만들어도 된다. java 9 버전부터 interface에서 private 제한자를 사용할 수 있지만, 하위 버전에선 사용할 수 없다. 

 

 

- ConcreteCreator

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

public class DarkHairDesigner extends DefaultHairDesigner {
    @Override
    public HairDye dyingService() {
        return new DarkHairDye();
    }
    
}
package me.whiteship.designpatterns._01_creational_patterns._02_factory_method.practice;

public class BrownHairDesigner extends DefaultHairDesigner {
    @Override
    public HairDye dyingService() {
        return new BrownHairDye();
    }
}

팩토리(Creator)에서 추상화된 구현체를 구체화 한다. 각각의 클래스가 정해진 기능만을 수행할 수 있도록 한다.

 

 

- Client

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

public class Client {
    public static void main(String[] args) {
        Client client = new Client();
        client.print(new DarkHairDesigner(), "검정색", 40);
        client.print(new BrownHairDesigner(), "갈색", 30);
    }
//        일반 디자이너 선생님이 작업합니다.
//        검정색 염색약을 바릅니다
//        40분간 기다립니다
//        사용된 염색약은 흑색(Dark), code: 2N 입니다

//        일반 디자이너 선생님이 작업합니다.
//        갈색 염색약을 바릅니다
//        30분간 기다립니다
//        사용된 염색약은 자연갈색(Brown), code: 5N 입니다

    private void print(HairDesigner hairDesigner, String color, int time) {
        System.out.println(hairDesigner.dying(color, time));
    }
}

사용자(Client)가 팩토리 메소드 패턴을 통해서 동일한 규격의 다른 데이터들을 구현할 수 있다.

 

 

 

 

팩토리 메소드 패턴은 팩토리(Creator)가 특정한 구현체(Product)를 다양한 방법으로 구현할 수 있다. 또한 기존의 팩토리(DarkHairDesigner, BrownHairDesigner) 뿐만 아니라 추가적인 팩토리(ex. YellowHairDesigner)를 만들 수 있으며(Open) 추가 하더라도 기존의 코드를 변경 또는 수정하지 않을 수 있다.(Closed)

하지만 단점으로, 기능을 계속 추가하게 되면 각자의 역할이 세분화 되어있기 때문에 클래스가 늘어나는 부분이 생긴다.

 

 

 

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

 

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

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

www.inflearn.com

[디자인패턴] 생성 패턴 - 싱글톤(Singleton) 패턴

 

 

*싱글톤 패턴

- 인스턴스를 오직 한개만 제공하는 클래스

  • 인스턴스가 여러개 있을때 문제가 발생할 수 있는 경우를 대비해 한개의 인스턴스만 제공하는 클래스가 필요하다.

 

 

- 기본적인 싱글톤패턴

public class Singleton {
  private static Singleton instance;
  
  private Singleton() {}
  
  public static Singleton getInstance() {
    if(instance == null){
      instance = new Singleton();
    }
    
    return instance;
  }
}
public class App {
 public static void main(String[] arg){
   Singleton singleton = Singleton.getInstance;
   System.out.println(singleton == Singleton.getInstance); // true
 }
}

위와 같은 경우는 하나의 싱글톤 인스턴스를 반환하지만 문제가 있다. 웹 애플리케이션 같은 멀티스레드 환경에선 이 코드가 안전하지 않다.(Thread-Safe 하지 않다)

 

멀티스레드 환경에서 안전하려면 다음과 같은 방법으로 구현할 수 있다.

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

synchronized 키워드를 사용하게되면 여러 스레드에서 getInstance 메소드에 동시에 접근할 때, Thread-Safe하게 접근할 수 있다. 멀티 스레드 환경에서 synchroinzed 키워드를 만났을 때, 어떤 스레드가 해당 부분에 진입한 상태라면 다른 스레드는 그 스레드의 작업을 끝날 때까지 대기한다. 하지만 synchronized는 내부적으로 동기화라는 매커니즘으로 locking 작업을 하기때문에 성능적은 불이익이 생길 수 있다.

 

 

 

그래서 성능을 조금 더 신경쓰고 싶다면 다른 방법을 사용할 수도 있다.

public class Singleton {
  private static final Singleton INSTANCE = new Singleton();
  
  private Singleton() {}
  
  public static Singleton getInstance() { 
    return INSTANCE;
  }
}

synchronized를 사용하지 않고 인스턴스를 먼저 생성한다. 이른 초기화라고 하는데 생성자를 먼저 생성한 후에 getInstance 메서드는 생성된 인스턴스만을 반환한다. Thread-Safe하며 synchronized 동기화에 관한 성능이슈도 없지만 미리 만드는것 자체가 이슈가 될 수 있다.

생성하는 인스턴스 자체로 메모리에 할당을 하게 되는데 만약 미리 만들어졌지만 애플리케이션 내에서 사용하지 않는다면 ? 이것 또한 자원의 낭비가 될 것이다.

 

 

그래서 또 다른 방법이 있다.

public class Singleton {
  private static volatile Singleton instance; // volatile java 1.5 버전 이상부터 사용
  
  private Singleton() {}
  
  public static Singleton getInstance() {
    if(instance == null) {
      synchronized (Singleton.class) {
        if(instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance;
  }
}

synchronized 키워드를 사용하긴 하지만 메서드 레벨에서 사용하는 것이 아닌, 메서드 안의 로직에서 사용한다. double checked locking 이라는 기법으로 if 문을 통해서 2번의 체크하는 로직을 가진다.

여러 스레드가 동시에 getInstance 메서드에 진입하더라도 if문에서 한번 필터링을 하고(동기화 매커니즘까지 가지 않음) synchronized 블럭 안에서 한번 더 if로 필더링을 하기 때문에, 메서드에서 synchronized를 사용한 경우처럼 메서드를 호출할 때마다 동기화 locking 작업을 하지 않는다.

하지만, 이 방법은 복잡하다. 필드에 선언된 volatile을 사용한 이유는 자바 1.5버전 이하(자바 1.4부터)의 메모리처리 방법을 이해 해야한다.

 

 

따라서 또 다른 방법이 있다.

public class Singleton {
  private Singleton() {}
  
  private static class SingletonHolder {
    private static final Singleton INSTANCE = new Singleton();
  }
  
  public static Singleton getInstance() {
    return SingletonHolder.INSTANCE;
  }
}

이 방법은 권장하는 방법중 하나이다. 싱글톤을 static inner 클래스를 만들어서 구현하는 방법이다.

이 방법은

1) Thread-safe하며

2) double check locking처럼 복잡하지도 않고

3) getInstance를 호출할 때 클래스 로딩을 통해서 인스턴스를 생성하기 때문에 lazy loading도 가능하다.

 

 


 

 

이러한 방법들은 싱글톤을 보장하며 멀티스레드 환경에서 적절한지, 성능상으로도 괜찮은지, 버전에 따른 복잡한 절차가 들어 있는지에 대한 문제에 대해 자유로울 수 있다.

하지만, 이러한 싱글톤의 구현을 깨뜨리는 방법 또한 존재한다. 사용자가 정해진 방법이 아닌 다른 방법으로 사용한다면 이러한 싱글톤을 보장하지 않을수도 있다.

  • 리플렉션
  • 직렬화와 역직렬화

이 두가지의 방법을 이용하면 위의 싱글톤을 깨뜨린다.

 

 

- 리플렉션

public class App {
  public static void main(String[] args) {
    Singleton singleton = Singleton.getInstance();
    
    Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
    constructor.setAccessable(true); // private 접근
    Singleton singleton1 = constructor.newInstance();
    
    System.out.println(singleton == singleton1); // false
  }
}

우리가 의도한대로 만든 싱글톤과 리플렉션을 사용하여 클래스에 접근하여 만들어낸 생성자는 다른 인스턴스를 만들어낸다.

 

 

- 직렬화 & 역직렬화

public class Singleton implements Serializable {
  private Singleton() {}
  
  private static class SingletonHolder {
    private static final Singleton INSTANCE = new Singleton();
  }
  
  public static Singleton getInstance() {
    return SingletonHolder.INSTANCE;
  }
}
public class App {
  public static void main(String[] args) throws IOException {
    Singleton singleton = Singleton.getInstance();
    Singleton singleton1 = null;
    
    try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("singleton.obj"))) {
      output.writeObject(singleton); // 직렬화
    }
    
    try (ObjectInput in = new ObjectInputStream(new FileInputStream("singleton.obj"))) {
      singleton1 = (Singleton) in.readObject(); // 역직렬화
    }
    
    System.out.println(singleton == singleton1); // false
  }
}

리플렉션과 마찬가지로 직렬화 & 역직렬화를 통해 만든 객체 인스턴스는 싱글톤으로 만들어낸 인스턴스와 다른 객체를 참조한다.

 

* 역직렬화 대응방안

public class Singleton implements Serializable {
  private Singleton() {}
  
  private static class SingletonHolder {
    private static final Singleton INSTANCE = new Singleton();
  }
  
  public static Singleton getInstance() {
    return SingletonHolder.INSTANCE;
  }
  
  protected Object readResolve() {
    return getInstance();
  }
}

명시적으로 readResolve 메서드가 있는건 아니지만 이 메서드는 역직렬화 될때 사용된다. 그래서 readResolve 메서드의 구현을 getInstance 메서드를 반환하게 바꿔주면 동일한 인스턴스를 얻을 수 있다.

 

 

 

권장하는 방법중 또 하나는 enum 타입으로 구현하는 방법이 있다.

public enum Singleton {
  INSTANCE;
}

enum 타입으로 싱글톤을 구현하게 되면 class로 구현하게 되어 생기는 리플렉션으로 싱글톤이 깨지는 현상에 대응 할수 있다. 

 

public class App {
  public static void main(String[] args) {
    Singleton singleton = Singleton.INSTANCE;
    Singleton singleton1 = null;
    
    Constructor<?>[] constructors = Singleton.class.getDeclaredConstructors();
    for (Constructor<?> constructor: constructors) {
      constructor.setAccessable(true);
      singleton1 = (singleton) constuctor.newInstance("INSTANCE"); // cannot reflectively create enum objects
    }
    
    System.out.println(singleton == singleton1);
  }
}

enum은 리플렉션에서 new를 통한 생성자를 만들수 없다. 따라서 유일한 인스턴스가 보장이 된다. enum은 역직렬화나 리플렉션과 같은 이탈행위를 방지할 수 있다. 하지만 단점은 enum은 클래스를 로딩하는 순간 미리 인스턴스가 만들어진다. 또한 enum은 오로지 enum만 상속 받을 수 있기때문에 다른 class를 상속 받을 수 없다.

 

 

 

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

 

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

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

www.inflearn.com

[JAVA] JAVA, JVM, JDK, JRE

 

 

 

- JVM(Java Virture Machine)

자바 가상 머신으로 자바 바이트코드(.class) 파일을 해당하는 운영체제(OS)에 맞는 코드로 변환하여 실행한다.(컴파일)

 

- JRE(Java Runtime Environment)

JVM + 라이브러리이며, 자바를 실행 할 수 있도록 구성된 배포판이다. 자바 런타임 환경에서 사용하는 리소스 파일 등을 가지고 있다. (11버전부터 따로 제공되지 않음)

 

- JDK(Java Development kit)

JRE + 개발 툴이며, JRE에서 추가적으로 개발에 관련된 도구들을 포함한다. Java를 사용하는 개발자들은 JDK를 설치하여 개발한다.

 

 


*JVM의 구조

  • 클래스 로더
  • 메모리
  • 실행 엔진
  • 네이티브 메소드 인터페이스(JNI)
  • 네이티브 메소드 라이브러리

 

 

1) 클래스 로더

자바 클래스를 자바 가상 머신으로 동적 로드하는 자바 런타임 환경의 일부이다. 로딩, 링크, 초기화 등의 작업을 수행한다. 자바 클래스(.class)에서 바이트코드를 읽고 메모리에 저장한다. (메모리에 가기 전 바이트코드를 조작할 수 있다)

 

  • 로딩: 클래스로더가 바이트코드(.class)를 읽고 그 내용에 따라 적절한 바이너리 데이터를 만들어 "메소드" 영역에 저장한다. 저장되는 데이터는 클래스, 인터페이스, 이늄, 클래스의 전체경로, 메소드, 변수 등이 있다. 로딩을 마치고나면 해당 클래스 타입의 Class 객체를 힙 영역에 저장한다.
  • 링크: 바이트코드(.class)를 검증, 준비, resolve 등의 작업을 거친다. 바이트코드(.class) 파일의 형식이 유효한지 체크하고 클래스 변수(static 변수)와 기본값에 필요한 메모리 등을 준비하고 심볼릭 메모리 레퍼런스를 메소드 영역에 있는 실제 레퍼런스로 교체한다.
  • 초기화: Static 변수의 값을 할당한다. (static 변수나 블럭을 이때 실행한다.)

 

 

2) 메모리

  • 스택: 쓰레드마다 런타임 스택을 만들고, 그 안에 메소드 호출을 스택으로 쌓는다. 쓰레드 종료시 런타임 스택도 사라진다.
  • PC: 쓰레드마다 쓰레드 내 현재 실행할 스택 프레임을 가리키는 포인터를 생성한다.
  • 네이티브 메소드 스택: 네이티브 메소드가 실행될 때, 스택을 만들고 네이티브 메소드 호출을 스택으로 쌓는다. 쓰레드에 따라 동작한다.
  • 힙: 객체를 저장 공유하는 자원이다.
  • 메소드: 클래스에 수준의 정보(클래스 이름, 메소드, 변수 등)를 저장 공유하는 자원이다.

 

 

3) 실행 엔진

  • 인터프리터: 바이트코드를 한줄씩 실행한다.
  • JIT 컴파일러: 인터프리터에서 바이트코드를 한줄씩 실행하면서 반복되는 바이트코드를 발견하면 JIT컴파일러로 반복되는 코드를 네이티브 코드로 바꿔두고 사용한다.(성능적 이점)
  • GC(Garbage Collector): 더 이상 참조되지 않는 객체를 모아서 정리한다.

 

 

4) JNI(Java Navtive Interface)

네이티브 인터페이스로 네이티브 메소드 라이브러리에 있는, 또는 native 키워드가 사용된 함수를 사용할 수 있는 방법을 제공한다. 

 

 

5) 네이티브 메소드 라이브러리

C, C++로 작성된 라이브러리이며 Java에선 native 키워드로 사용된 메소드로 되어있다.

 

 

 

 

[기타] 반조애(청담본점), 웨딩밴드 맞추기

 

 

 

이번에 반조애에서 웨딩밴드를 맞췄습니다 💫💍

 

반조애 건물입구(벨 눌러야 함)

 


저희는 인스타로 찾아보다가 처음부터 청담에 있는 브랜드에서 하자고 마음을 먹었고,

원래 가려던 곳은 마노다이아였어요. 그래서 하려던 반지까지 정해놨었는데 ! 

 


주변 사람들한테 물어봤을때도 반조애가 유명하기도하고 

제휴 업체인 베리굿웨딩에서 반조애를 추천해줘서 

한번 가볼까 ? 하는 마음에 가봤습니다 ㅎㅎ

 

 


저희가 생각했던것보다 반지나 디자인에 대해 설명도 잘 해주시고 

4계절 방식으로 나눠서 퍼스널컬러도 맞춰서 피부톤과 맞는 색상 추천도 해주시더라구요 !

(신기)

 


여러가지 반지를 고민한 끝에 저희가 선택한 반지는 '루미에르' 입니다 !! 

저희는 각각 화이트골드/5mm, 핑크골드/3mm로 진행했어요~~

 

 


이 반지에 있는 블랙 다이아는 타 브랜드에선 보기 어려운 디자인으로 되어 있었는데요 

처음에 이 디자인에 꽂혀서 다른건 눈에 안 들어오더라구요~

 

그날 다른 업체에도 들러서 다른 반지도 봤는데

다른 반지보면서도 성에 안차서 결국 여기로 다시 돌아왔습니다 ㅋㅋㅋ큐ㅠ

 

핑크골드 반지에 장식되어있는 다이아도 너무 예쁘네요 ✨

 

 

 

이건 웨딩밴드 구매하면서 추가로 구매한 반지입니다.

레이어드 반지를 사려다가 처음 들어오면서 봤던 물건인데 예뻐서 같이 했네요 💍

다이아 주변에 다이아로 장식되어 있어서 더 예쁜거 같습니다

반지 고르는 것도 어렵네요ㅠㅠ

 

 

확실히 반지 맞추고나니 결혼에 한 발짝 더 다가간 느낌이 듭니다..!  

빨리 받았으면 좋겠네요 👍👍👍

 

 

https://naver.me/Fn2mKIJx

 

반조애 청담본점 : 네이버

방문자리뷰 357 · 블로그리뷰 837

m.place.naver.com

 

[JavaScript] 함수형 프로그래밍 (go, pipe, curry)

 

 

 

- 데이터

const products = [
  { name: '반팔티', price: 15000 },
  { name: '긴팔티', price: 20000 },
  { name: '핸드폰케이스', price: 15000 },
  { name: '후드티', price: 30000 },
  { name: '바지', price: 25000 }
]

 

 

 

# go

const go = (...args) => reduce((f, a) => f(a), args);

go(
   0, 
   a => a + 1, 
   a => a + 10, 
   a => a + 100, 
   console.log
  );

go() 함수는 함수들과 인자를 전달해서 즉시 어떤 값을 평가한다. go() 함수는 5가지의 인자를 함수를 받는다. go의 인자는 rest 파라미터를 이용하여 여러가지 인수를 배열로 받는다. rest 파라미터는 배열이고 이터러블 프로토콜을 만족한다. 차례대로 함수를 실행하여 값을 도출하기 위해 reduce() 함수를 사용하여 축약된 값을 반환하고 마지막은 console.log 함수를 통해 결과를 로그로 출력한다.

 

- go 함수 이용하기

go(
   products,
   products => filter(p => p.price < 20000, products),
   products => map(p => p.price, products),
   prices => reduce(add, prices),
   console.log
  ); // 30000

기존의 map, filter, reduce 함수를 사용했을 때는 아래에서 위, 오른쪽에서 왼쪽으로 읽어야하는 느낌이 강했지만 go 함수를 사용하게되면 차례대로 위에서 아래, 왼쪽에서 오른쪽으로, 연속적으로 평가할 수 있다.

 

 

# pipe

const go = (...args) => reduce((a, f) => f(a), args);
const pipe = (...fs) => (a) => go(a, ...fs);

const f = pipe(
               a => a + 1, 
               a => a + 10,
               a => a + 100
              );
  
console.log(f(0));

pipe 함수는 함수들이 나열된 합성된 함수를 리턴하는 함수이다. pipe 함수는 여러개의 함수들을 인자로 받아 연속적으로 실행하여 축약하는 하나의 함수를 만들어 리턴한다. pipe 함수의 인자로 여러가지 함수(fs)를 받고 함수를 시작하는데 쓰는 인자(a)를 나중에 받고, 받은 인자를 가지고 go()함수를 실행하면서 인자(a)와 함수(fs)를 전달한다.

 

- go, pipe 변형

const go = (...args) => reduce((a, f) => f(a), args);
const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs);

go(
   add(0, 1),
   a => a + 10,
   a => a + 100,
   console.log
  ); // 111

const f = pipe(
               (a, b) => a + b, 
               a => a + 10,
               a => a + 100
              );
  
console.log(f(0, 1)); // 111
// console.log(f(add(0, 1))); // pipe() 함수의 이점이 사라짐

만약 인자를 2가지 이상을 받아서 평가된 값으로 함수를 진행시킨다면 go() 함수의 경우는 2개 이상의 인자를 전달 받고 평가하는 함수를 지정하면 된다. 하지만 pipe() 함수는 f(add(0, 1))의 형식으로 인자를 전달해야 하는데 이 방식은 pipe() 함수로 사용하기에 다소 아쉽다. 그렇기 때문에 pipe() 함수에서 인자를 전달 받는 방식을 변경한다. pipe() 함수의 첫번째 인자로 들어갈 함수(f)는 따로 지정하고 리턴할 함수의 인자(a)를 여러개를 받아야하기 때문에 (...as)로 받는다. go() 함수의 인자로 전달될 부분도 마찬가지로 첫번째 인자와 나머지 전달될 인자를 구분한다.

 

 

# curry

const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);

const mult = curry((a, b) => a * b);
console.log(mult(3)); // (..._) => f(a, ..._);
console.log(mult(3)(2)); // 6

const mult3 = mult(3);
console.log(mult3(10)); // 30
console.log(mult3(5)); // 15
console.log(mult3(3)); // 9

curry는 함수를 값으로 다루면서 받아둔 함수를 원하는 시점에 평가시키는 함수이다. 먼저 함수를 받아서 함수를 리턴하고 인자를 받아서 인자가 원하는 갯수만큼 일때 받아두었던 함수를 평가시킨다. mult() 함수는 두개의 인자를 받고 곱하는 함수이다. 이 함수를 curry() 함수로 호출하면 두번째 인자까지 받았다는 조건(_.length ?)이 만족되면 실행(f(a, ..._)하고 첫번째 인자까지 받았다면 함수((..._) => f(a, ..._))를 반환한다.

 

- go와 curry의 조합

const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);

const map = curry((f, iter) => {
  let res = [];
  for (const a of iter) {
    names.push(f(a));
  }
  return res;
});

const filter = curry((f, iter) => {
  let res = [];
  for(const a of iter){
    if (f(a)) res.push(a);
  }
  return res;
});

const reduce = curry((f, acc, iter) => {
  if(!iter){
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  }
  for(const a of iter){
    acc = f(acc, a)
  }
  return acc;
});


// go(
//    products,
//   products => filter(p => p.price < 20000)(products),
//    products => map(p => p.price)(products),
//    prices => reduce(add)(prices),
//    console.log
//   ); // 30000  
  
go(
   products,
   filter(p => p.price < 20000),
   map(p => p.price),
   reduce(add),
   console.log
  ); // 30000

기존에 만들었던 map, filter, reduce에도 curry를 적용했다. 하여 이 세 함수가 인자를 하나만 받으면 이후 인자를 더 받기로 기다리는 함수를 리턴하도록 되어있다. go() 함수를 살펴보면 기존에 함수(filter, map, reduce)를 첫번째 인자로 받고 두번째 인자에 데이터(products)를 받았는데, curry() 함수를 통해서 (map or filter or reduce)(products or price) 형식으로 첫번째 인자를 먼저 실행하면서 두번째 인자를 이후에 실행할 수 있다. 

또한, products를 받아서 함수(map or filter or reduce)에 그대로 products를 전달한다는 것은 해당하는 함수가 products를 받는다는 이야기고 products => (map or filter or reduce())(product) 함수가 products를 받는다는 얘기는 products를 제외하고 map or filter or reduce() 만으로 사용할 수 있다.

 

 

# 함수 조합으로 함수 만들기

const total_price = pipe(
  map(p => p.price),
  reduce(add));
)

const base_total_price = predi => pipe(
  filter(predi);
  total_price;
)

go(
   product,
   base_total_price(p => p.price < 20000),
   console.log
  ); // 30000
  
go(
   product,
   base_total_price(p => p.price >= 20000),
   console.log
  ); // 75000

 

total_price라는 pipe 함수를 만들어서 가격의 축약된 값을 도출하는 함수를 만들어 중복을 제거한다. 또, base_total_price라는 predi 함수를 전달 받는 pipe 함수를 만든다. 이 함수의 경우는 predi함수로 전달받은 함수를 filter 함수의 인자로 pipe를 리턴하는 함수이다. 이러한 방식들로 고차함수들을 통해 함수의 조합으로 만들어가면서 함수를 잘게 나누면서 중복을 제거하며 더 많은 곳에서 사용할 수 있다.

 

 

+ Recent posts