[객체 생성 패턴] Chapter 5-2. Prototype Pattern : 패턴 적용하기

 

✍️ 프로토타입 패턴, 적용하기

이전까지의 디자인 패턴과 다르게, 프로토타입 패턴은 자바가 제공하는 clone을 사용해서 간단하게 구현할 수 있다. clone은 기본적으로 인스턴스의 얕은 복사를 제공하는 메서드로 최상위 클래스인 Object에 선언되어 있다.

public class Object {
	...
    protected native Object clone() throws CloneNotSupportedException;
    ...
}

재밌게도 clone 메서드의 접근 제어자는 protected로, 아무 클래스나 clone을 제공하는 것이 아닌 명시적으로 clone이 가능하도록 만든 클래스만 clone을 외부에 제공할 수 있다.

 

앞서 말한 '명시적으로 clone이 가능하도록 하는 작업'이 바로 Cloneable interface를 구현하고 clone 메서드를 @Override 하는 작업이다. 참고로 Object가 제공하는 ㅏ얕은 복사의 clone을 사용하고 싶다면 super.clone() 그대로 반환하면 된다. 

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Country implements  Cloneable{
    private String name; // 국가명
    private String continent; // 대륙
    private int phone; // 국가 번호

    // 한국 정보를 DB에서 얻어온다고 생각하자!!!
    public void loadKoreaDataFromDB() {
        name = "Korea";
        continent = "Asia";
        phone = +82;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;

        if (o == null || getClass() != o.getClass())
            return false;

        Person person = (Person) o;
        
        return age == person.age 
                && Objects.equals(name, person.name) 
                && Objects.equals(address, person.address) 
                && Objects.equals(country, person.country);
    }
}

 

실제로 테스트해보면 clone으로 생성한 인스턴스는 기존의 인스턴스와 전혀 다르지만 값은 동일함을 확인할 수 있다.

public static void main(String[] args) throws CloneNotSupportedException {
    Country korea = new Country();
    korea.loadKoreaDataFromDB();

    Person korean = new Person("홍길동", "seoul", 17, korea);
    Person clone = (Person) korean.clone();

    System.out.println("같은 인스턴스인가? : " + (korean == clone));
    System.out.println("같은 값을 가지고 있는가? : " + korean.equals(clone));
    System.out.println("같은 클래스인가? : " + (korean.getClass() == clone.getClass()));
}

// 같은 인스턴스인가? : false
// 같은 값을 가지고 있는가? : true
// 같은 클래스인가? : true

 

🍊 clone(), 얕은 복사

얕은 복사를 알고 있다면 넘어가도 좋다.

 

지금까지 반복적으로 언급했듯, clone 메서드는 기본적으로 얕은 복사를 제공한다. 그렇다면 얕은 복사가 대체 뭘까?

가령 아래 코드에서 clone을 호출하면 새로운 인스턴스가 생성되고 "홍길동", "seoul", 17은 그 값이 복사가 된다. 그렇다면 레퍼런스 변수 korea는 어떤 형태로 복사가 됐을까?

public static void main(String[] args) throws CloneNotSupportedException {
    Country korea = new Country();
    korea.loadKoreaDataFromDB();

    Person korean = new Person("홍길동", "seoul", 17, korea);
    Person clone = (Person) korean.clone();
 }

 

 

첫 번째, 새로운 Country 인스턴스를 만들고 동일한 값으로 채워 넣었다.

두 번째, clone의 Country도 korean이 참조하는 Country를 동일하게 참조한다.

정답은 두 번째 방식으로 이를 얕은 복사라고 한다. 쉽게 말하면 어떤 인스턴스가 가리키는 메모리 공간을 동일하게 참조하는 인스턴스를 생성하는 것을 얕은 복사라 하고, 반대로 동일한 값을 새로운 메모리 공간에 채워 넣고 그곳을 가리키는 인스턴스를 생성하는 것을 깊은 복사라고 한다.  

 

실제로 테스트해 보면 korean과 clone의 Country는 동일한 레퍼런스 값을 가지고 있다.

public static void main(String[] args) throws CloneNotSupportedException {
    Country korea = new Country();
    korea.loadKoreaDataFromDB();

    Person korean = new Person("홍길동", "seoul", 17, korea);
    Person clone = (Person) korean.clone();

    System.out.println(korean.getCountry() == clone.getCountry());
}

// true

 

 

만약 깊은 복사를 구현하고 싶다면 clone 메서드가 단순히 super.clone()을 반환하는 것이 아닌 새로운 인스턴스를 만들고 그 값을 기존 인스턴스의 값으로 채워주면 된다.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person implements Cloneable {
    private String name;
    private String address;
    private int age;
    private Country country;


    // deep copy
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Country country = new Country();
        country.loadKoreaDataFromDB();

        Person person = new Person();

        person.setName(this.name);
        person.setAddress(this.address);
        person.setAge(this.age);
        person.setCountry(country);

        return person;
    }
    
    ...
}

 

이제 Country의 레퍼런스 값이 다름을 알 수 있다.

public static void main(String[] args) throws CloneNotSupportedException {
    Country korea = new Country();
    korea.loadKoreaDataFromDB();

    Person korean = new Person("홍길동", "seoul", 17, korea);
    Person clone = (Person) korean.clone();

    System.out.println(korean.getCountry() == clone.getCountry());
}

// false

 

인프런의 백기선님의 강의 코딩으로 학습하는 GoF의 디자인 패턴을 참고해서 작성했습니다.

 

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

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

www.inflearn.com