[객체 생성 패턴] Chapter 5-4. Prototype Pattern : Java에서 찾아보는 프로토타입 패턴

 

✍️ 프로토타입 패턴, Java에서 찾아보기

여기 Student를 담는 ArrayList가 있을 때, students를 복제를 하고 싶다면 ArrayList가 제공하는 clone 메서드를 호출할 수 있다. 하지만 이 방법은 자주 쓰이지 않는다.

public static void main(String[] args) {
    Student kang = new Student("kang");
    Student world = new Student("world");

    ArrayList<Student> students = new ArrayList<>();
    students.add(kang);
    students.add(world);

    ArrayList<Student> clones = (ArrayList<Student>) students.clone();
}
@AllArgsConstructor
public class Student {
    private String name;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

 

ArrayList의 clone을 사용하지 않는 대표적인 이유는 변수의 타입을 지정할 때 가능한 추상적인 타입을 사용하려 하기 때문이다. 추상 타입은 코딩의 유연성과 관련이 있는데 변수를 추상 타입으로 선언하면 얼마든지 구체적인 클래스의 인스턴스로 갈아끼울 수 있기 때문이다. 다시 말해 일반적으로 ArrayList가 아닌 List를 사용하고, List는 clone을 제공하지 않는다.

public static void main(String[] args) {
    Student kang = new Student("kang");
    Student world = new Student("world");

    List<Student> students = new ArrayList<>();
    students.add(kang);
    students.add(world);
}

 

프로토타입 패턴과 관련은 없지만 Collection을 복사(얕은 복사)하는 대표적인 방법으로 ArrayList의 생성자를 사용할 수 있다.

List<Student> clone = new ArrayList<>(students);

 

🧰 ModelMapper

프로토타입 패턴과 관련은 없지만.. 프로토타입 패턴과 유사하게 기존 인스턴스로 새로운 인스턴스를 구성할 때 유용한 라이브러리 ModelMapper를 정리하려 한다.

 

ModelMapper란, 인스턴스의 필드를 특정 클래스의 인스턴스 필드로 매핑할 때 사용하는 라이브러리다.

말이 조금 어렵다.. 예제를 보자.

 

여기 사람을 정의한 Person 클래스가 있고, 국가 정보를 정의한 Country 클래스가 있다.

@Data
public class Person {
    private String name;
    private String address;
    private int age;
    private Country country;
}
@Data
public class Country {
    private String countryName; // 국가명
    private String continent; // 대륙
    private int phone; // 국가 번호

    public void loadKoreaDataFromDB() {
        countryName = "Korea";
        continent = "Asia";
        phone = +82;
    }
}

 

가령 한국인을 표한하고 싶다면 다음과 같이 코드를 작성할 수 있다.

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

    Person korean = new Person("kangworld", "seoul", 20, korea);
}

 

그리고 여기 Person 클래스의 필드를 플랫하게 정의한 PersonData 클래스가 있다.

@Data
public class PersonData {
    private String name; // 사람 이름
    private String address; // 주소
    private int age; // 나이

    private String countryName; // 국가명
    private String countryContinent; // 대륙
    private int countryPhone; // 국가 번호
}

 

만약 Person 타입의 인스턴스를 PersonData 타입의 인스턴스로 옮겨 담고 싶다면 get set 메서드를 반복적으로 호출하면 된다. 다만 필드가 많아질수록 코드의 길이도 증가하고 실수가 발생할 수 있다.

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

    Person korean = new Person("kangworld", "seoul", 20, korea);
    PersonData koreanData = new PersonData();

    koreanData.setName(korean.getName());
    koreanData.setAddress(korean.getAddress());
}

 

바로 앞선 상황에서 ModelMapper 라이브러리를 사용하면 쉽게 인스턴스를 옮겨 담을 수 있다.

먼저 ModelMapper 인스턴스를 만들고 map 메서드에 source(Person)와 destination(PersonData)을 차례대로 넘겨주면 source의 필드값으로 채워진  PersonData 인스턴스를 반환한다.

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

    Person korean = new Person("kangworld", "seoul", 20, korea);
    ModelMapper modelMapper = new ModelMapper();

    PersonData koreanData = modelMapper.map(korean, PersonData.class);
    System.out.println(koreanData);
}

 

이렇게 필드의 이름과 계층구조를 파악해서 값을 옮겨 담을 수 있는 이유는 Java가 제공하는 reflection 덕분이다. 더 자세한 원리는 reflection을 참고하길 바라고 ModelMapper의 필드 매칭 전략과 같은 디테일은 공식 레퍼런스를 참고하길 바란다.  

 

 

 

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

 

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

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

www.inflearn.com