Factory Pattern adalah pattern yang digunakan untuk memisahkan (decouple) proses pembuatan/instansiasi sebuah objek (produk) dari objek lain (klien) yang menggunakannya. Tujuannya supaya perubahan pada product class nggak menyebabkan kita harus mengubah kode pada client. Paling nggak akibat dari perubahan itu bisa diminimalisir. Dan juga supaya si factory bisa digunakan oleh banyak class.
Factory adalah objek yang berfungsi membuat objek lain (produk). Class ini menyembunyikan proses pembuatan produk dari klien sehingga klien nggak perlu tahu proses pembuatannya, bahkan klien juga nggak perlu tahu nama class dari produk yang dia minta.
Mungkin kita sering menulis function seperti ini:
var coffee:AbstractCoffee = createCoffee("xxx");
function createCoffee(type:String):Coffee{
if(type == "xxx" ) return new XXXCoffee();
if(type == "yyy" ) return new YYYCoffee();
if(type == "zzz" ) return new ZZZCoffee();
return null;
}
Cukup bagus untuk proyek kecil. Tapi coba bayangin kalo kita punya 20 class yang harus bisa bikin kopi dan semuanya harus bisa ditukar-tukar saat run-time plus si produk kemungkinan besar bakal berubah. Repot. Di sini gunanya Factory Pattern.
Factory Pattern memungkinkan instansiasi sebuah produk didelegasikan ke subclass (concrete factory) dari base-class yang bersifat generik (abstrak) . Keuntungannya adalah si klien hanya perlu tau generik classnya, jadi concrete factory bisa ditukar-tukar secara dinamis (polymorfis) tergantung produk apa yang dibutuhkan dan klien nggak perlu tau detil pembuatan produk.
Ada 2 varian factory pattern yaitu :
Define an interface for creating an object, but let the subclasses decide which class to instantiate. The Factory method lets a class defer instantiation to subclasses. (wikipedia)
Factory Method is similar to Abstract Factory but without the emphasis on families. (sourcemaking.com)
Dalam Factory Method, setiap concrete factory bertugas membuat satu produk. Jadi ada pemetaaan 1–1 antara factory dan produk yang dibuatnya. Klien menggunakan abstract factory untuk membuat objek tapi jenis objek itu ditentukan oleh concrete factory yang dipilih.
Contoh:
public class Main extends Sprite{
public var factory:AbstractFactory;
public var product:AbstractProduct;
public function Main():void{
factory = new ABCFactory();
product = factory.createProduct();
factory = new DEFFactory();
product = factory.createProduct();
factory = new XYZFactory();
product = factory.createProduct();
}
}
//abstract factory
public class AbstractFactory {
public function AbstractFactory() {}
public function createProduct():AbstractProduct {
throw new IllegalOperationError("Abstract method. Must be implemented in subclass");
return null;
}
}
//abstract product
public class AbstractProduct {
public function AbstractProduct() {}
}
//concrete factories
public class ABCFactory extends AbstractFactory {
override public function createProduct():AbstractProduct {
return new ProductABC();
}
}
public class DEFFactory extends AbstractFactory {
override public function createProduct():AbstractProduct {
return new ProductDEF();
}
}
//concrete products
public class ProductABC extends AbstractProduct{
public function ProductABC() {
trace("ProductABC created");
}
}
public class ProductDEF extends AbstractProduct {
public function ProductDEF() {
trace("ProductDEF created");
}
}
Output:
ProductABC created
ProductDEF created
Dalam contoh di atas, kalo class ProductABC berubah, misalnya konstruktornya harus kita beri parameter “id”, kita nggak perlu utak-atik class Main. Cukup kita modifikasi ABCFactory. Contohnya gini :
public class ProductABC extends AbstractProduct{
public function ProductABC(id:String) {
trace("ProductABC created with id " + id);
}
}
public class ABCFactory extends AbstractFactory {
override public function createProduct():AbstractProduct {
var id:String = String(Math.round(Math.random() * 10));
return new ProductABC(id);
}
}
ProductABC created with id 8
ProductDEF created
The intent in employing the pattern is to insulate the creation of objects from their usage. This allows for new derived types to be introduced with no change to the code that uses the base class. (wikipedia)
Abstract Factory emphasizes a family of product objects. (sourcemaking.com)
Perbedaan utama antara Abstract Factory dengan Factory Method adalah dalam pemetaan antara factory dengan produk. Dalam AF, satu factory bisa membuat banyak produk yang sekelompok.
Biar lebih gampang dipahami, saya buat aplikasi sederhana berikut ini.
package factory {
import product.AbstractProduct;
import flash.errors.IllegalOperationError;
public class AbstractFactory {
public function AbstractFactory(name:String) {
trace("Pesan makanan/minuman di " + name);
}
public function createProduct(type:String):AbstractProduct {
throw new IllegalOperationError("Abstract method. Must be implemented in subclass.");
}
public function getProducts():Array {
throw new IllegalOperationError("Abstract method. Must be implemented in subclass.");
}
}
}
Berikut ini turunannya, yaitu concrete factory yang akan membuat produk berdasarkan pilihan menu.
//WarungBuTini.as
package factory {
import product.AbstractProduct;
import product.NasiRawon;
import product.KopiSusu;
import product.SodaGembira;
public class WarungBuTini extends AbstractFactory{
public static const NAME:String = "Warung Bu Tini";
public function WarungBuTini() {
super(NAME);
}
override public function createProduct(type:String):AbstractProduct {
if (type == SodaGembira.NAME) return new SodaGembira(NAME,"4500");
if (type == NasiRawon.NAME) return new NasiRawon(NAME, "8000");
if (type == KopiSusu.NAME) return new KopiSusu(NAME, "6000");
throw Error("Invalid product type");
return null;
}
override public function getProducts():Array {
return [SodaGembira.NAME,NasiRawon.NAME,KopiSusu.NAME];
}
}
}
//DepotAgung.as
package factory {
import product.AbstractProduct;
import product.NasiGoreng;
import product.PangsitMie;
import product.SodaGembira;
public class DepotAgung extends AbstractFactory{
public static const NAME:String = "Depot Agung";
public function DepotAgung() {
super(NAME);
}
override public function createProduct(type:String):AbstractProduct {
if (type == NasiGoreng.NAME) return new NasiGoreng(NAME, "5500");
if (type == PangsitMie.NAME) return new PangsitMie(NAME, "4000");
if (type == SodaGembira.NAME) return new SodaGembira(NAME, "3000");
throw Error("Invalid product type");
return null;
}
override public function getProducts():Array {
return [NasiGoreng.NAME,PangsitMie.NAME,SodaGembira.NAME];
}
}
}
package product {
import flash.display.Sprite;
import flash.text.TextField;
public class AbstractProduct extends Sprite{
//timeline objects
public var productName:TextField;
public var price:TextField;
public var factoryName:TextField;
public function AbstractProduct(productName:String,price:String,factoryName:String) {
this.productName.text = productName;
this.price.text = "Rp " + price;
this.factoryName.text = factoryName;
trace("Beli " + productName + " di " + factoryName);
}
public function pay():void{
trace("Bayar " + price.text + " ke " + factoryName.text);
}
}
}
Concrete Factory akan membuat objek dari concrete product dan mengirimkannya ke klien ( Main )
Berikut ini concrete product nya
//NasiGoreng.as
package product {
[Embed(source='../assets/assets.swf', symbol='NasGor')]
public class NasiGoreng extends AbstractProduct {
public static const NAME:String = "Nasi Goreng";
public function NasiGoreng(factoryName:String,price:String = "7000") {
super(NAME,price,factoryName)
}
}
}
//SodaGembira.as
package product {
[Embed(source='../assets/assets.swf', symbol='Sogem')]
public class SodaGembira extends AbstractProduct {
public static const NAME:String = "Soda Gembira";
public function SodaGembira(factoryName:String,price:String = "5000") {
super(NAME, price, factoryName);
}
}
}
package
{
import factory.AbstractFactory;
import factory.DepotAgung;
import factory.WarungBuTini;
import factory.WarungPakJaja;
import product.AbstractProduct;
import fl.controls.ComboBox;
import fl.controls.List;
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite{
public function Main():void{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private var _productPic:Sprite;
private var _selector:FactorySelector;
//main class hanya pakai base/abstract class, bukan concrete class
private var _factory:AbstractFactory;
private var _product:AbstractProduct;
private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
//_factory & _products _selector
_selector = new FactorySelector();
addChild(_selector);
_selector.factories.addEventListener(Event.CHANGE, selectFactory);
_selector.productList.addEventListener(Event.CHANGE, onProductChange);
_selector.factoryData = [WarungBuTini.NAME, DepotAgung.NAME];
//product picture holder
_productPic = new Sprite();
_productPic.x = 10;
_productPic.y = _selector.height + 20;
addChild(_productPic);
}
//----------------------------------------------------------- FACTORY SELECTION
private function selectFactory(e:Event = null):void {
switch(_selector.factories.selectedLabel) {
case WarungBuTini.NAME:
_factory = new WarungBuTini();
break;
case DepotAgung.NAME:
_factory = new DepotAgung();
break;
}
_selector.productData = _factory.getProducts();
}
//----------------------------------------------------------- PRODUCT SELECTION
private function onProductChange(e:Event):void {
var type:String = _selector.productList.itemToLabel(_selector.productList.selectedItem);
_product = _factory.createProduct(type);
_product.pay();
setPicture(_product);
}
//----------------------------------------------------------- DISPLAY PRODUCT IMAGE
private function setPicture(product:AbstractProduct):void {
var i:int = _productPic.numChildren;
while (i--) {
_productPic.removeChildAt(i);
}
_productPic.addChild(product);
}
}
}
Kalo kita ingin buat factory baru, langkah-langkahnya adalah :
NAME yang dipakai untuk menampilkan nama factory di dalam comboboxcreateProduct() dan getProducts()_selector.factoryData = [...,..., FactoryBaru.NAME];
selectFactory()
switch(_selector.factories.selectedLabel) {
....
case FactoryBaru.NAME:
_factory = new FactoryBaru();
break;
}
Untuk bikin produk baru:
AbstractProduct dengan parameterized constructor dan konstanta NAME
public static const NAME:String = "Produk Baru";
public function ProdukBaru(factoryName:String,price:String = "7000") {
super(NAME,price,factoryName)
}
getProducts()
override public function getProducts():Array {
return [...,...,ProdukBaru.NAME];
}
createProduct()
override public function createProduct(type:String):AbstractProduct {
...
if (type == ProdukBaru.NAME) return new ProdukBaru(NAME,"1200");
...
}
Contoh:
Produk baru : Kopi Susu
[Embed(source='../assets/assets.swf', symbol='Tarik')]
public class KopiSusu extends AbstractProduct {
public static const NAME:String = "Kopi Susu";
//----------------------------------------------------------- INIT
public function KopiSusu(factoryName:String,price:String = "3500") {
super(NAME, price, factoryName);
}
}
Factory baru : Warung Pak Jaja. Salah satu dagangannya adalah Kopi Susu.
public class WarungPakJaja extends AbstractFactory {
public static const NAME:String = "Warung Pak Jaja";
//----------------------------------------------------------- INIT
public function WarungPakJaja() {
super(NAME);
}
override public function createProduct(type:String):AbstractProduct {
if (type == KopiSusu.NAME) return new KopiSusu(NAME,"1200");
if (type == SodaGembira.NAME) return new SodaGembira(NAME, "3000");
if (type == NasiGoreng.NAME) return new NasiGoreng(NAME, "7500");
throw Error("Invalid product type");
return null;
}
override public function getProducts():Array {
return [KopiSusu.NAME,SodaGembira.NAME,NasiGoreng.NAME];
}
}
Tambahkan Warung Pak Jaja ke dalam Main class :
private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
//_factory & _products _selector
_selector = new FactorySelector();
addChild(_selector);
_selector.factories.addEventListener(Event.CHANGE, selectFactory);
_selector.productList.addEventListener(Event.CHANGE, onProductChange);
_selector.factoryData = [WarungBuTini.NAME, WarungPakJaja.NAME, DepotAgung.NAME];
//product picture holder
_productPic = new Sprite();
_productPic.x = 10;
_productPic.y = _selector.height + 20;
addChild(_productPic);
}
//----------------------------------------------------------- FACTORY SELECTION
private function selectFactory(e:Event = null):void {
switch(_selector.factories.selectedLabel) {
case WarungPakJaja.NAME:
_factory = new WarungPakJaja();
break;
case WarungBuTini.NAME:
_factory = new WarungBuTini();
break;
case DepotAgung.NAME:
_factory = new DepotAgung();
break;
}
_selector.productData = _factory.getProducts();
}
Di output panel :
Pesan makanan/minuman di Warung Bu Tini
Beli Kopi Susu di Warung Bu Tini
Bayar Rp 6000 ke Warung Bu Tini
Pesan makanan/minuman di Depot Agung
Beli Pangsit Mie di Depot Agung
Bayar Rp 4000 ke Depot Agung
Pesan makanan/minuman di Warung Pak Jaja
Beli Soda Gembira di Warung Pak Jaja
Bayar Rp 3000 ke Warung Pak Jaja
Beli Kopi Susu di Warung Pak Jaja
Bayar Rp 1200 ke Warung Pak Jaja
Di dalam arsip project file di atas, saya ada banyak produk yang bisa Anda pakai untuk utak atik.
Oke, sekian artikel tentang Factory Method & Abstract Factory. Mudah-mudahan bermanfaat. Kalo nggak ngerti silakan tanya.