Design Pattern 101 — Prototype Pattern
ในกลุ่มของ Creation Pattern ของ GoF Design Pattern นี้ จะมี pattern หนึ่งที่ชื่อว่า Prototype Pattern สำหรับผมไม่ได้รู้จัก pattern นี้สักเท่าไหร่ ดังนั้นในบทความนี้จะมาเรียนรู้กันว่า pattern นี้ใช้แก้ปัญหาอะไรบ้าง
Prototype
Problem
เราต้องเขียนแอพพลิเคชันวาดรูป โดยสามารถวาดรูป สีเหลี่ยมผืนผ้า (Rectangle), สีเหลี่ยมจตุรัส (Square) และ วงกลม (Circle) ซึ่งจะมี base class Shape
ที่มี field เป็น ตำแหน่ง x
, y
ชนิดของรูปวาด type
และมี method draw()
เพื่อวาดรูปนั้นๆ
public abstract class Shape {
private int x, y;
protected String type;
public Shape(String type) {
this.type = type;
}
void draw() {
System.out.println(format("Drawing -> %s at position: (%d, %d)", type, x, y));
}
// getter/setter
}public class Rectangle extends Shape {
public Rectangle() {
super("Rectangle");
}
}public class Square extends Shape {
public Square() {
super("Square");
}
}public class Circle extends Shape {
public Circle() {
super("Circle");
}
}
ถ้าเราต้องการวาดรูปสีเหลียมผืนผ้า 3 รูปที่ตำแหน่งเดียวกัน เราก็จะต้องสร้าง object 3 object และกำหนดค่าตำแหน่ง x, y ให้แต่ละรูปไปทีละรูป
Shape rect1 = new Rectangle();
rect1.setX(100);
rect1.setY(100);
Shape rect2 = new Rectangle();
rect2.setX(100);
rect2.setY(100);
Shape rect3 = new Rectangle();
rect2.setX(100);
rect2.setY(100);
rect1.draw();
rect2.draw();
rect3.draw();
Output:
Drawing -> Rectangle at position: (100, 100)
Drawing -> Rectangle at position: (100, 100)
Drawing -> Rectangle at position: (100, 100)
จะเห็นได้ว่าเป็นงานที่มีความซ้ำซ้อนของการสร้าง object ทั้งๆ ที่เราต้องการเพียง object ที่มีตำแหน่งเดิมเท่านั้นเอง
Solution
ถ้าจะดีกว่าไหมถ้าเราสามารถ copy object ของรูปวาดต่างๆ ทีมีตำแหน่งเดียวกัน ดังนั้น Prototype pattern จะมาช่วยแก้ปัญหานี้
โดยแก้ไขให้ abstract class Shape
มีความสามารถในการ copy ตัวเองได้ ซึ่งใน Java จะมี interface Cloneable
ที่มีความสามารถในการ clone หรือ copy instance ได้ (ซึ่ง implement ผ่าน JNI (Java Native Interface) ด้วยภาษา C++)
และเพิ่ม clone()
method เพื่อเรียกใช้ method clone()
ของ Cloneable
และทำให้ subclass ของ Shape
ใช้งาน clone ได้ ในส่วนของ subclass ไม่ต้องแก้ไข
public abstract class Shape implements Cloneable {
private int id;
private int x, y;
protected String type;
public Shape(String type) {
this.type = type;
}
public void draw() {
System.out.println(format("Drawing -> %s at position: (%d, %d)", type, x, y));
}
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
// getter/setter
}
เพิ่ม classShapePrototype
เพื่อเก็บและเรียกใช้งาน prototype ของรูปวาด (Shape) ในแบบต่างๆ และเป็นส่วนที่เรียกใช้งาน method clone()
ด้วย
public class ShapePrototype {
private static Map<Integer, Shape> caches = new HashMap<>();
public static Shape getShape(int shapeId) {
return (Shape) caches.get(shapeId).clone();
}
public static void loadShapes() {
Rectangle rectangle = new Rectangle();
rectangle.setId(1);
rectangle.setX(100);
rectangle.setY(100);
caches.put(rectangle.getId(), rectangle);
// other shape (Square, Cicle)
}
}
เมื่อนำ ShapePrototype
ของ Prototype pattern มาใช้ ก็เพียงแต่ load prototype ด้วย methodloadShapes()
และใช้ method getShape
เพื่อ copy prototype เอามาใช้ ด้วย method clone()
ShapePrototype.loadShapes();
Shape rect1 = ShapePrototype.getShape(1);
Shape rect2 = ShapePrototype.getShape(1);
Shape rect3 = ShapePrototype.getShape(1);
rect1.draw();
rect2.draw();
rect3.draw();
จะเห็นได้ว่าได้ output เหมือนกับตอนแรก โดยที่ไม่ต้องมากำหนดค่าตำแหน่ง x
, y
ของแต่ละ object แต่ไปใช้งาน prototype แทน
Drawing -> Rectangle at position: (100, 100)
Drawing -> Rectangle at position: (100, 100)
Drawing -> Rectangle at position: (100, 100)
Prototype Pattern ใช้เมื่อการสร้าง instance object ของ Class ที่การสร้างมีความซับซ้อน หรือ expensive
สรุป
จากการที่ได้ลองใช้งาน Prototype Pattern มาสร้าง object ที่มี creation cost อย่าง object ที่มีความเหมือนกันนั้น ทำให้เราได้ลดภาระในส่วนนี้ไปได้ แต่ Prototype Pattern ก็ยังใช้งานได้ใน use case อื่นๆ อย่างเช่นถ้าต้อง query data ขนาดใหญ่และใช้เวลานานจาก Database และมีการใช้ข้อมูลนี้หลายๆ ครั้ง ก็สามารถเก็บข้อมูลไว้เป็น prototype และ copy ข้อมูลไปใช้งานได้ทันที