Sebelumnya kita udah membahas Factory Method Design Pattern. Kalau belum baca, mending dibaca dulu biar nyambung dengan tulisan ini karena use case-nya masih sambungan dari tulisan tersebut🙂. Sekarang kita bahas versi upgrade-nya, yaitu Abstract Factory Design Pattern. Keduanya memang mirip, yaitu memberikan user kebebasan memilih implementasi objek apa yang dipakai secara runtime lewat input. Bedanya Abstract Factory Design Pattern ga hanya memberikan implementasi objeknya saja, tapi juga satu set family objek terkait. Makanya bisa dibilang ini adalah Factory Method versi upgrade. Oh ya, jika masih ambigu tentang terminologi "Factory" dalam programming, bisa baca artikel Macam-macam Factory terlebih dahulu.
Abstract Factory Design Pattern adalah Creational Design Pattern yang memberikan implementasi objek beserta family objek terkait.
Design Pattern
Use Case
Untuk mempermudah pembahasan, kita masih menggunakan use case yang sama seperti Factory Method dengan beberapa requirement tambahan:
- Terdapat 2 jenis laptop, Asus dan Dell;
- Masing-masing laptop memiliki Gaming Laptop dan Office Laptop;
- Gaming Laptop menggunakan Nvidia Graphic Card dan RGB Keyboard;
- Office Laptop menggunakan Intel Graphic Card dan Standard Keyboard;
- Ketika user menginginkan Gaming Laptop dengan input "asus" maka yang didapatkan adalah Asus Rog;
- Ketika user menginginkan Gaming Laptop dengan input "dell" maka yang didapatkan adalah Dell Alienware;
- Ketika user menginginkan Office Laptop dengan input "asus" maka yang didapatkan adalah Asus Vivobook;
- Ketika user menginginkan Office Laptop dengan input "dell" maka yang didapatkan adalah Dell Latitude;
- Ketika user memberikan input yang salah maka tampilkan error;
Contoh Code
Contoh code-nya kurang lebih sama seperti contoh pada Factory Method Design Pattern sebelumnya, karena menggunakan use case yang mirip. Jadi untuk menghemat tulisan code Laptop dan implementasinya ga gw tulis. Gw hanya menuliskan class tambahannya beserta contoh method dan contoh penggunaannya aja ya😅.
Interface LaptopGraphicCard
public interface LaptopGraphicCard{
String getGraphic();
}
Class GamingGraphicCard
public class GamingGraphicCard implements LaptopGraphicCard{
@Override
public String getGraphic(){
return "NVIDIA";
}
}
Class OfficeGraphicCard
public class OfficeGraphicCard implements LaptopGraphicCard{
@Override
public String getGraphic(){
return "Intel";
}
}
Interface LaptopKeyboard
public interface LaptopKeyboard{
String getKeyboard();
}
Class GamingKeyboard
public class GamingKeyboard implements LaptopKeyboard{
@Override
public String getKeyboard(){
return "RGB";
}
}
Class OfficeKeyboard
public class OfficeKeyboard implements LaptopKeyboard{
@Override
public String getKeyboard(){
return "Standard";
}
}
Method buildLaptop()
public static Laptop buildLaptop(String laptop, String type){
String key = laptop + "-" + type;
if("asus-gaming".equals(key)){
return new AsusRog(new GamingGraphicCard(), new GamingKeyboard());
}
if("dell-gaming".equals(key)){
return new DellAlienware(new GamingGraphicCard(), new GamingKeyboard());
}
if("asus-office".equals(key)){
return new AsusVivoBook(new OfficeGraphicCard(), new OfficeKeyboard());
}
if("dell-office".equals(key)){
return new DellLatitude(new OfficeGraphicCard(), new OfficeKeyboard());
}
throw new IllegalArgumentException("not found");
}
Contoh penggunaan
public static void main(String[] args){
Laptop laptop = buildLaptop("dell", "gaming");
System.out.println("dell.getLaptopName() = " + laptop.getLaptopName());
}
Untuk mempersingkat tulisan, gw menulis parameternya menggunakan String, tapi baiknya case seperti di atas menggunakan Enum parameter pada Factory.
Masalah
Seperti yang pernah dibahas sebelumnya, jika dibuat tanpa Design Pattern maka method buildLaptop() akan semakin gendut seiring bertambahnya requirement seperti jenis laptop. Lalu maintenance property seperti Keyboard dan Graphic Card juga akan semakin kompleks kedepannya apabila ada penambahan property lainnya seperti Processor, RAM, Monitor, dan lainnya.
Solusi
Sekarang kita improve menggunakan Abstract Factory Design Pattern😎.
Interface Laptop
interface Laptop{
String getLaptopName();
String getKeyboardType();
String getGraphicType();
}
Abstract Class Laptop
public abstract class AbstractLaptop implements Laptop{
protected final LaptopProperty property;
protected AbstractLaptop(LaptopProperty property){
this.property = property;
}
}
Class AsusRog
public class AsusRog extends AbstractLaptop{
public AsusRog(LaptopProperty property){
super(property);
}
@Override
public String getLaptopName(){
return "Asus ROG Zephyrus";
}
@Override
public String getKeyboardType(){
return property.getLaptopKeyboard().getKeyboard();
}
@Override
public String getGraphicType(){
return property.getLaptopGraphicCard().getGraphic();
}
}
Class DellAlienware
public class DellAlienware extends AbstractLaptop{
public DellAlienware(LaptopProperty property){
super(property);
}
@Override
public String getLaptopName(){
return "Dell AlienWare M15";
}
@Override
public String getKeyboardType(){
return property.getLaptopKeyboard().getKeyboard();
}
@Override
public String getGraphicType(){
return property.getLaptopGraphicCard().getGraphic();
}
}
Class AsusVivobook
public class AsusVivobook extends AbstractLaptop{
public AsusVivobook(LaptopProperty property){
super(property);
}
@Override
public String getLaptopName(){
return "Asus Vivobook 14";
}
@Override
public String getKeyboardType(){
return property.getLaptopKeyboard().getKeyboard();
}
@Override
public String getGraphicType(){
return property.getLaptopGraphicCard().getGraphic();
}
}
Class DellLatitude
public class DellLatitude extends AbstractLaptop{
public DellLatitude(LaptopProperty property){
super(property);
}
@Override
public String getLaptopName(){
return "Dell Latitude 7490";
}
@Override
public String getKeyboardType(){
return property.getLaptopKeyboard().getKeyboard();
}
@Override
public String getGraphicType(){
return property.getLaptopGraphicCard().getGraphic();
}
}
Interface LaptopProperty
public interface LaptopProperty{
LaptopGraphicCard getLaptopGraphicCard();
LaptopKeyboard getLaptopKeyboard();
}
Class GamingLaptopProperty
public class GamingLaptopProperty implements LaptopProperty{
@Override
public LaptopGraphicCard getLaptopGraphicCard(){
return new GamingGraphicCard();
}
@Override
public LaptopKeyboard getLaptopKeyboard(){
return new GamingKeyboard();
}
}
Class OfficeLaptopProperty
public class OfficeLaptopProperty implements LaptopProperty{
@Override
public LaptopGraphicCard getLaptopGraphicCard(){
return new OfficeGraphicCard();
}
@Override
public LaptopKeyboard getLaptopKeyboard(){
return new OfficeKeyboard();
}
}
Interface LaptopFactory
public interface LaptopFactory{
Laptop buildLaptop(String laptop);
}
Class GamingLaptopFactory
public class GamingLaptopFactory implements LaptopFactory{
@Override
public Laptop buildLaptop(String laptop){
LaptopProperty property = new GamingLaptopProperty();
if("asus".equals(laptop)){
return new AsusRog(property);
}
if("dell".equals(laptop)){
return new DellAlienware(property);
}
throw new IllegalArgumentException("not found");
}
}
Class OfficeLaptopFactory
public class OfficeLaptopFactory implements LaptopFactory{
@Override
public Laptop buildLaptop(String laptop){
LaptopProperty property = new OfficeLaptopProperty();
if("asus".equals(laptop)){
return new AsusVivobook(property);
}
if("dell".equals(laptop)){
return new DellLatitude(property);
}
throw new IllegalArgumentException("not found");
}
}
Contoh penggunaan
public static void main(String[] args){
LaptopFactory laptopFactory = new OfficeLaptopFactory();
Laptop laptop = laptopFactory.buildLaptop("asus");
System.out.println("laptop.getLaptopName() = " + laptop.getLaptopName());
System.out.println("laptop.getGraphicType() = " + laptop.getGraphicType());
System.out.println("laptop.getKeyboardType() = " + laptop.getKeyboardType());
}
Setelah kita apply Abstract Factory Design Pattern, semuanya jadi lebih independent dan fleksibel, baik itu Laptop, LaptopFactory, hingga LaptopProperty. Method sebelumnya dipecah menjadi beberapa class Factory, yaitu GamingLaptopFactory dan OfficeLaptopFactory. Graphic Card dan Keyboard dibungkus ke dalam LaptopProperty yang digunakan oleh Laptop secara komposisi. Untuk "memaksa" turunan Laptop menggunakan LaptopProperty sebagai komposisi, kita perlu membuat AbstractLaptop dan menambahkan constructor yang menampung LaptopProperty. LaptopProperty dapat di-inject lewat masing-masing factory sesuai family objek terkait, dalam hal ini GamingLaptopProperty ke GamingLaptopFactory, dan OfficeLaptopProperty ke OfficeLaptopFactory. Ketika kita menginputkan "asus" dengan GamingLaptopProperty, maka akan menghasilkan objek asus gaming dengan gaming property. Begitu juga ketika kita menggunakan OfficeLaptopFactory, maka akan menghasilkan objek asus dengan office property. Ketika ada penambahan jenis laptop atau jenis property, tinggal bikin implementasi baru tanpa mengubah class yang sudah ada.
Kenapa menggunakan Abstract Factory Design Pattern?
Secara pemakaian biasanya seorang software engineer menggunakan pendekatan Factory Method terlebih dahulu. Jika ditemukan case yang kompleks dan diselesaikan menggunakan komposisi, barulah menggunakan AbstractFactory. Biasanya ditandai dengan adanya beberapa komposisi dengan satu set family objek yang mirip, seperti pada contoh di atas adalah Graphic Card dan Keyboard, sehingga family objek tersebut bisa dipecah. Gaming Graphic Card & Gaming Keyboard untuk Gaming Laptop, dan Office Graphic Card & Office Keyboard untuk Office Laptop. Dengan begitu masing-masing objek jadi lebih spesifik beserta satu set komposisi family objek terkait.
Verdict
Abstract Factory Design Pattern merupakan versi upgrade dari Factory Method Design Pattern. Keduanya memiliki tujuan yang sama, yaitu memberikan implementasi objek sesuai input user. Bedanya, jika Factory Method hanya berfokus pada inheritance, maka Abstract Factory juga berfokus pada composition. Makanya Abstract Factory lebih rumit daripada Factory Method😵. Biasanya design pattern ini baru dipertimbangkan setelah Factory Method Design Pattern sudah terlalu kompleks dan berkembang memiliki beberapa composition yang mempunyai kemiripan sehingga dapat dipecah sesuai family objek terkait. Makanya Abstract Factory ini tidak sepopuler saudaranya, Factory Method.