본문 바로가기

Software Development/디자인 패턴

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

개발자 yeah의 Developer Story - 디자인 패턴

 

오늘은 저번 포스팅에 이어서 프로토 타입 패턴 을 이야기해보려 합니다.

 

 

 

먼저 프로토 타입 패턴 설명에 앞서 객체 생성에 대해 간단히 설명하도록 하겠습니다.

 

 

객체를 생성하기 위해서는 먼저 클래스 선언이 필요하며, 선언된 클래스를 기반으로 객체를 생성합니다.

객체는 선언된 클래스 인스턴스화를 통하여 메모리 에 적재됩니다.

여기서 일반적으로 객체를 만드는 방법new 키워드가 유일하며, new 키워드를 통해 선언한 클래스의 객체를 생성합니다.

 


이처럼 객체지향에서 객체를 생성하는 것은 시스템의 자원을 소모한다는 의미입니다.

 

 

 

다수의 동일한 객체를 생성가능

 

 

일반적인 클래스의 특징으로는 한 번의 선언으로 다수의 동일한 객체를 생성 할 수 있다는 것입니다.

 

물론 다수의 동일한 객체를 생성하더라도 각 객체마다 갖고 있는 상태값은 생성자 또는 setter함수를 통해 다를 수 있습니다.

 

프로그램에서 다수의 동일한 객체를 만드는 이유는 서로 다른 상태값을 가진 객체가 필요하기 때문이죠!

 


그렇다면 다수의 동일한 객체를 생성하는 방법에는 무엇이 있을까요?

 

첫 번째new키워드를 통해 생성하는 방법이 있습니다. 

 

public class User {
    private String name;        //유저 이름
    private String job;         //유저 직업
    private int age;            //유저 나이

    /* User 클래스의 생성자 */
    User(String name, String job, int age) {
        this.name = name;
        this.job = job;
        this.age = age;
    }

    /* User 클래스 내 맴버변수들의 setter */
    public void setName(String name) { this.name = name; }
    public void setJob(String job) { this.job = job; }
    public void setAge(int age) { this.age = age; }

    /* User 클래스 내 맴버변수들의 getter */
    public String getName() { return this.name; }
    public String getJob() { return this.job; }
    public int getAge() { return this.age; }
}

 

사용자를 의미하는 User 클래스 를 선언하고, 해당 클래스는 생성자 setter 통해 상태값을 set 할 수 있습니다.

 

public class ProtoTypePattern {
    public static void main(String[] args) {
        User user1 = new User("수지", "Engineer", 26);
        System.out.println("user1"
                + ", 이름 : " + user1.getName()
                + ", 직업 : " + user1.getJob()
                + ", 나이 : " + user1.getAge());

        User user2 = new User("성민", "백수", 29);
        System.out.println("user2"
                + ", 이름 : " + user2.getName()
                + ", 직업 : " + user2.getJob()
                + ", 나이 : " + user2.getAge());
    }
}

 

다음 main()에서 User 클래스를 new키워드를 통해 2가지 형태의 User 객체를 생성하였습니다.

 

이로써 new 키워드를 통해 상태값이 다른 객체를 생성할 수 있는 것을 확인하였습니다.

 

하지만 new 키워드는 객체를 생성할 때마다 메모리의 자원을 소모한다는 문제가 존재합니다.

 

따라서 객체가 많거나 객체 생성이 복잡할수록 시스템이 처리할 부하는 늘어나며 더 많은 자원을 소모하게 되는 것이죠.

 


 

두번째, 복사를 활용한 프로토 타입 패턴 입니다. 

 

 

먼저 복사의 개념을 살펴보도록 하겠습니다.

 

복사의 종류로는 얕은 복사깊은 복사 2가지로 나누어집니다.

 

 

얕은 복사는 객체를 공유 방식으로 복사합니다.

 

공유 방식'=' 키워드를 사용하여 객체를 복사할 때 새로운 자원을 할당받지 않고 객체를 복사하는 것입니다. 

 

즉 해당 객체의 주소값을 공유한다고 생각하시면 됩니다.

 

 

public class ProtoTypePattern {
    public static void main(String[] args) {
        User user1 = new User("수지", "Engineer", 26);
        System.out.println("user1"
                + ", 이름 : " + user1.getName()
                + ", 직업 : " + user1.getJob()
                + ", 나이 : " + user1.getAge());

        /* 얕은 복사의 예 */
        User user2 = user1;         //'='키워드를 통해 얕은 복사 진행.
        System.out.println("user2"
                + ", 이름 : " + user2.getName()
                + ", 직업 : " + user2.getJob()
                + ", 나이 : " + user2.getAge());

        /* 얕은 복사로 생성된 user2의 데이터 수정 */
        user2.setName("성민");
        user2.setJob("백수");
        user2.setAge(29);
        System.out.println("user2"
                + ", 이름 : " + user2.getName()
                + ", 직업 : " + user2.getJob()
                + ", 나이 : " + user2.getAge());

        /* user2의 수정으로 인해 user1의 데이터도 같이 수정됨 */
        System.out.println("user1"
                + ", 이름 : " + user1.getName()
                + ", 직업 : " + user1.getJob()
                + ", 나이 : " + user1.getAge());
    }
}

 

