본문 바로가기
쿤즈 Dev/Design pattern

[Design Pattern] 프로토타입: 객체 생성의 효율적인 복제

by Koonz:) 2024. 8. 2.
728x90

소프트웨어 개발에서 동일한 객체를 반복적으로 생성해야 할 때가 있습니다. 특히, 객체 생성비용이 높거나 복잡한 설정이 필요한 경우, 객체를 복제하여 효율적으로 생성할 수 있는 방법이 필요합니다.

 

이번 글에서는 이러한 상황에서 유용한 프로토타입 패턴의 개념과 필요성, 그리고 이를 자바로 구현하는 방법을 알아볼게요.


🚀 프로토타입 패턴 Prototype Pattern

프로토타입 패턴(Prototype Pattern)은 객체를 직접 생성하는 대신, 이미 존재하는 객체를 복제(clone)하여 새로운 객체를 생성하는 디자인 패턴입니다.

 

이는 객체 생성 비용이 높거나, 객체 초기화가 복잡한 경우 유용하게 사용됩니다. 프로토타입 패턴을 사용하면 객체의 복제를 통해 효율적으로 새로운 객체를 생성할 수 있습니다.


🚀 프로토타입 패턴의 필요성

프로토타입 패턴은 다음과 같은 경우 필요합니다.

 

객체 생성 비용 절감

객체 생성 비용이 높은 경우, 새로운 객체를 직접 생성하는 대신 기존 객체를 복제하여 생성 비용을 절감할 수 있습니다.

 

복잡한 초기화 로직 재사용

객체 초기화가 복잡한 경우, 초기화 로직을 반복하지 않고 기존 객체를 복제하여 동일한 초기화를 가진 객체를 쉽게 생성할 수 있습니다.

 

객체의 독립성 유지

복제된 객체는 원본 객체와 독립적으로 동작하며, 각 객체의 상태를 별도로 관리할 수 있습니다.

 


🚀  프로토타입 패턴 적용하기

기존과 마찬가지로 커피 주문 시스템을 위한 프로토타입 패턴 예제를 만들어볼게요. 이 예제에서는 커피 객체를 복제하여 효율적으로 생성하는 방법을 보여줍니다.

 

프로토타입 패턴 적용 전

프로토타입 패턴을 적용하기 전 코드를 만들어 볼게요. 먼저 Coffee 클래스를 다음과 같이 만들어줍니다.

package com.koonsland.designpatterns.creational.prototype.example;

public class Coffee {
    private String type;
    private String bean;
    private String size;
    private boolean milk;
    private String syrup;
    private String temperature;

    public Coffee(String type, String bean, String size, boolean milk, String syrup, String temperature) {
        this.type = type;
        this.bean = bean;
        this.size = size;
        this.milk = milk;
        this.syrup = syrup;
        this.temperature = temperature;
    }

    public String getType() {
        return type;
    }

    public String getBean() {
        return bean;
    }

    public String getSize() {
        return size;
    }

    public boolean isMilk() {
        return milk;
    }

    public String getSyrup() {
        return syrup;
    }

    public String getTemperature() {
        return temperature;
    }

    @Override
    public String toString() {
        return "Coffee{" + "type='" + type + '\'' + ", bean='" + bean + '\'' + ", size='" + size + '\'' + ", milk=" + milk + ", sugar=" + sugar + ", syrup='" + syrup + '\'' + ", temperature='" + temperature + '\'' + '}';
    }
}

 

여러 가지 필드들을 만들어 주었고 모든 필드가 있는 생성자와 getter 메서드를 만들어 주었습니다. 또한 추가적으로 어떤 값이 들어있는지 확인하기 위해서 toString() 메서드도 만들어 주었습니다.

 

이를 테스트하는 클라이언트 프로그램을 만들어 볼게요.

package com.koonsland.designpatterns.creational.prototype.example;

public class Client {
    public static void main(String[] args) {
        Coffee coffee = new Coffee("아메리카노", "아라비카", "TALL", false, null, "HOT");
        System.out.println(coffee);

        coffee = new Coffee("아메리카노", "아라비카", "TALL", false, null, "ICED");
        System.out.println(coffee);
    }
}

 

커피를 만들기 위해서는 여러 가지 parameter들을 생성자 필드에 넣어주어야 합니다. 그리고 모든 값이 동일하고 온도만 다른 커피를 만들기 위해서도 모든 필드의 값을 다 넣어주어야 합니다.

 

만약 이보다 더 복잡한 인스턴스를 만들기 위해서는 더 어려움이 따를 것 같아요. 그럼 이제 프로토타입으로 만들어 보도록 할게요.

 

프로토타입 패턴 적용

