open:prototype-pattern

Prototype Pattern

덱스 린지어스(Dex Ringeus)는 사용자들이 회원 등록 양식에 불편함을 느끼는 것을 발견했다. 이것을 좀 더 편리하게 만들어야 한다.

페드로: 등록 양식에 무슨 문제가 있나요?

이브: 사용자들이 입력할 항목들이 너무 많아서 짜증날 정도예요.

페드로: 예를 들면요?

이브: 예를 들면 체중 항목이예요. 여성 사용자들의 90%가 그런 항목을 보면 짜증을 낼 거예요.

페드로: 하지만 이 항목은 우리의 분석 시스템에 중요해요. 이 항목 값에 근거해 음식과 옷을 추천해 주고 있거든요.

이브: 그러면 이 항목을 필수 입력 항목에서 제외하기로 하죠. 이 항목 값이 입력되지 않으면 기본값을 넣어 주고요.

페드로: 60kg이면 적당할까요?

디브: 그런 것 같아요.

페드로: 알았어요. 2분만 기다려 주세요.

두 시간이 흐른 뒤

페드로: 모든 항목이 기본값으로 채워진 등록 양식 프로토타입을 사용해요. 사용자가 양식 작성을 끝냈을 때, 기본값들을 변경하면 돼요.

이브: 좋습니다.

페드로: 여기에 표준 등록 양식이 있어요. clone() 메소드에서는 프로토타입을 사용하고 있고요.

public class RegistrationForm implements Cloneable {
  private String name = "Zed";
  private String email = "[email protected]";
  private Date dateOfBirth = new Date(1970, 1, 1);
  private int weight = 60;
  private Gender gender = Gender.MALE;
  private Status status = Status.SINGLE;
  private List<Child> children = Arrays.asList(new Child(Gender.FEMALE));
  private double monthSalary = 1000;
  private List<Brand> favouriteBrands = Arrays.asList("Adidas", "GAP");
  // few hundreds more properties

  @Override
  protected RegistrationForm clone() throws CloneNotSupportedException {
    RegistrationForm prototyped = new RegistrationForm();
      prototyped.name = name;
      prototyped.email = email;
      prototyped.dateOfBirth = (Date)dateOfBirth.clone();
      prototyped.weight = weight;
      prototyped.status = status;
      List<Child> childrenCopy = new ArrayList<Child>();
      for (Child c : children) {
        childrenCopy.add(c.clone());
      }
      prototyped.children = childrenCopy;
      prototyped.monthSalary = monthSalary;
      List<String> brandsCopy = new ArrayList<String>();
      for (String s : favouriteBrands) {
        brandsCopy.add(s);
      }
      prototyped.favouriteBrands = brandsCopy;
    return  prototyped;
  }
}

페드로: 사용자를 만들 때마다, clone()을 호출해서 기본값을 바꿉니다.

이브: 끔직하네요! 가변 자료형의 세상에서는 동일한 값의 객체를 새로 생성하려면 clone()이 필요해요. 난점은 깊은 복사를 해야 한다는 것입니다. 단순히 레퍼런스를 복사하면 안되고, 재귀적으로 내부의 객체들을 clone() 해야만 하지요. 그런데 그 객체들 중의 일부에 clone() 메소드가 없으면 어떻게 될까요?

페드로: 그게 바로 문제인데, 이 패턴은 그 문제를 해결해 주죠.

이브: 제가 보기에, 새로운 객체를 추가해 줄 때마다 clone 메소드를 구현해 주어야만 한다면, 그것은 제대로 된 해결책이라고 보기 힘들다고 생각해요.

페드로: 클로저로는 이런 문제를 어떻게 피할 수 있죠?

이브: 클로저는 불변 자료구조를 제공해요. 그것이 전부예요.

페드로: 불변 자료구조로 프로토타입 문제를 어떻게 해결한다는 거죠?

이브: 객체를 변경할 때마다, 새로운 불변 객체를 얻게 되요. 그래서 예전 객체는 변경되지 않지요. 불변 자료형의 세상에서는 프로토타입 패턴이 필요 없어요.

(def registration-prototype
     {:name          "Zed"
      :email         "[email protected]"
      :date-of-birth "1970-01-01"
      :weight        60
      :gender        :male
      :status        :single
      :children      [{:gender :female}]
      :month-salary  1000
      :brands        ["Adidas" "GAP"]})

;; return new object
(assoc registration-prototype
     :name "Mia Vallace"
     :email "[email protected]"
     :weight 52
     :gender :female
     :month-salary 0)

페드로: 훌륭하네요! 하지만 그런 식으로는 성능에 영향을 미치지 않을까요? 새로운 값을 추가할 때마다 수백만 개의 데이터를 복사하려면 꽤 시간이 걸리지 않나요?

이브: 아니, 그렇지 않아요. 구글에 가서 존속 데이터 구조 (persistent data structures)와 구조 공유 (structural sharing)에 관해 검색해 보세요.

페드로: 고마워요.


  • open/prototype-pattern.txt
  • 마지막으로 수정됨: 2021/11/21 12:28
  • 저자 127.0.0.1