Coder for Hire

Factory Pattern

Factory Pat­tern adalah pat­tern yang digunakan untuk memisahkan (decouple) proses pembuatan/instansiasi sebuah objek (produk) dari objek lain (klien) yang meng­gunakan­nya. Tujuan­nya supaya per­ubahan pada product class nggak menyebabkan kita harus meng­ubah kode pada client. Paling nggak akibat dari per­ubahan itu bisa diminimalisir. Dan juga supaya si factory bisa digunakan oleh banyak class.

Factory adalah objek yang ber­fungsi mem­buat objek lain (produk). Class ini menyem­bunyikan proses pem­buatan produk dari klien sehingga klien nggak perlu tahu proses pem­buatan­nya, bahkan klien juga nggak perlu tahu nama class dari produk yang dia minta.

Mung­kin kita sering menulis fun­ction 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 kemung­kinan besar bakal ber­ubah. Repot. Di sini gunanya Factory Pattern.

Factory Pat­tern memung­kinkan instan­siasi sebuah produk didelegasikan ke subclass (con­crete factory) dari base-class yang ber­sifat generik (abs­trak) . Keun­tungan­nya adalah si klien hanya perlu tau generik class­nya, jadi con­crete factory bisa ditukar-tukar secara dinamis (polymor­fis) ter­gan­tung produk apa yang dibutuhkan dan klien nggak perlu tau detil pem­buatan produk.

Diagram Dasar Factory

Diagram Dasar Factory

Ada 2 varian factory pat­tern yaitu :

  1. Factory Method
  2. Abs­tract Factory

Factory Method (FM)

Define an inter­face for creating an object, but let the subclas­ses decide which class to instan­tiate. The Factory method lets a class defer instan­tiation to subclas­ses. (wikipedia)

Factory Method is similar to Abs­tract Factory but without the emphasis on families. (sourcemaking.com)

Factory Method

Factory Method

Dalam Factory Method, setiap con­crete factory ber­tugas mem­buat satu produk. Jadi ada pemetaaan 1–1 antara factory dan produk yang dibuat­nya. Klien meng­gunakan abs­tract factory untuk mem­buat objek tapi jenis objek itu diten­tukan oleh con­crete factory yang dipilih.

Con­toh:

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 con­toh di atas, kalo class ProductABC ber­ubah, misal­nya kon­struk­tor­nya harus kita beri parameter “id”, kita nggak perlu utak-atik class Main. Cukup kita modifikasi ABCFactory. Con­toh­nya 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

Abs­tract Factory (AF)

The intent in employing the pat­tern 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)

Abs­tract Factory emphasizes a family of product objects. (sourcemaking.com)

Abstract Factory

Abs­tract Factory

Per­bedaan utama antara Abs­tract Factory dengan Factory Method adalah dalam pemetaan antara factory dengan produk. Dalam AF, satu factory bisa mem­buat banyak produk yang sekelompok.

Biar lebih gam­pang dipahami, saya buat aplikasi seder­hana ber­ikut ini.

Demo : Warung Factory

Click to see the demo

Click to see the demo

Class Diagram

Class Diagram

Project files : → AbstractFactory.rar

Factory

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.");
		}

	}

}

Ber­ikut ini turunan­nya, yaitu con­crete factory yang akan mem­buat produk ber­dasarkan 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];
		}

	}

}

Product

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);
		}

	}

}

Con­crete Factory akan mem­buat objek dari con­crete product dan meng­irim­kan­nya ke klien ( Main )
Ber­ikut ini con­crete 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);
		}

	}

}

Main class

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);
		}
	}
}

Tam­bah Factory & Produk Baru

Kalo kita ingin buat factory baru, langkah-langkahnya adalah :

  1. Buat sub-class dari AbstractFactory
  2. Sub-class itu harus punya kon­stanta ber­nama NAME yang dipakai untuk menam­pilkan nama factory di dalam combobox
  3. Over­ride createProduct() dan getProducts()
  4. Tam­bahkan ke daf­tar factory
    _selector.factoryData = [...,..., FactoryBaru.NAME];
  5. Instan­siasi factory di dalam selectFactory()
    switch(_selector.factories.selectedLabel) {
    	....
    	case FactoryBaru.NAME:
    		_factory = new FactoryBaru();
    	break;
    }
    

Untuk bikin produk baru:

  1. Buat subclass dari AbstractProduct dengan parameterized con­structor dan kon­stanta NAME
    
    public static const NAME:String = "Produk Baru";
    public function ProdukBaru(factoryName:String,price:String = "7000") {
    	super(NAME,price,factoryName)
    }
    
    
  2. Daf­tarkan nama produk di dalam factory pem­buat­nya, lihat getProducts()
    override public function getProducts():Array {
    	return [...,...,ProdukBaru.NAME];
    }
    
  3. Instan­siasi produk di dalam createProduct()
    override public function createProduct(type:String):AbstractProduct {
    	...
    	if (type == ProdukBaru.NAME) return new ProdukBaru(NAME,"1200");
    	...
    }

Con­toh:

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 dagangan­nya 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];
	}

}

Tam­bahkan 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 ten­tang Factory Method & Abs­tract Factory. Mudah-mudahan ber­man­faat. Kalo nggak ngerti silakan tanya.

Links


Also in this category …


Comments are closed.

Powered by Wordpress | Designed by Elegant Themes