프로토타입 패턴을 적용하기 위해서 복제라는 기능을 사용해야 합니다. 직접 구현할 수도 있지만 자바에는 이미 구현된 Cloneable 인터페이스가 있습니다. 이를 상속받아서 Coffee 클래스에서 복제 메서드를 사용해 볼게요.

package com.koonsland.designpatterns.creational.prototype.after;

public class Coffee implements Cloneable{
    private String type;
    private String bean;
    private String size;
    private boolean milk;
    private String syrup;
    private String temperature;

    public Coffee(String type, String bean, String size, boolean milk, String syrup, String temperature) {
        this.type = type;
        this.bean = bean;
        this.size = size;
        this.milk = milk;
        this.syrup = syrup;
        this.temperature = temperature;
    }

    public String getType() {
        return type;
    }

    public String getBean() {
        return bean;
    }

    public String getSize() {
        return size;
    }

    public boolean isMilk() {
        return milk;
    }

    public String getSyrup() {
        return syrup;
    }

    public String getTemperature() {
        return temperature;
    }

    @Override
    public String toString() {
        return "Coffee{" + "type='" + type + '\'' + ", bean='" + bean + '\'' + ", size='" + size + '\'' + ", milk=" + milk + ", syrup='" + syrup + '\'' + ", temperature='" + temperature + '\'' + '}';
    }

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

 

Coffee 클래스에 clone() 메서드를 구현하였습니다. 이미 복제기능은 만들어져 있기 때문에 상위 클래스의 복제 기능을 그대로 사용하기 위해서 super.clone() 메서드를 사용했습니다.

 

그럼 이제 클라이언트 코드를 만들어 볼게요.

package com.koonsland.designpatterns.creational.prototype.after;

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Coffee coffee = new Coffee("아메리카노", "아라비카", "TALL", false, null, "HOT");
        System.out.println(coffee);

        Coffee clone = (Coffee) coffee.clone();
        clone.setTemperature("ICED");
        System.out.println(clone);
    }
}

 

기존과 동일하게 Coffee 클래스의 인스턴스를 만들었습니다. 그리고 새로운 인스턴스를 만들 때에는 clone() 메서드를 이용해서 복제하였고, 여기에 setTemperature() 메서드를 만들어서 온도만 수정해 주었습니다. 결과 화면을 볼게요.

Coffee{type='아메리카노', bean='아라비카', size='TALL', milk=false, syrup='null', temperature='HOT'}
Coffee{type='아메리카노', bean='아라비카', size='TALL', milk=false, syrup='null', temperature='ICED'}

 

❓의문점: clone() 메서드는 어디에 있는가?

여기서 의문인 점이 하나 발생합니다. Cloneable 인터페이스를 보았더니 아무런 메서드가 존재하지 않습니다.

Cloneable 인터페이스

그렇다면 clone() 메서드는 어디에 있을까요?

Object 클래스의 clone 메서드

Object 클래스 내부에 있습니다. Cloneable 인터페이스를 상속받았기 때문에 당연히 Cloneable 인터페이스 내부에 메서드가 있을 것이라 생각할 수 있습니다. 그렇다면 Cloneable 인터페이스를 상속받지 않는다면 어떻게 될까요?

 

컴파일 상으로는 문제가 없습니다. 다만 런타임 과정에서 오류를 발생시킵니다.

finished with non-zero exit value 1

 

즉, Cloneable 인터페이스를 상속받는다는 의미는 다시 해석하면 'clone에 의해 복사할 수 있는 클래스다'라는 의미입니다. 이와 같은 인터페이스를 marker interface 라고 합니다.

Cloneable 인터페이스를 받은 LinkedList 클래스

 

clone() 메서드는 shallow copy(얕은 복사)

그렇다면 clone() 메서드는 어떤 방법으로 복사를 할까요? 복사 방법은 바로 얕은 복사입니다. Shallow Copy라고 부르며 이는 값 하나하나가 복사되는 것이 아닌 값에 대한 참조만 복사될 뿐입니다. 따라서 @Override 한 clone() 메서드를 재정의해서 자신이 필요한 복사를 정의해서 사용할 수도 있습니다.


🚀 결론

프로토타입 패턴은 객체를 직접 생성하는 대신 복제하여 새로운 객체를 생성할 수 있는 유용한 디자인 패턴입니다. 이를 통해 객체 생성 비용을 절감하고, 복잡한 초기화 로직을 재사용하며, 객체의 독립성을 유지할 수 있습니다. 스프링 부트와 함께 사용하면 DI를 활용하여 더욱 유연하게 객체를 관리할 수 있습니다.

 

이번 글에서는 커피 주문 시스템 예제를 통해 프로토타입 패턴의 개념과 구현 방법을 살펴보았습니다. 도움이 되시길 바랍니다. 감사합니다.

댓글