Factory Pattern

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.

Diagram Dasar Factory

Diagram Dasar Factory

Ada 2 varian factory pattern yaitu :

  1. Factory Method
  2. Abstract Factory

Factory Method (FM)

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)

Factory Method

Factory Method

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

Abstract Factory (AF)

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)

Abstract Factory

Abstract Factory

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.

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

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

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

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

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

Tambah Factory & Produk Baru

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

  1. Buat sub-class dari AbstractFactory
  2. Sub-class itu harus punya konstanta bernama NAME yang dipakai untuk menampilkan nama factory di dalam combobox
  3. Override createProduct() dan getProducts()
  4. Tambahkan ke daftar factory
    _selector.factoryData = [...,..., FactoryBaru.NAME];
  5. Instansiasi 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 constructor dan konstanta NAME
    
    public static const NAME:String = "Produk Baru";
    public function ProdukBaru(factoryName:String,price:String = "7000") {
    	super(NAME,price,factoryName)
    }
    
    
  2. Daftarkan nama produk di dalam factory pembuatnya, lihat getProducts()
    override public function getProducts():Array {
    	return [...,...,ProdukBaru.NAME];
    }
    
  3. Instansiasi produk di dalam 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.

Links

Also in this category ...