얕은 복사를 통한 객체의 복사

 

main() 에서 user1의 객체를 user2에 '=' 키워드를 통해 얕은 복사를 진행하였습니다.

 

처음 출력에서는 user2의 객체가 user1의 내용을 그대로 출력하는것을 확인 할 수 있으며,

 

setter를 통해 user2 수정 후에는 user1도 같이 수정되는 것 을 확인할 수 있습니다.

 

따라서 얕은 복사는 우리가 원하는 상태값이 다른 객체를 생성하는 방법과는 다르다는것을 확인 할 수 있습니다.

 

 

 

깊은 복사는  물리적으로 할당된 변수를 다른 물리적 변수로 복사하는 것입니다.

 

일반적인 '='키워드로는 깊은 복사를 처리 할 수 없으며, 다른 복잡한 과정이 필요합니다.

 

자바에서는 깊은 복사를 제공하기위해 특별한 Cloneable 인터페이스를 제공합니다.

 

public class User implements Cloneable{		//깊은 복사를 위해 Cloneable implements.
    private String name;        //유저 이름
    private String job;         //유저 직업
    private int age;            //유저 나이

    /* User 클래스의 생성자 */
    User(String name, String job, int age) {
        this.name = name;
        this.job = job;
        this.age = age;
    }

    /* User 클래스 내 맴버변수들의 setter */
    public void setName(String name) { this.name = name; }
    public void setJob(String job) { this.job = job; }
    public void setAge(int age) { this.age = age; }

    /* User 클래스 내 맴버변수들의 getter */
    public String getName() { return this.name; }
    public String getJob() { return this.job; }
    public int getAge() { return this.age; }
    
    /* 깊은 복사를 위해 Clone() 재정의 */
    @Override
    public Object clone() throws CloneNotSupportedException {
        User user = (User)super.clone();
        return user;
    }
}

 

User 클래스깊은 복사를 제공해주는 Cloneable 인터페이스를 구현합니다.

 

public class ProtoTypePattern {
    public static void main(String[] args) throws CloneNotSupportedException {
        User user1 = new User("수지", "Engineer", 26);
        System.out.println("user1"
                + ", 이름 : " + user1.getName()
                + ", 직업 : " + user1.getJob()
                + ", 나이 : " + user1.getAge());

        /* 깊은 복사의 예 */
        User user2 = (User)user1.clone();         //clone 을 통한 깊은 복사 진행.
        System.out.println("user2"
                + ", 이름 : " + user2.getName()
                + ", 직업 : " + user2.getJob()
                + ", 나이 : " + user2.getAge());

        /* 깊은 복사로 생성된 user2의 데이터 수정 */
        user2.setName("성민");
        user2.setJob("백수");
        user2.setAge(29);
        System.out.println("user2"
                + ", 이름 : " + user2.getName()
                + ", 직업 : " + user2.getJob()
                + ", 나이 : " + user2.getAge());

        /* user2의 수정에도 user1의 데이터는 변동없음 */
        System.out.println("user1"
                + ", 이름 : " + user1.getName()
                + ", 직업 : " + user1.getJob()
                + ", 나이 : " + user1.getAge());
    }
}

 

 

 

main() 에서 user2에 user1의 clone() 을 호출하여 깊은 복사를 진행하였습니다.

 

결과에서 보시다시피 깊은 복사를 통해 복사한 user2 객체는 동일한 상태값을 갖고 있으며, 

 

user2의 상태값을 변경해도 user1의 상태값은 변동이 없는것을 확인 할 수 있습니다.

 

 

이와같이 상태값이 다른 객체를 생성을 위해 깊은복사를 사용하는 객체 생성 방식을 프로토 타입 패턴이라고 합니다.

 


정리하자면!

 

다수의 동일한 객체를 생성하기 위해 매번 new 키워드로 새로운 상태값의 객체를 생성하는것은 중복된 자원소모가 발생합니다.

 

따라서 깊은 복사를 활용한 프로토 타입 패턴을 활용하면 매번 인스턴스화 과정을 거치지 않고 객체를 복제하여 생성 할 수 있습니다.

 

이는 인스턴스화를 통해 생성 로직에 소모되는 처리시간자원을 아낄수 있다는 의미입니다. (생성자 로직이 동작하지 않음)

 

 

따라서 적은 리소스로 많은 객체를 생성해야 하는경우 프로토 타입 패턴은 매우 유용합니다 !

 

 

 

 

 

이렇게 오늘은 프로토 타입 패턴 을 이야기해봤는데요.

 

 

궁금하시거나 질문이 있으시면 댓글로 달아주세요 !!

 

 

감사합니다!