-
Prototype PatternDesign-Pattern/Creational Pattern 2023. 9. 28. 22:32728x90
개요
- 프로토타입 패턴은 객체 생성을 최적화하고 새로운 객체를 기존 객체의 복사를 통해 생성하는 디자인 패턴이다.
문제점
객체가 있고 그 객체를 외부에서부터 복사하는 것은 객체의 필드 중 일부가 비공개일 경우 항상 가능하지 않다.
또한 객체의 복제본을 생성하려면 객체의 클래스를 알아야 하므로, 코드가 해당 클래스에 의존하게 되고, 메서드의 매개 변수가 일부 인터페이스를 따르는 모든 객체를 수락한다면 우리는 그 객체가 따르는 인터페이스만 알고,
그 객체의 구상 클래스는 알지 못 한다.해결책
프로토타입 패턴은 실제로 복제되는 객체에 복제 프로세스를 위임한다.
패턴은 복제를 지원하는 모든 객체에 대한 공통 인터페이스를 선언하고 이 인터페이스를 사용하면 코드를 객체의 클래스에 결합하지않고도 해당 객체를 복제할 수 있다.
일반적으로 이런 인터페이스에는 단일clone
메서드만 포함된다.clone
메서드의 구현은 모든 클래스에서 유사한데 이 메서드는 현재 클래스의 객체를 만든 후 이전 객체의 모든 필드 값을 새 객체로 전달한다.
대부분의 프로그래밍 언어는 객체들이 같은 클래스에 속한 다른 객체의 비공개 필드에 접근 할 수 있도록 하므로 비공개 필드를 복사하는 것도 가능하다.
이 때 복제를 지원하는 객체를 프로토타입이라고 한다.만약 사용하는 객체에 수십 개의 필드와 수백 개의 메서드가 있는 경우 이를 복제해 사용하는 것이 서브 클래싱의 대안이 될 수 있다.
적용 시기
- 복사해야 하는 객체의 구현 클래스에 코드가 의존하면 안될 때 사용할 수 있다.
- 이 경우는 코드가 특정 인터페이스를 통해 외부 API에서 전달된 객체들과 함께 작동할 때 많이 발생한다.
이런 객체들의 구현 클래스를 알 수 없기 때문에 의존 할 수 없다. - 프로토타입 패턴은 클라이언트 코드에 복제를 지원하는 모든 객체와 작업할 수 있도록 일반 인터페이스를 제공한다.
이 인터페이스는 클라이언트 코드가 복제하는 객체의 구현 클래스에서 클라이언트 코드를 독립적으로 유지할 수 있게 한다.
- 이 경우는 코드가 특정 인터페이스를 통해 외부 API에서 전달된 객체들과 함께 작동할 때 많이 발생한다.
- 각자의 객체를 초기화하는 방식만 다른 자식 클래스들의 수를 줄이고 싶을 때 사용할 수 있다.
- 프로토타입 패턴은 다양한 방식으로 설정된 미리 만들어진 객체들의 집합을 프로토타입으로 사용할 수 있도록 하고
일부 설정과 일치하는 자식 클래스를 인스턴스화하는 대신 클라이언트는 간단하게 적절한 프로토타입을 찾아 복제한다.
- 프로토타입 패턴은 다양한 방식으로 설정된 미리 만들어진 객체들의 집합을 프로토타입으로 사용할 수 있도록 하고
패턴 장점
- 객체를 구현 클래스에 결합하지 않고 복제할 수 있다.
- 반복되는 초기화 코드를 제거한 후 미리 만들어진 프로토타입을 복제하는 방법을 사용할 수 있다.
- 복잡한 객체를 쉽게 생성할 수 있다.
- 복잡한 객체들에 대한 사전 설정을 처리할 때 상속을 대신 해 사용할 수 있는 방법이다.
패턴 단점
- 순환 참조가 있는 복잡한 객체를 복제할 때 매우 까다롭다.
프로토타입 패턴 구조
(1) Prototype
프로토타입 인터페이스는 복제 메서드를 선언하며, 이 메서드의 대부분은 단일
clone
메서드이다.(2) ConcretePrototype
구현 프로토타입 클래스는 복제 메서드를 구현한다.
원본 객체의 데이터를 복제본에 복사하는 것 외에도 이 메서드는 복제 프로세스와 관련된 일부 예외적인 경우도 처리할 수 있다.
(ex. 연결된 객체 복제, 재귀 종속성 제거)(3) Client
클라이언트는 프로토타입 인터페이스를 따르는 모든 객체의 복사본을 생성할 수 있다.
(1) PrototypeRegistry
- 프로토타입 레지스트리는 자주 사용하는 프로토타입에 쉽게 접근하는 방법을 제공한다.
- 이 레지스트리에는 복사될 준비가 된 미리 만들어진 객체의 집합을 저장한다.
- 가장 간단한 프로토타입 레지스티리는
name -> prototype
해시맵 형태이지만
단순히 이름을 검색하는 것보다 더 나은 검색 기준을 설정하는 것으로 보다 더 탄탄한 레지스트리를 구축할 수 있다.
구현 방법
- 프로토타입 인터페이스 혹은 추상 클래스를 생성한 후
clone
메서드를 선언한다.
(만약 기존 계층 구조가 있는 경우,clone
메서드를 계층 구조의 모든 클래스에 추가) - 프로토타입 클래스는 이 클래스의 객체를 인수로 받는 대체 생성자를 반드시 정의해야 한다.
또 생성자는 이 클래스에 정의된 모든 필드의 값을 전달된 객체에서 새로 생성된 인스턴스로 복사해야한다.
자식 클래스를 변경할 때에는 부모 생성자를 호출해 부모 클래스가 부모 클래스의 비공개 필드를 복제하도록 해야 한다. - 복제 메서드는 일반적으로 한 줄로 구성된다.
이 한 줄은 생성자의 프로토타입 버전으로new
연산자를 실행하고 모든 클래스는 복제 메서드를 오버라이딩한 후new
연산자와 함께 자체 클래스 이름을 사용해야 한다.
(이렇게 하지 않으면 복제 메서드가 부모 클래스의 객체를 생성할 가능성이 있다.) - 추가 옵션으로 자주 사용하는 프로토타입을 저장할 중앙 관리형 프로토타입 레지스트리를 생성할 수 있다.
Prototype Pattern 구현
모든 shape 클래스는 같은 인터페이스를 따르며, 이 인터페이스는 복제 메서드를 제공한다.
자식 클래스는 자신의 필드 값을 생성된 객체에 복사하기 전에 부모의 복제 메서드를 호출할 수 있다.Shape.java
package com.design.pattern.creational.prototype.shapes; import java.util.Objects; public abstract class Shape { public int x; public int y; public String color; public Shape() { } public Shape(Shape target) { if (target != null) { this.x = target.x; this.y = target.y; this.color = target.color; } } public abstract Shape clone(); @Override public boolean equals(final Object obj) { if (!(obj instanceof Shape)) return false; Shape shape = (Shape) obj; return shape.x == x && shape.y == y && Objects.equals(shape.color, color); } }
Circle.java
package com.design.pattern.creational.prototype.shapes; public class Circle extends Shape{ public int radius; public Circle() { } public Circle(Circle target) { super(target); // 부모 클래스 생성자 호출 if (target != null) { this.radius = target.radius; } } @Override public Shape clone() { return new Circle(this); } @Override public boolean equals(final Object obj) { if (!(obj instanceof Circle) || !super.equals(obj)) return false; Circle shape = (Circle) obj; return shape.radius == radius; } }
Rectangle.java
package com.design.pattern.creational.prototype.shapes; public class Rectangle extends Shape{ public int width; public int height; public Rectangle() { } public Rectangle(Rectangle target) { super(target); // 부모 클래스 생성자 호출 if (target != null) { this.width = target.width; this.height = target.height; } } @Override public Shape clone() { return new Rectangle(this); } @Override public boolean equals(final Object obj) { if (!(obj instanceof Rectangle) || !super.equals(obj)) return false; Rectangle shape = (Rectangle) obj; return shape.width == width && shape.height == height; } }
Demo.java
package com.design.pattern.creational.prototype; import com.design.pattern.creational.prototype.shapes.Circle; import com.design.pattern.creational.prototype.shapes.Rectangle; import com.design.pattern.creational.prototype.shapes.Shape; import java.util.ArrayList; import java.util.List; public class Demo { public static void main(String[] args) { List<Shape> shapes = new ArrayList<>(); List<Shape> shapesCopy = new ArrayList<>(); Circle circle = new Circle(); circle.x = 10; circle.y = 20; circle.radius = 15; circle.color = "red"; shapes.add(circle); Circle anotherCircle = (Circle) circle.clone(); shapes.add(anotherCircle); Rectangle rectangle = new Rectangle(); rectangle.width = 10; rectangle.height = 20; rectangle.color = "blue"; shapes.add(rectangle); cloneAndCompare(shapes, shapesCopy); } private static void cloneAndCompare(final List<Shape> shapes, final List<Shape> shapesCopy) { for (Shape shape : shapes) { shapesCopy.add(shape.clone()); } for (int i = 0; i < shapes.size(); i++) { if (shapes.get(i) != shapesCopy.get(i)) { System.out.println(i + ": Shapes are different objects"); if (shapes.get(i).equals(shapesCopy.get(i))) { System.out.println(i + ": And they are identical"); } else { System.out.println(i + ": But they are not identical"); } } else { System.out.println(i + ": Shape objects are the same"); } } } }
실행 결과
프로토타입 레지스트리
미리 정의된 프로토타입 객체의 집합을 포함한 중앙 집중식 프로토타입 레지스트리(또는 팩토리)를 구현하면 이를 통해 이름 또는 다른 매개변수들을 전달하여
팩토리로부터 새로운 객체들을 가져올 수 있다. 팩토리는 적절한 프로토타입을 검색한 후 복제를 통해 복사본을 반환한다.BundledShapeCache.java
package com.design.pattern.creational.prototype.caching.cache; import com.design.pattern.creational.prototype.shapes.Circle; import com.design.pattern.creational.prototype.shapes.Rectangle; import com.design.pattern.creational.prototype.shapes.Shape; import java.util.HashMap; import java.util.Map; public class BundledShapeCache { private Map<String, Shape> cache = new HashMap<>(); public BundledShapeCache() { Circle circle = new Circle(); circle.x = 5; circle.y = 7; circle.radius = 45; circle.color = "Green"; Rectangle rectangle = new Rectangle(); rectangle.x = 6; rectangle.y = 9; rectangle.width = 8; rectangle.height = 10; rectangle.color = "blue"; cache.put("Big green circle", circle); cache.put("Medium blue rectangle", rectangle); } public Shape put(String key, Shape shape) { cache.put(key, shape); return shape; } public Shape get(String key) { return cache.get(key).clone(); } }
Demo.java
package com.design.pattern.creational.prototype.caching; import com.design.pattern.creational.prototype.caching.cache.BundledShapeCache; import com.design.pattern.creational.prototype.shapes.Shape; public class Demo { public static void main(String[] args) { BundledShapeCache cache = new BundledShapeCache(); Shape shape1 = cache.get("Big green circle"); Shape shape2 = cache.get("Medium blue rectangle"); Shape shape3 = cache.get("Medium blue rectangle"); if (shape1 != shape2 && !shape1.equals(shape2)) { System.out.println("Big green circle != Medium blue rectangle"); } else { System.out.println("Big green circle == Medium blue rectangle"); } if (shape2 != shape3) { System.out.println("Medium blue rectangles are two different objects"); if (shape2.equals(shape3)) { System.out.println("And they are identical"); } else { System.out.println("But they are not identical"); } } else { System.out.println("Rectangle objects are the same"); } } }
실행 결과
디자인 패턴 비교
프로토타입을 사용하여 추상 팩토리의 구상 클래스의 생성 메서드를 구현할 수 있다.
프로토타입은 커맨드 패턴의 복사본을 저장해야 할 때 도움이 될 수 있다.
데코레이터 및 복합체 패턴을 많이 사용하는 패턴은 프로토타입을 사용해 이득을 볼 수 있다.
(프로토타입 패턴을 적용하면 복잡한 구조들을 처음부터 다시 건축하는 대신 복제할 수 있기 때문이다.)프로토타입은 상속 기반이 아니기 때문에 상속과 관련된 단점이 없다. 대신 복제된 객체의 복잡한 초기화가 필요하다.
이와 반대로 팩토리 메서드는 상속을 기반으로 하지만 초기화 단계가 필요하지않다.때로는 프로토타입이 메멘토 패턴의 간단한 대안이 될 수 있다.
추상 팩토리, 빌더 및 프로토타입은 모두 싱글턴으로 구현할 수 있다.
정리
- 프로토타입은 객체들(복잡한 객체 포함)을 특정 클래스들에 결합하지 않고 복제할 수 있도록 하는 생성 디자인 패턴이다.
- 모든 프로토타입 클래스들은 객체들의 구상 클래스들을 알 수 없는 경우에도 해당 객체들을 복사할 수 있도록 하는 공통 인터페이스가 있어야 한다.
- 프로토타입 객체들은 전체 복사본을 생성할 수 있다.
(같은 클래스의 객체들은 서로 비공개 필드에 접근할 수 있기 때문이다.) - 자바에서 프로토타입 패턴은
Cloneable
인터페이스를 통해 바로 사용할 수 있다.
'Design-Pattern > Creational Pattern' 카테고리의 다른 글
Factory Method (0) 2023.09.28 Builder (0) 2023.09.28 Abstract Factory (0) 2023.09.28