Zastosowanie
Prototyp
(ang. Prototype
) (wzorzec kreacyjny) ma za zadanie utworzyć kopię macierzystego obiektu zwanego prototypem
. Zamiast tworzyć obiekt od nowa używa on istniejącą instancje do wygenerowania jej kopii, która jest zupełnie nowym obiektem, ale o tych samych właściwościach co prototyp
. Modyfikacja kopii nie wpływa zatem w żaden sposób na obiekt macierzysty. Użycie wzorca Prototyp
uniezależnia system od znajomości sposobu tworzenia obiektów, a także może pozytywnie wpływać na jego wydajność. Wzorzec ten jest standardowym elementem w wielu językach programowania.
Ograniczenia
Kopiowanie skomplikowanych obiektów z wieloma referencjami do innych obiektów może być utrudnione. W takiej sytuacji wiele klas, których instancje zawierają się w klasie obiektu macierzystego muszą implementować metodę klonowania copy
.
Użycie
Prototyp
stosowany jest tam, gdzie zachodzi potrzeba tworzenia dużej ilości podobnych lub tych samych obiektów, a ich kopiowanie jest wydajniejsze niż tworzenie. Może zostać także wykorzystany wraz z innymi wzorcami kreacyjnymi jak np.: Fabryka abstrakcyjna
czy też w procesach przywracania/przechowywania stanu systemu.
Implementacja
Klasy dla których instancje mogą być klonowane implementują metodę copy
w której tworzona i zwracana jest kopia bieżącego obiektu na podstawie kopi wszystkich pól obiektu. Zadanie to może zostać oddelegowane także do konstruktora kopiującego
. Należy mieć na uwadzę, aby wszystkie klasy (których instancje zawierają się w klasie klonowanej) także implementowały metodę copy
lub posiadały konstruktor kopiujący. W przeciwnym wypadku w klasie macierzystej należy rekurencyjnie pobrać wszystkie wartości prymitywne.
Poniższy listing przedstawia implementacja wzorca Prototyp
dla klasy Prototype1
składającej się z prymitów - płytka kopia
(shallow copy
) oraz dla klasy Prototype2
składającej się z obiektów złożonych - głęboka kopia
(deep copy
).
public class Prototype1 extends Prototype {
private double price;
//other primitive fields
public Prototype1() {
this(0);
}
public Prototype1(int price) {
super();
this.price = price;
}
public Prototype1(Prototype1 prototype) {
super(prototype);
if(prototype != null) {
this.price = prototype.price;
//do the same for all fields
}
}
@Override
public Prototype copy() {
//shallow copy
return new Prototype1(this);
}
@Override
public void action() {
//do specific action for Prototype1
}
//some other methods
}
public class Prototype2 extends Prototype {
private int value;
private Complex complex; //has only int and boolean fields
//other object and primitive types
public Prototype2() {
this(0, new Complex(0, false));
}
public Prototype2(int value, Complex complex) {
super();
this.value = value;
this.complex = complex;
}
public Prototype2(Prototype2 prototype) {
super(prototype);
if(prototype != null) {
this.value = prototype.value;
//copy complex object by getting all primitive types or provide copy method/copy constructor
this.complex = new Complex(prototype.complex.getNumber(), prototype.complex.getAvailability());
//do the same for all fields
}
}
@Override
public Prototype copy() {
//deep copy
return new Prototype2(this);
}
@Override
public void action() {
//do specific action for Prototype2
}
//some other methods
}
public abstract class Prototype {
//some fields
//some common methods
public Prototype() {
//initialize fields
}
public Prototype(Prototype prototype) {
//copy all fields from prototype into this instance
}
//in Java clone is abstract method of Object class so no need copy extra method
public abstract Prototype copy();
public abstract void action();
}
Na podstawie istniejących obiektów (prototypów
) klient generuje ich kopie. Następnie na całej kolekcji klonów wykonuje operacje.
//create prototype to copy
Prototype1 prototype1 = new Prototype1(10.0);
Prototype2 prototype2 = new Prototype2(5, new Complex(3, true));
//copy prototypes to save time and resources
List<Prototype> clones = new ArrayList<>();
Prototype1 clone1 = prototype1.copy();
clones.add(clone1);
Prototype2 clone2 = prototype2.copy();
clones.add(clone2);
//do more clones
//do some action for every copies
for(Prototype copy : clones)
copy.action();
Przykład
Aplikacja MathFigures
pomaga w nauce geometrii i figur geometrycznych. Pozwala m.in. na rysowanie figur na tablicy, a następnie na wyliczenie dla nich pola powierzchni oraz obwodu. Rezultaty te mogą być porównane z wyliczeniami użytkownika dzięki czemu sprawdza on swoją wiedzę. Użytkownik tworząc nowe figury geometryczne często kopiuje już istniejące figury, a następnie modyfikuje je wg swojego uznania. Ze względu na wymóg klonowanie istniejących figur w projekcie został zaimplementowany wzorzec Prototyp
.
public class Circle extends Figure {
private int x, y, radius;
public Circle() {
this(0, 0, 0);
}
public Circle(int x, int y, int radius) {
super();
this.x = x;
this.y = y;
this.radius = radius;
}
public Circle(Circle prototype) {
super(prototype);
if(prototype != null) {
this.x = prototype.x;
this.y = prototype.y;
this.radius = prototype.radius;
}
}
@Override
public Figure copy() {
//shallow copy
return new Circle(this);
}
@Override
public void draw() {
//draw Circle on the board in own way
}
@Override
public double area() {
return Math.PI * Math.pow(radius);
}
@Override
public double perimeter() {
return 4 * Math.PI * radius;
}
}
public class Triangle {
private Point point1, point2, point3;
public Triangle() {
this(new Point(0, 0), new Point(0, 0), new Point(0, 0));
}
public Triangle(Point point1, Point point2, Point point3) {
super();
this.point1 = point1;
this.point2 = point2;
this.point3 = point3;
}
public Triangle(Triangle prototype) {
super(prototype);
if(prototype != null) {
this.point1 = new Point(prototype.point1.getX(), prototype.point1.getY());
this.point2 = new Point(prototype.point2.getX(), prototype.point2.getY());
this.radius = new Point(prototype.point3.getX(), prototype.point3.getY());
}
}
@Override
public Figure copy() {
//deep copy
return new Triangle(this);
}
@Override
public void draw() {
//draw Triangle on the board in own way
}
@Override
public double area() {
double p = perimeter() / 2;
double a = getSideLength(point1, point2);
double b = getSideLength(point1, point3);
double c = getSideLength(point2, point3);
return Math.sqrt(p*(p-a)*(p-b)*(p-c));
}
@Override
public double perimeter() {
double a = getSideLength(point1, point2);
double b = getSideLength(point1, point3);
double c = getSideLength(point2, point3);
return a + b + c;
}
private double getSideLength(Point point1, Point point2) {
double size = Math.pow(point1.getX() - point2.getX()) + Math.pow(point1.getY() - point2.getY());
return Math.sqrt(size);
}
}
//other figures like rectangle
public abstract class Figure {
private int color;
private int borderColor;
public Figure() {
this.color = Color.WHITE;
this.borderColor = Color.BLACK;
}
public Figure(int color, int borderColor) {
this.color = color;
this.borderColor = borderColor;
}
public Figure(Figure prototype) {
this.color = prototype.color;
this.borderColor = prototype.borderColor;
}
public abstract Prototype copy();
public abstract void draw();
public abstract double area();
public abstract double perimeter();
}
Użytkownik tworzy w całości kilka figur i na podstawie ich kopii generuje kolejne oraz sprawdza swoje obliczenia z faktycznymi wynikami.
//new empty board initialize
List<Figure> figures = new ArrayList();
//user creates some figures
Circle prototypeCircle = new Circle(10, 5, 4);
prototypeCircle.draw();
figures.add(prototypeCircle);
Triangle prototypeTriangle = new Triangle(new Point(1, 1), new Point(1, 5), new Point(5, 1));
prototypeTriangle.draw();
figures.add(prototypeTriangle);
//after creation basic giures, user just copy them instead of creat new ones from the beginning
//some of them are modifed
Circle copy1 = prototypeCircle.copy();
copy1.setColor(Color.BLUE);
copy.setRadius(2);
figures.add(copy1);
Triangle copy2 = prototypeTriangle.copy();
copy2.setColor(Color.GREEN);
triangle.setPoint1(new Point(5, 5));
figures.add(copy2);
//user calculates themself area and perimeter of the figures and check them with real results
//calculate area and delimeter for all figures on the board
for(Figure figure : figures) {
double area = figures.area();
double perimeter = figures.perimeter();
//check the answer
}
Biblioteki
Każda klasa implementująca interfejs Cloneable
oraz rozszerzająca metodę copy
klasy Object
(ze standardowego pakietu Java
) jest przykładem implementacji wzorca Prototyp
jednakże ze względu na narzucane ograniczenia takie rozwiązanie jest odradzane. Jako alternatywe podaje się BeanUtils.cloneBean
oraz SerializationUtils.clone
.