Catatan Interpreting Design Pattern by. Ahmad Hidayat Sumber: http://www.oodesign.com I. Design Principles I.1 Pendahuluan I.1.1 Apa itu Software Design Principles? Software design principles merupakan kumpulan pedoman yang membantu kita untuk menghindari desain yang buruk. Prinsip-prinsip desain yang berhubungan dalam buku Robert Martin yang berjudul "Agile Software Development: Principles, Patterns, and Practices". Terdapat 3 karakteristik penting dari desain yang buruk yang harus dihindari : 1. Rigidity (Kekakuan). Sulit untuk melakukan perubahan karena setiap perubahan terlalu banyak mempengaruhi bagian lain dari sistem. 2. Fragility (kerapuhan). Ketika melakukan perubahan, dapat membuat terhentinya bagian dari sistem yang tidak kita duga. 3. Immobility. Sulit untuk digunakan kembali dalam aplikasi lain karena tidak bisa diuraikan dari aplikasi yang bersangkutan. I.1.2 Open Close Principle Software entitas seperti class , modul dan fungsi harus terbuka untuk ekstensi (digunakan jangka panjang, by Yayat) tapi ditutup untuk modifikasi. OPC adalah prinsip generik. Anda dapat mempertimbangkan hal itu ketika menulis class Anda untuk memastikan bahwa ketika Anda perlu untuk memperpanjang perilaku mereka Anda tidak harus mengubah class namun untuk memperpanjang itu. Prinsip yang sama dapat diterapkan untuk modul, paket, perpustakaan. Jika Anda memiliki sebuah perpustakaan yang berisi satu set class ada banyak
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Catatan Interpreting Design Pattern by. Ahmad HidayatSumber: http://www.oodesign.com
I. Design PrinciplesI.1 Pendahuluan
I.1.1 Apa itu Software Design Principles?Software design principles merupakan kumpulan pedoman yang membantu kita untuk menghindari desain yang buruk. Prinsip-prinsip desain yang berhubungan dalam buku Robert Martin yang berjudul "Agile Software Development: Principles, Patterns, and Practices". Terdapat 3 karakteristik penting dari desain yang buruk yang harus dihindari :1. Rigidity (Kekakuan). Sulit untuk melakukan perubahan karena setiap
perubahan terlalu banyak mempengaruhi bagian lain dari sistem.2. Fragility (kerapuhan). Ketika melakukan perubahan, dapat membuat
terhentinya bagian dari sistem yang tidak kita duga.3. Immobility. Sulit untuk digunakan kembali dalam aplikasi lain karena tidak
bisa diuraikan dari aplikasi yang bersangkutan.
I.1.2 Open Close Principle Software entitas seperti class, modul dan fungsi harus terbuka untuk
ekstensi (digunakan jangka panjang, by Yayat) tapi ditutup untuk modifikasi.
OPC adalah prinsip generik. Anda dapat mempertimbangkan hal itu ketika menulis class Anda untuk memastikan bahwa ketika Anda perlu untuk memperpanjang perilaku mereka Anda tidak harus mengubah class namun untuk memperpanjang itu. Prinsip yang sama dapat diterapkan untuk modul, paket, perpustakaan. Jika Anda memiliki sebuah perpustakaan yang berisi satu set class ada banyak alasan yang membuat Anda lebih memilih untuk memperpanjang tanpa mengubah kode yang sudah ditulis (kompatibilitas, pengujian regresi). Ini sebabnya mengapa kita harus memastikan modul kita mengikuti Prinsip Buka Tutup (Open Close Principle).
Ketika mengacu pada class, Open Close Principle dapat dipastikan dengan menggunakan abstract class dan concrete classes untuk menerapkan perilakunya. Ini akan membuat kita memiliki concrete classes untuk memperluas abstract class bukan mengubah abstract class. Contoh kasusnya adalah Template Pattern dan Strategy Pattern.
I.1.3 Dependency Inversion Principle Modul High-level tidak harus bergantung pada modul Low-level.
Abstraksi tidak harus tergantung pada detail. Detail harus bergantung pada abstraksi.
Dependency Inversion Principle menyatakan bahwa kita harus memisahkan modul High-level dari modul Low-level, memasukan abstraction layer di antara high level classes dan low level classes. Lebih jauh lagi tentang membalikkan ketergantungan (inverts the dependency) : daripada pada kita menulis abstraksi didasarkan pada rincian, lebih baik menulis rincian berdasarkan abstraksi.Ketergantungan Inversi atau Pembalikan dari Control (Dependency Inversion or Inversion of Control) lebih mengacu pada istilah suatu cara di mana dependensi terealisasi. Dengan cara klasik ketika software module (class, framework, dll) membutuhkan beberapa modul lain, dilakukan inisialisasi dan memegang direct reference. Ini akan membuat 2 modul yang sukar digabungkan. Dalam rangka untuk memisahkan mereka modul pertama akan memberikan sangkutan (hook) (seperti properti, parameter dll) dan sebuah modul eksternal mengontrol dependensi akan menyuntikkan (injeksi) referensi untuk yang kedua..Dengan menerapkan Dependency Inversion (Inversi Ketergantungan), suatu modul dapat dengan mudah diubah oleh modul lain hanya dengan mengubah modul ketergantungan. Factories and Abstract Factorie dapat digunakan sebagai dependency frameworks (framework kerja ketergantungan), tetapi ada framworks khusus untuk itu, yang dikenal sebagai Inversion of Control Container.
I.1.4 Interface Segregation Principle Clients tidak boleh dipaksa untuk bergantung pada interfaces yang
tidak digunakan.Prinsip ini mengajarkan kita untuk berhati-hati bagaimana kita membuat interface. Ketika kita membuat interface, kita harus berhati-hati untuk hanya menambahkan metode yang harus ada. Jika kita menambahkan metode di dalam interface yang seharusnya tidak ada class yang mengimplementasikan interface dengan menerapkan metodenya. Sebagai contoh jika kita membuat sebuah interface yang disebut Pekerja dan menambahkan metode istirahat makan siang, semua pekerja harus menerapkannya. Bagaimana jika pekerja adalah robot?Sebagai kesimpulan interface yang berisi metode yang tidak spesifik seperti di atas disebut interface tercemar. Kita harus menghindarinya.
I.1.5 Single Responsibility Principle Sebuah class hanya memiliki satu alasan untuk berubah.Dalam konteks ini tanggung jawab (responsibility) ini dianggap sebagai satu-satunya alasan untuk berubah. Prinsip ini menyatakan bahwa jika kita memiliki 2 alasan untuk mengubah satu class, kita harus membagi fungsi dalam dua class. Setiap class hanya akan menangani satu tanggung jawab dan jika kemudian hari kita perlu membuat satu perubahan kita akan membuatnya dalam class yang menanganinya. Ketika kita perlu melakukan
perubahan dalam class yang memiliki tanggung jawab yang lebih dari satu fungsi, hal ini dapat mempengaruhi fungsi lainya.Single Responsibility Principle diperkenalkan Tom DeMarco dalam bukunya yang berjudul Structured Analysis and Systems Specification, 1979. Robert Martin menafsirkan kembali konsep dan menegaskan tanggung jawab (responsibility) sebagai satu alasan untuk berubah.
I.1.6 Liskov's Substitution Principle Tipe turunan harus benar-benar dapat menggantikan basis atau
induknya.Prinsip ini hanyalah kelanjutan dari Open Close Principle dalam hal behavior, bahwa kita harus memastikan class turunan memperpanjang class dasar atau induk tanpa mengubah behaviornya. Class-class turunan yang baru harus mampu menggantikan class induk tanpa perubahan kode.Prinsip Pergantian Liskov yang diperkenalkan oleh Barbara Liskov dalam Konferensi 1987 pada Aplikasi dan Sistem Bahasa Pemrograman Berorientasi Obyek, dalam abstraksi dan hirarki data.
I.2 Pembahasan Lebih DalamI.2.1 Open Close Principle
Motivasi
Sebuah desain aplikasi dan impelentasi kode yang pandai harus memperhatikan frekuensi perubahan yang sering dilakukan selama fase pengembangan dan pemeliharaan aplikasi. Biasanya, banyak perubahan ketika sebuah fungsi baru ditambahkan ke aplikasi. Perubahan-perubahan dalam kode yang sudah ada harus diminimalkan, karena telah diasumsikan bahwa kode yang ada sudah dilakukan unit test dan perubahan kode yang sudah ditulis dapat mempengaruhi fungsi yang ada yang tidak diinginkan.
Open Close Principle menyatakan bahwa desain dan penulisan kode harus dilakukan untuk meminimalisasi perubahan kode yang sudah ada ketika fungsi baru harus ditambahkan. Desain harus dilakukan untuk memungkinkan penambahan fungsi baru sebagai class baru, menjaga sebanyak mungkin kode yang ada tidak berubah.
Maksud
Software entities seperti classes, modules dan functions harus terbuka untuk diperluas, tapi tertutup untuk modifikasi .
Contoh
Di bawah ini adalah contoh yang melanggar Open Close Principle. Implementasi sebuah editor grafis yang dapat menggambar dengan berbagai bentuk. Ini jelas bahwa ia tidak mengikuti Open Close Principle sejak class GraphicEditor harus dimodifikasi untuk setiap class bentuk baru yang harus ditambahkan. Ada beberapa kelemahan:1. Untuk setiap penambahan bentuk baru, pengujian unit GraphicEditor
harus diulang.2. Ketika jenis bentuk baru ditambahkan waktu untuk menambahkan hal
itu akan menjadi tinggi karena pengembang yang menambahkannya harus memahami logika GraphicEditor tersebut.
3. Menambahkan bentuk baru dapat mempengaruhi fungsi yang ada dengan cara yang tidak diinginkan, bahkan jika bentuk baru bekerja sempurna
Dalam rangka untuk memiliki efek yang lebih dramatis, bayangkan bahwa Editor Grafis adalah class besar, dengan banyak fungsi di dalamnya, ditulis dan diubah oleh banyak pengembang, sementara bentuknya (shape) mungkin class yang diimplementasikan hanya oleh satu pengembang. Dalam hal ini akan menjadi perbaikan besar untuk memungkinkan penambahan bentuk (shape) baru tanpa mengubah class GraphicEditor.
// Open-Close Principle - Bad example
class GraphicEditor { public void drawShape(Shape s) { if (s.m_type==1) drawRectangle(s); else if (s.m_type==2) drawCircle(s); } public void drawCircle(Circle r) {....} public void drawRectangle(Rectangle r) {....} } class Shape { int m_type; } class Rectangle extends Shape { Rectangle() { super.m_type=1; } } class Circle extends Shape { Circle() { super.m_type=2; } }
Di bawah ini adalah contoh yang mendukung Open Close Principle. Dalam desain baru kita menggunakan metode abstract draw() dalam GraphicEditor untuk menggambar objek, sampai berpindah implementasinya di concrete shape objects (bentuk). Menggunakan Open Close Principle masalah dari desain sebelumnya sudah dihindari, karena GraphicEditor tidak berubah ketika class shape baru ditambahkan:
Tidak diperlukan unit testing. Tidak perlu memahami kode sumber dari GraphicEditor. Karena kode drawing telah dipindahkan ke concrete shape classes, itu
menurunkan risiko untuk mempengaruhi functionallity yang sudah ada ketika functionallity baru ditambahkan.
// Open-Close Principle - Good example class GraphicEditor { public void drawShape(Shape s) { s.draw(); } } class Shape { abstract void draw(); } class Rectangle extends Shape { public void draw() { // draw the rectangle } }
Kesimpulan
Prinsip OCP hanyalah sebuah prinsip. Membuat desain yang fleksibel melibatkan tambahan waktu dan usaha yang dihabiskan untuk itu dan memperkenalkan tingkat baru abstraksi meningkatkan kompleksitas kode. Jadi prinsip ini harus diterapkan pada area yang paling mungkin untuk diubah.
Ada banyak design patterns yang membantu kita untuk memperluas kode tanpa mengubahnya. Misalnya Decorator pattern membantu kita untuk mengikuti Open Close Principle. Juga Metode Factory atau Observer pattern dapat digunakan untuk merancang suatu aplikasi yang mudah untuk berubah dengan perubahan minimal dalam kode yang sudah ada.
I.2.2 Dependency Inversion Principle
Motivasi
Dalam sebuah aplikasi kita memiliki low level class yang melaksanakan operasi utama dan dasar, dan high level class yang merangkum (encapsulate) logika yang kompleks dan bergantung pada low level class. Sebuah cara alami menerapkan struktur seperti ini adalah kita menulis low level class, kemudian kita punya low level class untuk menulis high level class yang kompleks. Sejak high level class di dalam terminologi ini , kita terlihat sudah melakukan hal yang logis. Tapi ini bukan desain yang fleksibel. Apa yang terjadi jika kita perlu mengganti low level class?
Mari kita ambil contoh klasik dari copy module yang membaca karakter dari keyboard dan mencetak ke perangkat printer. High level class yang mengandung logika adalah class Copy. Low level class adalah KeyboardReader dan PrinterWriter.
Dalam desain yang buruk high level class menggunakan langsung low level class. Dalam hal ini jika kita ingin mengubah desain untuk mengarahkan output ke class FileWriter baru, kita harus mengubah class Salin. (Mari kita asumsikan bahwa itu adalah class yang sangat kompleks, dengan banyak logika dan benar-benar sulit untuk diuji).
Untuk menghindari masalah tersebut kita dapat menggunakan abstraction layer di antara high level class dan low level class. Karena high level modules berisi logika yang kompleks mereka tidak harus bergantung pada low level modules dan abstraction layer baru tidak harus dibuat berdasarkan low level modules. Low level modules dibuat berdasarkan abstraction layer.
Menurut prinsip ini cara merancang struktur class adalah mulai dari high level class ke low level modules: High Level Classes --> Abstraction Layer --> Low Level Classes
Maksud
high level modules tidak harus bergantung pada high level modules. Keduanya harus bergantung pada abstraksi.
Abstraksi tidak harus tergantung pada rincian. Rincian harus bergantung pada abstraksi.
Contoh
Di bawah ini adalah contoh yang melanggar Dependency Inversion Principle. Kita memiliki class Manager yang merupakan high level class, dan Worker yang merupakan low level class. Kita perlu menambahkan modul baru untuk aplikasi kita karena di perusahaan ada beberapa pekerja khusus yang baru dipekerjakan. Kita membuat class baru SuperWorker untuk ini.
Mari kita berasumsi bahwa class Manager itu kompleks yang mengandung logika sangat kompleks. Dan sekarang kita harus mengubahnya dalam rangka menambahkan class baru yaitu SuperWorker. Mari kita lihat kelemahanya: Kita harus mengubah class Manager (ingat itu adalah sesuatu yang
kompleks dan ini akan membutuhkan banyak waktu dan usaha). Beberapa fungsi yang sudah ada di class Manager mungkin akan
terpengaruh. Unit testing harus diulang.
Semua masalah tersebut akan membutuhkan banyak waktu untuk menyelesaikanya. Akan lebih sederhana jika aplikasi ini dirancang mengikuti Dependency Inversion Principle. Untuk itu kita membuat class Manager, sebuah interface IWorker dan class Worker implementasi dari interface IWorker. Ketika kita perlu menambahkan class SuperWorker yang harus kita lakukan adalah mengimplementasikan interface IWorker untuk itu.
// Dependency Inversion Principle - Bad exampleclass Worker {
public void work() {// ....working
}}class Manager {
Worker m_worker;
public void setWorker(Worker w) {m_worker=w;
}public void manage() {
m_worker.work();}
}class SuperWorker {
public void work() {//.... working much more
}}
Berikut ini adalah kode yang mendukung Dependency Inversion Principle. Dalam desain baru ini layer abstraction baru ditambahkan melalui Interface IWorker. Sekarang masalah dari kode di atas sudah diselesaikan: Class Manager tidak boleh diubah. Mengurangi resiko untuk mempengaruhi fungsionalitas yang sudah ada
di class Manager. Tidak perlu mengulangi unit testing untuk class Manager.
// Dependency Inversion Principle - Good exampleinterface IWorker {
public void work();}
class Worker implements IWorker{public void work() {
// ....working}
}
class SuperWorker implements IWorker{public void work() {
//.... working much more}
}
class Manager {IWorker m_worker;
public void setWorker(IWorker w) {m_worker=w;
}
public void manage() {m_worker.work();
}}
Kesimpulan
Bila prinsip ini diterapkan, itu berarti high level class tidak bekerja secara langsung dengan class low level classes, tapi menggunakan interface sebagai abstract layer. Dalam kasus kreasi dalam membuat baru low level objects di dalam high level classes (jika perlukan) tidak dapat dilakukan dengan menggunakan operator new. Untuk itu, beberapa Creational design pattern dapat digunakan, seperti Factory Method, Abstract Factory, Prototype.
Template Design Pattern adalah contoh di mana prinsip DIP diterapkan.
Tentu saja, dengan menggunakan prinsip ini menyiratkan upaya peningkatan dan kode yang lebih kompleks, tetapi lebih fleksibel. Prinsip ini tidak dapat diterapkan untuk setiap class atau setiap modul. Jika kita memiliki fungsi class yang lebih mungkin untuk tetap tidak berubah di masa depan ada tidak perlu menerapkan prinsip ini.
I.2.3 Interface Segregation Principle (ISP)
Motivasi
Ketika kita merancang sebuah aplikasi harus berhati-hati ketika membuat modul abstrak yang berisi beberapa submodul. Mengingat modul diimplementasikan oleh class, kita dapat memiliki sebuah abstraksi dari sistem dengan sebuah interface. Tetapi jika ingin memperluas aplikasi kita, menambahkan modul lain yang hanya berisi beberapa submodul dari sistem yang asli, kita dipaksa untuk mengimplementasikan interface secara penuh dan menulis beberapa metode kosong. Seperti sebuah interface yang dikenal interface lemak atau interface tercemar. Memiliki interface tercemar bukanlah solusi yang baik dan mungkin menyebabkan perilaku yang tidak tepat dalam sistem.
Interface Segregation Principle menyatakan bahwa Client tidak boleh dipaksa untuk mengimplementasikan interface yang tidak digunakan. Daripada sebuah interface gemuk lebih baik beberapa interface kecil yang terkelompokan berdasarkan metode, yang masing-masing melayani satu submodule.
Maksud
Client tidak boleh dipaksa untuk bergantung pada interface yang tidak digunakan.
Contoh
Di bawah ini adalah contoh yang melanggar Interface Segregation Principle. Kita memiliki class Manager yang mewakili orang yang mengelola para pekerja. Dan kita memiliki 2 jenis pekerja yaitu pekerja rata-rata dan beberapa pekerja yang sangat efisien. Keduanya bekerja dan butuh istirahat untuk makan. Tapi sekarang beberapa robot dipekerjakan juga di perusahaan, tetapi mereka tidak makan sehingga mereka tidak perlu istirahat. Di satu sisi class Robot baru perlu untuk mengimplementasikan
interface IWorker karena robot bekerja. Di sisi lain, class Robot tidak perlu menerapkannya karena robot tidak makan.
Inilah sebabnya mengapa dalam kasus ini IWorker dianggap interface tercemar.
Jika kita menjaga desain ini, class Robot dipaksa untuk menerapkan metode makan. Kita bisa menulis sebuah class kosong yang tidak melakukan apa-apa (katakanlah istirahat makan 1 detik setiap hari), dan dapat mempunyai pengaruh yang tidak diinginkan dalam aplikasi (Misalnya manager akan melihat laporan makan siang yang diambil lebih dari jumlah orang yang ada).
Menurut Interface Segregation Principle, desain yang fleksibel tidak akan memiliki interface tercemar. Dalam kasus ini interface IWorker harus dibagi dalam 2 interface yang berbeda.
// interface segregation principle - bad exampleinterface IWorker {
public void work();public void eat();
}
class Worker implements IWorker{public void work() {
// ....working}public void eat() {
// ...... eating in launch break}
}
class SuperWorker implements IWorker{public void work() {
//.... working much more}
public void eat() {//.... eating in launch break
}}
class Manager {IWorker worker;
public void setWorker(IWorker w) {worker=w;
}
public void manage() {worker.work();
}} Kode di bawah sudah mendukung Interface Segregation Principle. Dengan memisahkan interface IWorker dalam 2 interface yang berbeda, class Robot baru tidak lagi dipaksa untuk implement metode makan. Juga jika kita membutuhkan fungsi lain untuk robot seperti charging kita membuat interface lain yaitu IRechargeble dengan metode recharge.
class Worker implements IWorkable, IFeedable{public void work() {
// ....working}
public void eat() {//.... eating in launch break
}}
class Robot implements IWorkable{public void work() {
// ....working}
}
class SuperWorker implements IWorkable, IFeedable{public void work() {
//.... working much more}
public void eat() {//.... eating in launch break
}
}
class Manager {Workable worker;
public void setWorker(Workable w) {worker=w;
}
public void manage() {worker.work();
}}
Kesimpulan
Jika desain seperti ini sudah dilakukan interface lemak dapat dipisahkan menggunakan Adapter pattern.
Seperti prinsip lainnya, Interface Segregation Principle adalah salah satu prinsip yang membutuhkan tambahan waktu dan usaha yang dihabiskan untuk menerapkannya selama waktu desain dan meningkatkan kompleksitas kode. Tapi itu menghasilkan desain yang fleksibel. Jika kita menerapkanya lebih daripada yang diperlukan, itu akan menghasilkan kode yang terdapat banyak interface dengan satu metode, sehingga penerapanya harus berdasarkan pengalaman dan akal sehat dalam mengidentifikasi daerah-daerah di mana perluasan kode lebih mungkin terjadi di masa depan.
I.2.4 Single Responsibility PrincipleMotivasi
Dalam konteks ini responsibility (tanggung jawab) dianggap sebagai salah satu alasan untuk berubah. Prinsip ini menyatakan bahwa jika kita memiliki 2 alasan untuk mengubah sebuah class, kita harus membagi fungsionalitasnya menjadi dua class. Setiap class hanya akan menangani satu tanggung jawab dan ketika kita perlu membuat satu perubahan kita akan membuatnyandalam class yang menanganinya. Ketika kita perlu membuat perubahan dalam class yang memiliki tanggung jawab yang lebih, perubahan dapat mempengaruhi fungsi lain dari class.
Single Responsibility Principle adalah prinsip sederhana dan intuitif, tetapi dalam prakteknya kadang-kadang sulit untuk bisa melakukannya dengan benar.
MaksudSebuah class harus memiliki satu alasan untuk berubah.
Contoh
Mari kita asumsikan kita perlu obyek untuk melayani pesan email. Kita akan menggunakan interface IEmail dari contoh di bawah. Pada pandangan pertama semuanya terlihat baik-baik saja. Dengan melihat lebih dekat kita dapat melihat bahwa kami interface IEmail dan class Email memiliki 2 tanggung jawab (alasan untuk berubah). Yang pertama penggunaan class untuk melayani beberapa protokol email seperti pop3 atau imap. Jika protokol lain harus dapat didukung, obyek harus terserialisasi dengan cara lain dan kode harus ditambahkan untuk mendukung protokol baru. Yang kedua untuk mengelola field Konten. Bahkan jika konten adalah sebuah string mungkin kita ingin di masa depan untuk mendukung format HTML atau lainnya.
Jika kita tetap hanya satu class, setiap perubahan untuk sebuah tanggung jawab mungkin akan mempengaruhi yang lain: Menambahkan sebuah protokol baru akan menciptakan kebutuhan
untuk menambahkan kode untuk melakukan parsing dan serialisasi konten untuk setiap jenis field.
Menambahkan jenis konten baru (seperti html) membuat kita menambahkan kode untuk setiap protokol yang sudah diimplementasikan.
// single responsibility principle - bad exampleinterface IEmail {
public void setSender(String sender);public void setReceiver(String receiver);public void setContent(String content);
}
class Email implements IEmail {public void setSender(String sender) {// set sender; }public void setReceiver(String receiver) {// set receiver; }public void setContent(String content) {// set content; }
}
Kita bisa membuat interface baru dan class yang disebut IContent dan Content untuk membagi tanggung jawab. Setelah hanya satu tanggung jawab untuk masing-masing class memberi kita desain yang lebih fleksibel: menambahkan protokol baru menyebabkan perubahan hanya di class
Email. menambahkan jenis konten baru yang didukung menyebabkan
perubahan hanya di class Conten.
// single responsibility principle - good exampleinterface IEmail {
public void setSender(String sender);public void setReceiver(String receiver);public void setContent(IContent content);
}
interface IContent {public String getAsString(); // used for serialization
}
class Email implements IEmail {public void setSender(String sender) {// set sender; }public void setReceiver(String receiver) {// set receiver; }public void setContent(IContent content) {// set content; }
}
Kesimpulan
Single Responsibility Principle merupakan cara yang baik untuk mengidentifikasi class selama fase desain sebuah aplikasi dan itu mengingatkan Anda untuk memikirkan semua cara class dapat berkembang. Sebuah pemisahan dari tanggung jawab yang baik ini dilakukan bila gambaran lengkap tentang bagaimana aplikasi harus bekerja dengan baik dimengerti.
I.2.5 Liskov's Substitution Principle(LSP)
MotivasiSepanjang waktu kita merancang modul program dan membuat beberapa hierarki class. Kemudian kita memperluas beberapa class, membuat beberapa classs turunan.
Kita harus memastikan bahwa class turunan baru hanya diperluas tanpa mengganti fungsi lama class. Kalau tidak, class-class baru dapat menghasilkan efek yang tidak diinginkan ketika mereka digunakan dalam modul program yang ada.
Likov's Substitution Principle ini menyatakan bahwa jika modul program menggunakan Base class, maka referensi ke Base class bisa diganti dengan class turunanya tanpa mempengaruhi fungsi dari modul program.
MaksudDerived types harus benar-benar disubstitusikan untuk base types nya.
Contoh
Di bawah ini adalah contoh klasik yang melanggar Likov's Substitution Principle. Dalam contoh ini ada 2 class yang digunakan: Rectangle dan Square. Kita asumsikan bahwa objek Rectangle digunakan di suatu tempat di dalam aplikasi. Kita memperluas aplikasi dan menambahkan class Square. Class Square dikembalikan oleh factory pattern, didasarkan pada beberapa kondisi dan kita tidak tahu persis apa jenis objek yang akan dikembalikan. Tapi kita tahu itu Rectangle. Kita mendapatkan (get) objek rectangle, mengatur (set) lebar (width) sampai 5 dan tinggi (height) sampai 10 dan mendapatkan (get) area untuk rectangle dengan lebar (width) 5 dan tinggi (height) 10 luas area nya menjadi 50. Sebaliknya, hasilnya menjadi 100
// Violation of Likov's Substitution Principleclass Rectangle{
protected int m_width;protected int m_height;
public void setWidth(int width){m_width = width;
}
public void setHeight(int height){m_height = height;
}
public int getWidth(){return m_width;
}
public int getHeight(){return m_height;
}
public int getArea(){return m_width * m_height;
}}
class Square extends Rectangle {
public void setWidth(int width){m_width = width;m_height = width;
}
public void setHeight(int height){m_width = height;m_height = height;
}
}
class LspTest{
private static Rectangle getNewRectangle(){
// it can be an object returned by some factory ... return new Square();
}
public static void main (String args[]){
Rectangle r = LspTest.getNewRectangle();
r.setWidth(5);r.setHeight(10);// user knows that r it's a rectangle. // It assumes that he's able to set the width and height as for the
base class
System.out.println(r.getArea());// now he's surprised to see that the area is 100 instead of 50.
}}
Kesimpulan
Prinsip ini hanyalah perpanjangan dari Open Close Principle dan itu berarti kita harus memastikan bahwa derived (turunan) class baru memperpanjang base class tanpa mengubah perilaku mereka.
II. Creational PatternsII.1 Singleton Pattern
Motivasi
Kadang-kadang penting untuk memiliki hanya satu instance untuk satu class. Misalnya, dalam suatu sistem harus ada hanya satu window manager (atau hanya satu sistem file atau hanya satu print spooler). Biasanya singletons digunakan untuk manajemen sumber daya internal atau eksternal terpusat dan menyediakan akses global untuk diri mereka sendiri.
Singleton pattern merupakan salah satu design patterns paling sederhana: hanya melibatkan satu class yang responsible terhadap instance nya sendiri, untuk memastikan itu ciptakan tidak lebih dari satu instance, di saat yang sama memberikan sebuah akses global ke instance tersebut. Dalam kasus ini instance yang sama dapat digunakan dari mana-mana, karena tidak mungkin untuk memanggil konstruktor langsung setiap waktu.
Maksud
Memastikan bahwa hanya satu instance dari class yang dibuat. Memberikan global point untuk mengakses objek.
Implementasi
Implementasi melibatkan static member di dalam class "Singleton", sebuah private constructor dan sebuah static public method yang mengembalikan reference ke static member.
Singleton Pattern mendefinisikan operasi getInstance yang memperlihatkan instance unik yang diakses oleh Client. getInstance() bertanggung jawab untuk menciptakan instance class unik dalam kasus ini tidak dibuat dan untuk mengembalikan instance tersebut.
public static synchronized Singleton getInstance()
{if (instance == null)
instance = new Singleton();
return instance;}...public void doSomething(){
...}
}
Anda dapat melihat pada kode di atas bahwa metode getInstance memastikan hanya satu instance dari class yang dibuat. Konstruktor tidak boleh diakses dari luar class untuk memastikan satu-satunya cara instansiasi class hanya melalui metode getInstance.
Metode getInstance digunakan juga untuk memberikan akses dari luar ke objek dan dapat digunakan dengan cara berikut:
Singleton.getInstance().doSomething();
Contoh & Penerapan
Menurut definisi singleton pattern digunakan bila harus ada tepat satu instance dari class, dan ketika itu harus dapat diakses oleh Client dari titik akses global. Berikut adalah beberapa situasi nyata di mana singleton digunakan:
Contoh 1 - Logger Classes
Singleton pattern digunakan dalam desain class logger. Class ini umumnya diimplementasikan sebagai singleton, dan menyediakan global logging access point dalam semua komponen aplikasi tanpa perlu untuk menciptakan sebuah objek setiap kali operasi logging dilakukan.
Contoh 2 - Configuration Classes
Singleton pattern digunakan untuk merancang class yang menyediakan pengaturan konfigurasi untuk aplikasi. Dengan menerapkan configuration class sebagai Singleton tidak hanya menyediakan sebuah global access point, tapi kita juga tetap menggunakan instance sebagai cache dari objek. Ketika class sudah diinstansiasi (atau ketika nilai dibaca) singleton akan menjaga nilai-nilai dalam struktur internal. Jika nilai-nilai yang dibaca dari database atau dari file ini menghindari reload nilai setiap kali parameter konfigurasi digunakan.
Contoh 3 - Accesing resources in shared mode
Hal ini dapat digunakan dalam desain sebuah aplikasi yang perlu untuk bekerja dengan port serial. Mari kita mengatakan bahwa ada banyak class dalam aplikasi, bekerja di lingkungan multi-threading, yang harus melakukan operasi pada port serial. Dalam kasus ini singleton dengan synchronized methods dapat digunakan untuk mengelola semua operasi pada port serial.
Contoh 4 - Factories implemented as Singletons
Mari kita asumsikan merancang aplikasi dengan sebuah factory untuk generate objek baru (Acount, Customer, Site, Address) dengan id mereka, dalam lingkungan multithreading. Jika factory telah diinstansiasi dua kali dalam 2 thread yang berbeda maka memungkinkan untuk memiliki 2 id tumpang tindih untuk 2 objek yang berbeda. Jika kita menerapkan Factory sebagai singleton kita menghindari masalah ini. Menggabungkan Abstract Factory atau Factory Method dan Singleton design patterns adalah praktek yang umum.
Implementasi dan Masalah Spesifik
Thread-safe diimplementasikan untuk penggunaan multi-threading.
Sebuah implementasi robust singleton harus dapat bekerja dalam kondisi apapun. Inilah sebabnya mengapa kita perlu memastikan untuk dapat bekerja ketika multiple threads menggunakannya. Seperti yang terlihat pada contoh sebelumnya singleton dapat digunakan khususnya dalam aplikasi multi-threaded untuk memastikan proses reads/writes tersinkronisasi.
Lazy instantiation menggunakan double locking mechanism.
Penerapan standar ditunjukkan dalam kode di atas adalah implementasi thread safe, tapi itu bukan penerapan terbaik thread-safe karena sinkronisasi sangat mahal ketika kita berbicara tentang kinerja. Kita bisa melihat bahwa metode getInstance sudah tersinkronisasi tidak perlu lagi diperiksa untuk sinkronisasi setelah objek diinisialisasi. Jika kita melihat bahwa objek singleton sudah dibuat kita hanya perlu mengembalikannya tanpa menggunakan syncronized block. Optimasi ini terdiri dari pemeriksaan di dalam sebuah unsynchronized block jika objek bernilai null dan jika tidak untuk diperiksa lagi dan menciptakannya dalam synchronized block. Ini disebut mekanisme penguncian ganda (double locking mechanism).
Dalam kasus ini singleton instance dibuat ketika metode getInstance () dipanggil untuk pertama kalinya. Ini disebut lazy instantiation dan memastikan bahwa singleton instance dibuat hanya bila diperlukan.
//Lazy instantiation using double locking mechanism.class Singleton{
System.out.println("getInstance(): First time getInstance was invoked!");
instance = new Singleton();}
} }
return instance;}
public void doSomething(){
System.out.println("doSomething(): Singleton does something!");}
}
Diskusi lebih detail (double locking mechanism) dapat ditemukan di http://www-128.ibm.com/developerworks/java/library/j-dcl.html?loc=j
Awal Instansiasi menggunakan implementasi dengan static field.
Dalam implementattion berikut obyek singleton diinstansiasi ketika class tersebut dimuat dan tidak ketika pertama kali digunakan, karena faktanya instance member dideklarasikan static. Inilah sebabnya mengapa kita tidak perlu untuk menyinkronkan setiap bagian dari kode dalam kasus ini. Class hanya dimuat sekali, ini menjamin uniquity dari objek.
Singleton - Contoh sederhana (java)
//Early instantiation using implementation with static field.class Singleton{
private static Singleton instance = new Singleton();
System.out.println("doSomething(): Singleton does something!");}
}
Protected constructor
Hal ini dimungkinkan untuk menggunakan protected constructor dalam rangka untuk memungkinkan subclassing dari singleton tersebut. Teknik ini memiliki 2 kelemahan yang membuat pewarisan singleton praktis: Pertama-tama, jika konstruktor adalah protected, itu berarti bahwa class dapat
instantiated dengan memanggil konstruktor dari class lain dalam paket yang sama. Sebuah solusi untuk menghindarinya adalah dengan membuat paket terpisah untuk singleton.
Kedua, untuk menggunakan class turunan semua panggilan getInstance harus diubah dalam kode yang ada dari Singleton.getInstance () untuk NewSingleton.getInstance ().
Multiple singleton instances jika class dimuat oleh classloaders yang berbeda mengakses sebuah singleton.
Jika sebuah class (nama yang sama, paket yang sama) yang dimuat oleh 2 classloaders yang berbeda, mereka mewakili 2 class yang berbeda di memori.
Serialization
Jika class Singleton mengimplementasikan interface java.io.Serializable, ketika singleton telah diserialisasi dan kemudian deserialized lebih dari sekali, ini akan ada multiple instances Singleton yang dibuat. Untuk menghindari hal ini metode
readResolve harus diimplementasikan. Lihat Method Serializable() dan readResolve() dalam javadocs.
public class Singleton implements Serializable {...
// This method is called immediately after an object of this class is deserialized.
// This method returns the singleton instance.protected Object readResolve() {
return getInstance();}
}
Abstract Factory dan Factory Methods diimplementasikan sebagai singleton.
Ada situasi tertentu ketika sebuah factory harus unik. Memiliki 2 factory mungkin memiliki efek yang tidak diinginkan ketika objek diciptakan. Untuk memastikan factory yang unik, maka harus diimplementasikan sebagai singleton. Dengan demikian kita juga menghindari untuk instansiasi class sebelum menggunakannya.
Hot Spot:
Multithreading - Sebuah perawatan khusus harus diambil ketika singleton harus digunakan dalam aplikasi multithreading.
Serialization - Ketika singleton mengimplementasikan interface Serializable harus implementasikan metode readResolve untuk menghindari memiliki 2 objek yang berbeda.
Classloaders - Jika class Singleton diload oleh 2 class loader yang berbeda maka akan memiliki 2 class yang berbeda, satu untuk setiap class loader.
Global Access Point represented by the class name – Instance singleton diperoleh dengan menggunakan nama class. Pada pandangan pertama terlihat sebuah cara yang mudah untuk mengaksesnya, tetapi sangat tidak fleksibel. Jika kita perlu mengganti class Sigleton, semua references dalam kode harus diubah untuk menyesuaikan.
II.2 Factory Pattern
Motivasi
Factory Design Pattern mungkin adalah pattern desain yang paling banyak digunakan dalam bahasa pemrograman modern seperti Java dan C #. Muncul dalam berbagai varian dan implementasi. Jika Anda mencari tentang itu,
kemungkinan besar, Anda akan menemukan referensi tentang GoF patterns: Factory Method dan Abstract Factory. Dalam artikel ini akan menggambarkan sebuah rasa dari factory pattern yang umum digunakan saat ini. Anda juga dapat mengecek original Factory Method yang sangat mirip.
Maksud
menciptakan objek tanpa memaparkan logika Instansiasi ke Client. mengacu pada objek yang baru dibuat melalui interface umum
Implementasi
Implementasi yang sangat sederhana Client membutuhkan Product, tapi bukannya menciptakan langsung
menggunakan operator new, ia meminta obyek factory untuk Product baru, memberikan informasi tentang jenis objek yang dibutuhkan.
Factory menginstansiasi sebuah new concrete product dan kemudian mengembalikan ke Client the newly created product (menolak abstract product class).
Client menggunakan Product sebagai Product abstrak tanpa mengetahui tentang implementasi konkret mereka.
Contoh & Penerapan
Mungkin factory pattern merupakan salah satu pattern yang paling sering digunakan.
Misalnya aplikasi grafis bekerja dengan shapes. Dalam implementasi kita drawing framework adalah client dan shapes adalah product. Semua shapes berasal dari abstract shape class (atau interface). Class Shape mendefinisikan operasi draw dan move yang harus diimplentasikan oleh concrete shapes. Mari kita asumsikan perintah dipilih dari menu untuk membuat lingkaran baru. Framework tersebut menerima shape type sebagai parameter string, itu meminta factory untuk menciptakan shape baru mengirimkan parameter yang diterima dari menu. Factory menciptakan circle baru dan mengembalikannya ke framework, dilempar dengan bentuk abstrak. Kemudian framework menggunakan objek sebagai lemparan ke class abstrak tanpa menyadari jenis objek concret.
Keuntungannya jelas: bentuk baru dapat ditambahkan tanpa mengubah satu baris pun kode dalam framework (kode Client yang menggunakan bentuk dari factory). Seperti yang terlihat pada bagian selanjutnya, ada implementasi factory tertentu yang memungkinkan penambahan Product baru tanpa memodifikasi class factory.
Mereka juga dikenal sebagai parameterized Factories. Menghasilkan metode yang dapat ditulis sehingga dapat menghasilkan lebih banyak jenis obyek Product, menggunakan pengkondisian (dengan masukan sebagai parameter metode atau membaca dari beberapa parameter konfigurasi global - lihat pattern factory abstract pattern) untuk mengidentifikasi jenis objek yang harus dibuat , sebagai berikut:
public class ProductFactory{public Product createProduct(String ProductID){
if (id==ID1)return new OneProduct();
if (id==ID2) returnreturn new AnotherProduct();
... // so on for the other Ids
return null; //if the id doesn't have any of the expected values } ...}
Implementasi ini adalah yang paling sederhana dan intuitif (Mari kita sebut saja noob implementation (implementasi pemula)). Disini masalahnya adalah sekali kita tambahkan concrete product call kita harus memodifikasi class factory. Hal ini sangat tidak fleksibel dan melanggar open close principle. Tentu saja kita bisa membuat subclass dari factory class, tetapi jangan lupa bahwa class factory biasanya digunakan sebagai singleton. Subclassing itu berarti mengganti semua referensi class factory di mana-mana melalui kode.
Class Registration - avoiding reflection
Jika Anda dapat menggunakan refleksi, misalnya di Java atau bahasa .NET, Anda dapat mendaftarkan class product baru ke factory tanpa mengubah factory itu sendiri. Untuk membuat objek di dalam class factory tanpa mengetahui jenis objek kita membuat sebuah pemetaan antara productID dan jenis class product. Dalam kasus ini ketika sebuah product baru ditambahkan ke aplikasi itu harus didaftarkan ke factory. Operasi ini tidak memerlukan perubahan kode class factory.
class ProductFactory{
private HashMap m_RegisteredProducts = new HashMap();
public void registerProduct (String productID, Class productClass){
Kita bisa menempatkan kode registrasi di manapun dalam kode kita, tapi tempat yang cocok adalah di dalam class product dalam konstruktor statis. Lihatlah contoh di bawah ini:
1. Pendaftaran dilakukan di luar class Product:
public static void main(String args[]){Factory.instance().registerProduct("ID1", OneProduct.class);
Kita harus memastikan bahwa concrete product classes sudah dimuat sebelum mereka diminta oleh factory untuk pendaftaran (jika mereka tidak dimuat mereka tidak akan didaftarkan di factory dan createProduct akan mengembalikan nilai null). Untuk memastikan itu kita akan menggunakan metode Class.forName tepat di bagian static dari main class. Bagian ini dijalankan tepat setelah main class di-load. Class.forName seharusnya mengembalikan instance class dari class yang ditunjukkan. Jika class tidak dimuat oleh kompilator sama sekali, itu akan dimuat saat Class.forName ini dipanggil. Akibatnya blok static di class masing-masing akan dieksekusi ketika masing-masing class dimuat:
Implementasi refleksi memiliki kelemahan tersendiri. Yang utama adalah kinerja. Ketika refleksi digunakan kinerja pada kode yang melibatkan refleksi dapat menurunkan sampai 10% dari poerfomance dari kode non reflection. Masalah lainnya adalah bahwa tidak semua bahasa pemrograman menyediakan mekanisme refleksi.
Class Registration - avoiding reflection
Seperti kita lihat dalam paragraf sebelumnya obyek factory menggunakan internal HashMap untuk membuat pemetaan antara parameter (dalam kasus kita adalah Strings) dan concrete products class. Pendaftaran dibuat dari luar factory karena objek diciptakan menggunakan refleksi factory yang tidak mengetahui jenis objectnya.
Kita tidak ingin menggunakan refleksi tapi di saat yang sama kita ingin memiliki mengurangi coupling antara factory dan concrete products. Sejak factory harus tidak mengetahui product, kita harus memindahkan penciptaan object di luar factory untuk mengetahui object dari concrete product classes. Itu akan menjadi concrete class nya sendiri.
Kita menambahkan metode abstrak baru di class product abstrak. Setiap class concrete akan menerapkan metode ini untuk membuat objek baru dari jenis yang sama seperti dirinya sendiri. Kami juga harus mengubah metode registration tersebut bahwa kita akan meregistrasi obyek Product concrete bukan objek class.
abstract class Product{
public abstract Product createProduct();...
}
class OneProduct extends Product{
...static{
ProductFactory.instance().registerProduct("ID1", new OneProduct());
}public OneProduct createProduct(){
return new OneProduct();}...
}
class ProductFactory{
public void registerProduct(String productID, Product p) {m_RegisteredProducts.put(productID, p);
A more advanced solution - Factory design pattern with abstractions (Factory Method)
Implementasi ini merupakan alternatif untuk impelentasi class registration. Mari kita asumsikan kita perlu menambahkan Product baru ke aplikasi. Untuk implementasi switch-case prosedural kita perlu mengubah class Factory, sedangkan dalam implementasi class registration, kita butuh untuk mendaftarkan class untuk factory tanpa memodifikasi class factory sedikitpun. Untuk meyakinkan ini adalah solusi yang fleksibel.
Implementasi prosedural adalah contoh kasil yang buruk untuk Open-Close Principle. Seperti yang bisa kita lihat ada solusi yang paling intuitif untuk menghindari memodifikasi class Factory untuk memperluasnya.
Ini adalah implementasi klasik dari factory method pattern. Ada beberapa kekurangan atas implementasi class registration dan tidak beigut banyak keuntungan:
+ derived factory method dapat diubah untuk melakukan operasi tambahan ketika objek diciptakan (mungkin beberapa inisialisasi berdasarkan beberapa parameter global ...).
- Factory tidak dapat digunakan sebagai singleton. - Setiap factory harus diinisialisasi sebelum menggunakannya. - Lebih sulit untuk diterapkan. - Jika objek baru harus ditambahkan sebuah factory baru harus diciptakan.
Pokoknya, implementasi klasik ini memiliki keuntungan bahwa itu akan membantu kita memahami Abstract Factory design pattern.
Kesimpulan
Bila Anda merancang aplikasi hanya berpikir jika Anda benar-benar membutuhkan sebuah factory untuk membuat objek. Mungkin menggunakannya akan membawa kompleksitas yang tidak perlu dalam aplikasi Anda. Jika Anda memiliki banyak object dari base type yang sama dan Anda memanipulasi mereka sebagian besar casted to abstract types, maka Anda perlu sebuah factory. Jika Anda harus memiliki banyak kode seperti berikut kode ini, Anda harus mempertimbangkan kembali itu:
Jika Anda memutuskan menggunakan factory, saya akan merekomendasikan menggunakan salah satu class registration implementations (dengan atau tanpa refleksi) dan untuk menghindari Metode Factory (Factory design pattern dengan abstractions). Harap dicatat implementasi switch-case prosedural (noob) adalah paling sederhana, melanggar prinsip OCP hanya digunakan untuk menjelaskan teori. Satu-satunya cara bijaksana untuk menggunakannya adalah untuk modul sementara sampai diganti dengan factory yang nyata.
II.3 Factory Method PatternMotivasi
Dikenal juga sebagai Virtual Constructor, Factory Method berkaitan dengan gagasan di mana libraries bekerja: library menggunakan abstract class untuk mendefinisikan dan memelihara relasi antara objek. Salah satu jenis responsibility nya adalah membuat object. Library diketahui ketika suatu objek perlu dibuat,
tetapi tidak tahu jenis dari objek yang harus membuat, ini menjadi spesifik untuk aplikasi menggunakan library.
Factory method bekerja dengan cara yang sama: ia mendefinisikan sebuah interface untuk menciptakan objek, tetapi membuat pilihan dari tipenya pada subclass, penciptaan yang ditangguhkan pada saat run-time. Sebuah contoh kehidupan nyata sederhana Metode Factory adalah hotel. Ketika ingin tinggal di sebuah hotel pertama Anda harus check in. Orang yang bekerja di front-desk akan memberi Anda kunci untuk ruangan Anda setelah Anda membayar untuk ruangan yang Anda inginkan dan cara ini ia dapat dipandang sebagai sebuah ruang factory. Sementara tinggal di hotel, Anda mungkin perlu untuk melakukan panggilan telepon, sehingga Anda menelepon meja depan dan orang di sana akan menghubungkan Anda dengan nomor yang Anda butuhkan, menjadi sebuah “panggilan telepon” factory, karena ia mengontrol akses ke panggilan , juga.
Maksud
Mendefinisikan sebuah interface untuk menciptakan objek, tetapi membiarkan subclass untuk menentukan class untuk instansiasi
Mengacu pada objek yang baru dibuat melalui interface umum
Implementasi
Pattern dasarnya bekerja seperti yang ditunjukkan di bawah ini, dalam diagram UML:
Class peserta dalam pattern ini adalah: Product mendefinisikan interface untuk objek yang dibuat metode factory. ConcreteProduct mengimplementasikan interface Product. Creator (juga disebut sebagai factory karena ia menciptakan obyek Product)
menyatakan metode FactoryMethod, yang mengembalikan objek Product. Mungkin memanggil generating metode untuk membuat objek Product
ConcreteCreator overrides the generating method untuk membuat objek ConcreteProduct
Semua concrete products adalah subclass dari class Product, sehingga semua dari mereka memiliki implementasi dasar yang sama, pada batas tertentu. Class Creator menentukan semua behavior standar dan generik dari Product dan ketika sebuah Product baru dibutuhkan, ia akan mengirimkan rincian penciptaan yang disediakan oleh Client untuk ConcreteCreator tersebut. Setelah diagram ini sudah di dalam pikiran, mudah bagi kita sekarang untuk menghasilkan kode yang berhubungan dengan itu. Berikut adalah bagaimana implementasi metode factory klasik akan terlihat:
public interface Product { � }
public abstract class Creator {
public void anOperation() {
Product product = factoryMethod();}
protected abstract Product factoryMethod();}
public class ConcreteProduct implements Product { � }
public class ConcreteCreator extends Creator {
protected Product factoryMethod() {
return new ConcreteProduct();}
}
public class Client {
public static void main( String arg[] ) {
Creator creator = new ConcreteCreator();creator.anOperation();
}
}Contoh dan Penerapan
Kebutuhan untuk menerapkan Metode Factory sangat sering. Kasus nya seperti salah satunya di bawah ini: ketika class tidak dapat mengetahui lebih dulu tipe dari objek ingin dibuat ketika sebuah class ingin subclass untuk menjadi satu-satunya tipe spesifik dari
objek yang baru dibuat
Contoh 1 - Document Application.
Mempertimbangkan framework untuk aplikasi desktop. Aplikasi ini dimaksudkan untuk bekerja dengan document. Sebuah framework untuk aplikasi desktop terdapat definisi untuk operasi seperti opening, creating dan saving sebuah document. Class-class dasar yang abstrak, bernama Application dan Document, Client mereka harus membuat subclass dari mereka dalam rangka untuk menentukan aplikasi mereka sendiri. Untuk menghasilkan sebuah aplikasi menggambar, misalnya, mereka harus menentukan class DrawingApplication dan DrawingDocument. Class Aplication mempunyai tugas mengelola document, mengambil tindakan atas permintaan Client (misalnya, ketika pengguna memilih perintah open atau save dari menu).
Karena class Document perlu untuk diinstansiasi untuk aplikasi yang spesifik, class Application tidak tahu itu sebelumnya, sehingga tidak tahu apa yang harus diinstansiasi, tidak tahu kapan harus instantiate itu. Framework ini perlu instantiate class tertentu, tetapi hanya tahu class abstrak yang tidak dapat instantiated.
Factory Method design pattern memecahkan masalah dengan menempatkan semua informasi yang berkaitan dengan class yang perlu instantiated menjadi objek dan menggunakan mereka di luar framework, seperti yang Anda lihat di bawah ini
Dalam class Application metode CreateDocument baik memiliki implementasi default atau tidak memiliki implementasi sama sekali, operasi ini sedang didefinisikan ulang dalam subclass MyApplication sehingga membuat objek MyDocument dan mengembalikan referensi untuk itu.
public Document CreateDocument(String type){if (type.isEqual("html"))
return new HtmlDocument();if (type.isEqual("proprietary"))
return new MyDocument();if (type.isEqual("pdf"))
return new PdfDocument ();}
Asumsikan class Application memiliki anggota disebut docs yang merupakan list dari document yang ditangani oleh aplikasi, maka metode NewDocument akan terlihat seperti ini:
public void NewDocument(String type){Document doc=CreateDocument(type);Docs.add(doc);Docs.Open();
}
Metode ini akan diwariskan oleh class MyApplication dan, sehingga, melalui metode CreateDocument, itu benar-benar akan instantiate objek MyDocument. Kita akan memanggil metode CreateDocument sebuah Method Factory karena bertanggung jawab dengan 'membuat' objek. Melalui metode ini, didefinisikan kembali dalam subclass Aplikasi, kita benar-benar dapat membentuk situasi di mana class Application menciptakan objek tanpa mengetahui jenis mereka. Dari sudut pandang ini metode factory adalah pattern yang memberikan kita cara untuk mencapai DIP principle.
Implementasi dan Spesifik Masalah
Ketika mengimplementasi Factory Method design pattern beberapa masalah yang dapat muncul:
Definisi dari Creator class
Jika kita menerapkan pattern ini ke kode yang sudah ditulis mungkin ada masalah dengan cara kita memiliki Creator class sudah ditetapkan. Ada dua kasus: class Creator adalah abstrak dan mengenerate metode yang tidak memiliki
implementasi apapun. Dalam hal ini class ConcreteCreator harus mendefinisikan metode generasi mereka sendiri dan situasi ini biasanya
muncul dalam kasus-kasus di mana class Creator tidak dapat memprediksi apa yang akan diinstantiate ConcreteProduct.
Class Creator adalah class concrete, menghasilkan metode yang memiliki implementasi default. Jika ini terjadi, class ConcreteCreator akan menggunakan generating metode untuk fleksibilitas ketimbang untuk generasi. Programmer akan selalu dapat memodifikasi class objek yang class Creator implisit menciptakan, mendefinisikan ulang metode generasi.
Metode Factory hanya kasus tertentu dari factory design pattern. Pada saat yang sama itu adalah factory pattern yang paling dikenal, mungkin karena diterbitkan di GoF. Dalam bahasa pemrograman modern factory dengan registration lebih digunakan.
Kekurangan dan Manfaat
Berikut adalah manfaat dan kelemahan dari factory method pattern: + Alasan utama factory pattern yang digunakan adalah bahwa ia
memperkenalkan pemisahan antara aplikasi dan keluarga class (memperkenalkan kopling lemah bukan kopling ketat menyembunyikan class konkret dari aplikasi). Ini menyediakan cara sederhana untuk memperluas keluarga products dengan perubahan kecil dalam kode aplikasi.
+ Ini menyediakan kait kustomisasi. Jika objek yang dibuat secara langsung di dalam class sulit untuk menggantikan mereka dengan objek yang memperpanjang fungsi mereka. Daripada factory digunakan untuk menciptakan sebuah keluarga objek, customisasi objek dapat dengan mudah menggantikan objek aslinya, konfigurasikan factory untuk menciptakan mereka.
- Factory harus digunakan untuk keluarga object. Jika class tidak memperpanjang common base class atau interface mereka tidak dapat digunakan dalam template desain factory.
Hot Poin:
Metode factory adalah salah satu yang paling digunakan dan salah satu desain pattern yang lebih kuat. Hanya ada beberapa poin yang harus dipertimbangkan ketika Anda menerapkan metode factory.
Bila Anda merancang aplikasi hanya berpikir jika Anda benar-benar membutuhkannya sebuah factory untuk membuat objek. Mungkin menggunakannya akan membawa kompleksitas yang tidak perlu dalam aplikasi Anda. Namun demikian, jika Anda memiliki banyak objek dari tipe dasar yang sama dan Anda memanipulasi mereka kebanyakan sebagai obyek abstrak, maka Anda perlu sebuah factory. Code anda harus punya banyak kode seperti berikut, pertimbangkan ini.
if (genericProduct typeof ConcreteProduct)((ConcreteProduct)genericProduct).doSomeConcreteOperation();
II.4 Abstract FactoryMotivasi
Modularisasi adalah isu besar dalam pemrograman saat ini. Programmer di seluruh dunia sedang mencoba untuk menghindari gagasan menambahkan kode ke class yang ada dalam rangka untuk membuat mereka mendukung encapsulasi informasi yang lebih umum. Ambil kasus seorang manajer informasi yang mengelola nomor telepon. Nomor telepon memiliki aturan tertentu di mana mereka bisa dihasilkan tergantung pada wilayah dan state. Jika di beberapa titik aplikasi harus diubah dalam rangka mendukung menambahkan angka membentuk state baru, kode aplikasi harus diubah dan itu akan menjadi lebih rumit.
Untuk mencegah hal itu, Abstract Factory design pattern digunakan. Menggunakan pattern ini, sebuah framework didefinisikan, yang menghasilkan objek-objek yang mengikuti pattern umum dan pada saat runtime factory ini dipasangkan dengan concrete factory untuk memProductsi objek-objek yang mengikuti pattern dari tempat tertentu. Dengan kata lain, Abstract Factory adalah super-factory yang menciptakan factories lain (factory of factories).
Maksud
Abstract Factory menawarkan interface untuk menciptakan sebuah keluarga obyek yang terkait, tanpa secara eksplisit menentukan class mereka.
Implementasi
Pattern basically bekerja seperti yang ditunjukkan di bawah ini, dalam diagram UML:
Class-class yang berpartisipasi Abstract Factory pattern di atas adalah:AbstractFactory - mendeklarasikan interface untuk operasi yang menciptakan abstract products.ConcreteFactory - mengimplementasikan operasi untuk menciptakan concrete products.AbstractProduct - mendeklarasikan sebuah interface untuk jenis objek product.Product - mendefinisikan Product yang akan dibuat oleh ConcreteFactory yang sesuai, menerapkan interface AbstractProduct.Client - menggunakan interface yang telah dideklarasikan oleh class AbstractFactory dan AbstractProduct.
Class AbstractFactory adalah salah satu yang menentukan jenis sebenarnya dari concrete object dan menciptakannya, tetapi mengembalikan sebuah abstract pointer ke concrete object yang baru saja dibuat. Ini menentukan perilaku client yang meminta factory untuk membuat sebuah objek dari tipe abstrak tertentu dan mengembalikan pointer abstrak untuk itu, menjaga client dari mengetahui apa-apa tentang penciptaan sebenarnya dari objek.
Fakta bahwa factory mengembalikan sebuah abstract pointer ke objek yang telah diciptakan, berarti bahwa client tidak memiliki pengetahuan tentang objek type nya. Ini berarti bahwa tidak ada kebutuhan untuk including deklarasi class apapun yang berkaitan dengan concrete type, client berurusan setiap saat dengan abstract type. Obyek dari concrete type yang dibuat oleh factory, diakses oleh client hanya melalui interface abstrak.
Implikasi kedua dari cara membuat objek adalah ketika menambahkan concrete type baru dibutuhkan, yang harus kita lakukan adalah memodifikasi kode client dan membuatnya menggunakan factory yang berbeda, yang jauh lebih mudah daripada instantiating type baru, yang mengharuskan mengubah kode di manapun objek baru dibuat.
Impelentasi klasik untuk Abstract Factory pattern adalah sebagai berikut:
abstract class AbstractProductA{public abstract void operationA1();public abstract void operationA2();
}
class ProductA1 extends AbstractProductA{ProductA1(String arg){
public static void main(String args[]){AbstractFactory pf=FactoryMaker.getFactory("a");AbstractProductA product=pf.createProductA();//more function calls on product
}}
Penerapan & Contoh
Kita harus menggunakan Abstract Factory design pattern ketika: sistem harus independen dari cara kerja product yang dibuat. sistem ini atau harus dikonfigurasi untuk bekerja dengan multiple families of
products. keluarga product dirancang hanya untuk bekerja bersama-sama. creation dari sebuah perpustakaan product dibutuhkan, yang relevan hanya
interface, bukan implementasi, juga.
Contoh Phone Number
Contoh pada awal artikel dapat ditambahkan dengan alamat, juga. Class AbstractFactory akan berisi metode untuk membuat entri baru dalam information manager untuk nomor telepon dan alamat, metode yang menghasilkan product abstract Alamat dan PhoneNumber, yang milik AbstractProduct class. Class-class AbstractProduct akan mendefinisikan metode product ini untuk mendukung: untuk alamat get dan set metode untuk jalan, kota, daerah dan anggota kode pos dan nomor telepon get dan set metode untuk nomor.
Class-class ConcreteFactory dan ConcreteProduct akan mengimplementasikan interface yang didefinisikan di atas dan akan muncul dalam contoh kita dalam bentuk class USAddressFactory dan class USAddress dan USPhoneNumber. Untuk setiap state baru yang perlu ditambahkan ke aplikasi, satu set baru class concrete-type akan ditambahkan. Dengan cara ini kita dapat memiliki EnglandAddressFactory, EnglandAddress dan EnglandPhoneNumber yang semuanya adalah file untuk informasi alamat Inggris.
Contoh Pizza Factory
Contoh lain, kali ini lebih sederhana dan mudah dimengerti, adalah salah satu pizza factory, yang mendefinisikan nama metode dan return types untuk membuat berbagai jenis pizza. Abstrak factory dapat diberi nama AbstractPizzaFactory, RomeConcretePizzaFactory dan MilanConcretePizzaFactory menjadi dua ekstensi dari class abstrak. Abstrak factory akan menentukan jenis topping untuk pizza, seperti pepperoni, sosis atau ikan teri, dan beberapa concrete factory akan mengimplementasikan hanya satu set topping, dengan area yang spesifik dan bahkan jika salah satu topping diimplementasikan di kedua concrete factory, pizza yang dihasilkan akan subclass yang berbeda, masing-masing untuk area yang sudah diimplementasikan di dalamnya.
Contoh Look & Feel
Look & Feel Abstract Factory adalah contoh yang paling umum. Misalnya, framework GUI harus mendukung beberapa look and feel tema, seperti Motif dan Windows look. Gaya masing-masing mendefinisikan look dan behavior yang berbeda untuk setiap jenis control: Buttons dan Edit box. Untuk menghindari hardociding untuk setiap jenis kontrol kita mendefinisikan sebuah abstrak LookAndFeel. Panggilan ini akan instantiate, tergantung pada parameter konfigurasi dalam aplikasi dari concrete factories: WindowsLookAndFeel atau MotifLookAndFeel. Setiap permintaan untuk objek baru akan didelegasikan ke instatiated concrete factory yang akan mengembalikan control dengan jenis yang spesifik.
Implementasi dan Masalah Spesifik
Abstract Factory pattern memiliki manfaat dan kekurangan. Di satu sisi, mengisolasi penciptaan objek-objek dari client yang membutuhkan mereka, client hanya diberi kemungkinan untuk mengakses mereka melalui interface, yang membuat manipulasi lebih mudah. Menukar keluarga product lebih mudah, sebagai concrete factory yang muncul hanya dalam kode dimana ia instantiated. Juga jika product dari keluarga dimaksudkan untuk bekerja sama, Abstract Factory memudahkan untuk menggunakan object-object hanya dari satu keluarga pada suatu waktu. Di sisi lain, menambahkan product baru ke factory yang sudah ada itu sulit, karena interface factory abstract menggunakan seperangkat product fix yang dapat dibuat. Itulah mengapa menambahkan Product baru berarti memperluas interface factory, yang melibatkan perubahan di class AbstractFactory dan semua subclass. Bagian ini akan membahas cara-cara menerapkan pattern untuk menghindari masalah yang mungkin muncul.
Factories sebagai singletons
Sebuah aplikasi biasanya membutuhkan hanya satu instance dari class ConcreteFactory per product keluarga. Ini berarti bahwa yang terbaik adalah menerapkannya sebagai Singleton.
Menciptakan Product
Class AbstractFactory hanya menyatakan interface untuk menciptakan Product. Ini adalah tugas dari class ConcreteProduct untuk benar-benar menciptakan Product. Untuk setiap keluarga, ide terbaik adalah menerapkan Factory Method design pattern. Sebuah concrete factory akan menentukan Productnya dengan masing-masing mengoverride metode factory. Bahkan jika implementasinya mungkin tampak sederhana, menggunakan ide ini berarti mendefinisikan subclass concrete factory baru untuk setiap product keluarga, bahkan jika class serupa dalam sebagian besar aspek.
Untuk menyederhanakan kode dan meningkatkan kinerja Prototype design pattern dapat digunakan sebagai pengganti Metode Factory, terutama ketika ada banyak keluarga Product. Dalam hal ini concrete factory diawali dengan prototypical instance dari setiap Product dalam keluarga dan ketika yang baru diperlukan bukannya menciptakan itu, Prototype yang ada di cloning. Pendekatan ini menghilangkan kebutuhan untuk concrete factory baru untuk setiap keluarga baru dari product.
Memperluas factories
Pengoperasian perubahan factory dalam rangka untuk itu untuk mendukung terciptanya Product baru tidak mudah. Apa yang dapat dilakukan untuk mengatasi masalah ini adalah, bukan metode CreateProduct untuk setiap Product, menggunakan sebuah Create method tunggal yang mengambil parameter yang mengidentifikasi jenis Product yang dibutuhkan. Pendekatan ini lebih fleksibel, tapi kurang aman. Masalahnya adalah bahwa semua obyek yang dikembalikan oleh Create method akan memiliki interface yang sama, yaitu salah satu yang sesuai dengan jenis dikembalikan oleh Create method dan Client tidak akan selalu dapat mendeteksi dengan benar untuk class dengan instance yang benar-benar miliknya.
Hot Poin:Class AbstractFactory dideklarasikan hanya sebagai interface untuk menciptakan product. Penciptaan yang sebenarnya adalah tugas class ConcreteProduct, di mana pendekatan yang baik adalah menerapkan Factory Methode design pattern untuk setiap Product dari keluarga.Memperluas factories dapat dilakukan dengan menggunakan satu metode Create untuk semua Product dan melampirkan informasi tentang jenis Product yang dibutuhkan.
II.5 Builder Pattern
Motivasi
Aplikasi yang lebih kompleks adalah kompleksitas class dan objek yang digunakan meningkat. Objek yang kompleks terbuat dari bagian-bagian yang dihasilkan oleh objek-objek lain yang membutuhkan perawatan khusus ketika sedang dibangun. Sebuah aplikasi mungkin perlu sebuah mekanisme untuk membangun objek yang kompleks yang independen dari yang membentuk objeknya. Jika ini adalah masalah yang Anda sedang dihadapkan dengan, Anda mungkin ingin mencoba menggunakan Builder (atau Adaptive Builder) design pattern.
Pattern ini memungkinkan objek client untuk membangun sebuah obyek yang kompleks dengan hanya menetapkan jenis dan isi, yang terlindung dari rincian yang terkait dengan representasi objek. Dengan cara ini proses konstruksi dapat digunakan untuk membuat representasi yang berbeda. Logika proses ini adalah form terisolasi langkah-langkah yang sebenarnya digunakan dalam menciptakan obyek yang kompleks, sehingga proses dapat digunakan kembali untuk membuat form objek yang berbeda, set yang sama dari obyek sederhana seperti yang pertama.
Maksud
Mendefinisikan instance untuk menciptakan objek tetapi membiarkan subclass memutuskan class mana yang di instantiate
Mengacu pada objek yang baru dibuat melalui interface umum
Implementasi
Builder design pattern menggunakan Factory Builder pattern untuk menentukan class concrete untuk memulai untuk membangun tipe objek yang diinginkan, seperti yang akan kita lihat di bawah dalam diagram UML:
Class yang berpartisipasi dalam pattern ini adalah: The Builder class menentukan sebuah interface abstrak untuk membuat
bagian-bagian dari obyek Product. The ConcreteBuilder membangun dan menempatkan bersama-sama bagian
dari Product dengan mengimplementasi interface Builder. Ini mendefinisikan dan melacak representasi itu menciptakan dan menyediakan sebuah interface untuk menyimpan product.
Direktur class mengkonstruksi obyek yang kompleks dengan menggunakan interface Builder.
The Product merupakan obyek yang kompleks yang sedang dibangun.
Client, yang mungkin berupa object lain atau client aktual yang memanggil main() metode aplikasi, inisiasi class Builder dan Director. Builder merupakan obyek yang kompleks yang perlu dibangun dalam object dan type yang lebih sederhana. Konstruktor di class Director menerima objek Builder sebagai parameter dari Client dan bertanggung jawab untuk memanggil metode yang sesuai dari class Builder. Dalam rangka menyediakan Client dengan interface untuk semua concrete Builders, class Builder harus menjadi salah satu abstrak. Dengan cara ini Anda dapat menambahkan jenis baru dari obyek yang kompleks dengan hanya
mendefinisikan struktur dan menggunakan kembali logika untuk proses konstruksi yang sebenarnya. Client adalah satu-satunya yang perlu tahu tentang jenis baru, Director perlu mengetahui metode Builder yang dipanggil.
Contoh berikut ini membahas kasus aplikasi mengkonversi teks:
Client perlu mengkonversi document dari format RTF ke format ASCII. Untuk itu, dia akan memanggil createASCIIText metode yang mengambil sebagai parameter document yang akan dikonversi. Metode ini panggilan concrete builder, ASCIIConverter, yang meng-extends Builder, TextConverter, dan meng-override dua metode untuk mengubah karakter dan paragraf, dan juga Director, RTFReader, yang mem-parsing document dan memanggil metode builder tergantung pada tipe Token yang ditemui. Product, ASCIIText, dibangun langkah demi langkah, dengan menambahkan karakter yang dikonversi.
//Abstract Builderclass abstract class TextConverter{
//This class abstracts the document objectclass Document{
static int value;char token;public char getNextToken(){
//Get the next tokenreturn token;
}}
//Directorclass RTFReader{
private static final char EOF='0'; //Delimitor for End of Filefinal char CHAR='c';final char PARA='p';char t;TextConverter builder;RTFReader(TextConverter obj){
builder=obj;}void parseRTF(Document doc){
while ((t=doc.getNextToken())!= EOF){switch (t){
case CHAR: builder.convertCharacter(t);case PARA: builder.convertParagraph();
}}
}}
//Clientpublic class Client{
void createASCIIText(Document doc){ASCIIConverter asciiBuilder = new ASCIIConverter();RTFReader rtfReader = new RTFReader(asciiBuilder);rtfReader.parseRTF(doc);ASCIIText asciiText = asciiBuilder.getResult();
system.out.println("This is an example of Builder Pattern");}
}
Contoh & Penerapan
Builder Pattern digunakan ketika: Membuat algoritma dari obyek yang kompleks yang independen dari bagian
yang benar-benar membentuk objek Sistem perlu untuk memungkinkan representasi yang berbeda untuk objek
yang sedang dibangun
Contoh 1 - Vehicle Manufacturer.
Mari kita ambil kasus vehicle manufacturer, dari satu set bagian, dapat membangun sebuah mobil, sepeda, sepeda motor atau skuter. Dalam hal ini Builder akan menjadi VehicleBuilder tersebut. VehicleBuilder menentukan interface untuk membangun salah satu kendaraan dalam daftar di atas, dengan menggunakan set dari bagian yang sama dan seperangkat rule yang berbeda untuk setiap jenis kendaraan. ConcreteBuilders akan menjadi builders yang attached pada setiap objects yang sedang dalam pembangunan. Product ini tentu saja vehicle yang sedang dibangun dan Direktur adalah manufacturer dan toko.
Contoh 1 - Students Exams.
Jika kita memiliki sebuah aplikasi yang dapat digunakan oleh para mahasiswa dari Universitas untuk menyediakan daftar nilai ujian mereka, aplikasi ini perlu dijalankan dengan cara yang berbeda tergantung pada pengguna yang menggunakannya, pengguna tersebut yang harus log in. Ini berarti bahwa misalnya, admin harus memiliki beberapa tombol diaktifkan, tombol yang perlu dinonaktifkan untuk mahasiswa, pengguna umum. Builder menyediakan interface untuk membangun form tergantung pada informasi login. Para ConcreteBuilders adalah bentuk spesifik untuk setiap jenis pengguna. Product ini adalah form akhir bahwa aplikasi akan digunakan dalam kasus tertentu dan Direktur adalah aplikasi yang, berdasarkan informasi login, membutuhkan form yang spesifik.
masalah Spesifik dan implementasi
Builder dan Abstrak Factory
Builder design pattern sangat mirip, pada batas tertentu, dengan Abstract Factory pattern. Itulah mengapa penting untuk dapat membuat perbedaan antara situasi ketika satu atau yang lain digunakan. Dalam kasus Abstract Factory, Client menggunakan metode factorys untuk membuat objek sendiri. Dalam kasus
Builders, class Builder yang diinstruksikan tentang cara membuat objek dan kemudian diminta untuk membuatnya, tapi cara class disatukan terserah class Builder, detail ini membuat perbedaan antara two patterns.
Common interface for products
Dalam prakteknya Product yang diciptakan oleh concrete builders memiliki struktur yang berbeda, jadi jika tidak ada alasan untuk memperoleh Product yang berbeda dari sebuah class induk yang sama. Hal ini juga membedakan pattern Builder dari pattern Factory Abstrak yang menciptakan object-object yang berasal dari jenis umum.
II.6 Prototype PatternMotivasi
Pemrograman saat ini adalah semua tentang biaya. Saving merupakan masalah besar ketika datang untuk menggunakan sumber daya komputer, sehingga programmer melakukan yang terbaik untuk menemukan cara meningkatkan kinerja Ketika kita berbicara tentang pembuatan obyek kita dapat menemukan cara yang lebih baik untuk memiliki objek baru: cloning. Untuk ide ini satu pattern desain tertentu terkait: daripada penciptaan menggunakan clonning. Kita mengcloning objek, jika cost untuk menciptakan objek baru itu besar dan penciptaan adalah sumber daya yang intensif.
Prototype design pattern merupakan satu pertanyaan. Hal ini memungkinkan sebuah objek untuk membuat customized objects tanpa mengetahui class mereka atau rincian tentang bagaimana untuk menciptakan mereka. Sampai poin ini kedengarannya sangat mirip dengan Metode Factory pattern, perbedaannya the fact that for the Factory the palette of prototypical objects tidak pernah mengandung lebih dari satu objek.
Maksud
menentukan jenis objek yang dibuat menggunakan prototypical instance menciptakan objek baru dengan menyalin Prototype ini
Implementasi
Pattern ini menggunakan class abstrak, seperti yang kita lihat di bawah dan hanya ada tiga jenis class membuat implementasinya agak mudah.
Class-class yang berpartisipasi dengan Prototype Pattern adalah: Client - menciptakan objek baru dengan meminta Prototype untuk
mengcloning dirinya sendiri. Prototype - menyatakan sebuah interface untuk cloning itu sendiri. ConcretePrototype - mengimplementasikan operasi untuk cloning itu sendiri.
Proses cloning dimulai ketika class diinisialisasi dan diinstantiated. Client meminta objek baru dari tipenya dan mengirimkan permintaan ke class Prototype. Sebuah ConcretePrototype, tergantung dari jenis objek yang diperlukan, akan menangani cloning melalui Clone() metode, membuat instance baru dari dirinya sendiri.
Berikut adalah contoh kode untuk Prototype Pattern:
public interface Prototype {public abstract Object clone ( );
}
public class ConcretePrototype implements Prototype {public Object clone() {
return super.clone();}
}
public class Client {
public static void main( String arg[] ) {
ConcretePrototype obj1= new ConcretePrototype ();ConcretePrototype obj2 = ConcretePrototype)obj1.clone();
}}
Contoh ini agak sepele, tetapi penggunaan nyata dari pattern muncul saat kita tidak tahu apa yang kita sebenarnya cloning. Sebagai contoh jika kita membutuhkan objek yang baru dibuat untuk disimpan dalam hashtable kita dapat menggunakannya seperti ini:
// Violation of Likov's Substitution Principleclass Rectangle{
protected int m_width;protected int m_height;
public void setWidth(int width){m_width = width;
}
public void setHeight(int height){m_height = height;
}
public int getWidth(){return m_width;
}
public int getHeight(){return m_height;
}
public int getArea(){return m_width * m_height;
}}
class Square extends Rectangle {
public void setWidth(int width){m_width = width;m_height = width;
}
public void setHeight(int height){m_width = height;m_height = height;
}
}
class LspTest{
private static Rectangle getNewRectangle(){
// it can be an object returned by some factory ... return new Square();
}
public static void main (String args[]){
Rectangle r = LspTest.getNewRectangle();
r.setWidth(5);r.setHeight(10);// user knows that r it's a rectangle.// It assumes that he's able to set the width and height as for the base
class
System.out.println(r.getArea());// now he's surprised to see that the area is 100 instead of 50.
}}
Penerapan & Contoh
Gunakan Prototype Pattern ketika sistem harus independen dari bagaimana Product itu created, composed, dan represented. dan: Class yang akan diinstansiasi ditentukan pada saat run-time Diperlukan untuk menghindari penciptaan hirarki factory Lebih mudah untuk menyalin instance yang ada daripada membuat yang baru.
Contoh 1
Dalam membangun stages untuk permainan yang menggunakan labirin dan object-object visual yang berbeda bertemu karakter diperlukan metode cepat menghasilkan haze map dengan menggunakan objek-objek yang sama: wall, door, passage, room... Prototype Pattern berguna dalam kasus ini karena bukan hard coding (menggunakan operasi baru) obyek kamar, pintu, dan objek dinding yang bisa instantiated, metode CreateMaze akan parameterized oleh berbagai objek kamar, pintu, dinding dan objek jalan, sehingga komposisi peta dapat dengan mudah diubah dengan mengganti objek prototipikal dengan yang berbeda.
Client adalah metode CreateMaze dan class ConcretePrototype akan menjadi orang-orang membuat salinan untuk objek yang berbeda.
Contoh 2:
Misalkan kita melakukan analisis penjualan pada set data dari database. Biasanya, kita akan menyalin informasi dari database, merangkum itu menjadi obyek dan melakukan analisis. Tetapi jika analisis lain diperlukan pada set data yang sama, membaca database lagi dan menciptakan objek baru bukanlah ide yang terbaik. Jika kita menggunakan pattern Prototype maka obyek yang digunakan dalam analisis pertama akan dicloning dan digunakan untuk analisis lainnya.
Client di sini salah satu metode yang memproses obyek yang merangkum informasi dari database. Class-class ConcretePrototype yang akan menjadi class, dari objek yang sudah dibuat setelah penggalian data dari database, akan disalin ke obyek yang digunakan untuk analisis.
Spesifik masalah dan implementasi
Menggunakan prototype manager
Ketika aplikasi menggunakan banyak Prototype yang dapat diciptakan dan dihancurkan secara dinamis, registry Prototype yang tersedia harus dijaga. Registri ini disebut manajer Prototype dan harus melaksanakan operasi untuk mengelola Prototype terdaftar seperti mendaftarkan Prototype di bawah tombol tertentu, mencari Prototype dengan kunci yang diberikan, menghapus salah satu dari register, dll. Client akan menggunakan interface Prototype Manager untuk menangani Prototype pada saat run-time dan akan meminta izin sebelum menggunakan Clone() method.
Tidak ada banyak perbedaan antara pelaksanaan Prototype yang menggunakan manajer Prototype dan metode factory diimplementasikan menggunakan mekanisme registrasi class. Mungkin satu-satunya perbedaan terdapat dalam kinerja.
Melaksanakan operasi Clone
Sebuah diskusi kecil muncul ketika berbicara tentang seberapa dalam atau dangkal tiruan harus: a deep clone clones the instance variables in the cloning object while a shallow clone shares the instance variables between the clone and the original. Usually, a shallow clone is enough and very simple, but cloning complex prototypes should use deep clones so the clone and the original are independent, a
deep clone needing its components to be the clones of the complex object’s components.
Initializing klon
Ada kasus ketika internal states dari clone harus diinisialisasi setelah dibuat. Hal ini terjadi karena nilai-nilai ini tidak dapat dikirimkan ke metode Clone(), yang menggunakan interface yang akan didestroy jika parameter tersebut digunakan. Dalam hal ini inisialisasi harus dilakukan dengan menggunakan pengaturan dan pengaturan ulang operasi dari class Prototype atau dengan menggunakan metode menginisialisasi yang mengambil sebagai parameter nilai-nilai di mana internal state clone itu harus diset.
Hot poin
Prototype Manager - diimplementasikan biasanya sebagai hashtable menjaga objek untuk mengcloning. Ketika menggunakannya, Prototype menjadi metode factory yang menggunakan cloning bukan Instansiasi.Deep Clones vs. Shallow Clones - ketika kita mengcloning objek kompleks yang berisi objek-objek lain, kita harus berhati-hati bagaimana mereka dicloning. Kita bisa mengcloning yang terkandung objek juga (deep cloning) atau kita dapat referensi yang sama bagi mereka, dan untuk berbagi antara cloned container objects.Initializing Internal States - ada situasi tertentu ketika object perlu diinisialisasi setelah mereka diciptakan.
II.7 Obyek Pool
Motivasi
Performa kadang dapat menjadi isu utama selama pengembangan perangkat lunak dan penciptaan objek (Instansiasi kelas) merupakan langkah mahal. Sementara Prototype pattern membantu dalam meningkatkan kinerja dengan kloning objek, Object Pool pattern menawarkan mekanisme untuk menggunakan kembali objek-objek yang mahal untuk dibuat.
Clients of an object pull "feel" like they are owners of a service although the service is shared among many other clients. Intent
-reuse and share objects that are expensive to create.
Implementasi
Implementasinya melibatkan object-object berikut: Reusable - Wraps the limited resource, will be shared by several clients for a
limited amount of time. Client - uses an instance of type Reusable. ReusablePool - manage the reusable objects for use by Clients, creating and
managing a pool of objects.
Ketika Client meminta sebuah objek Reusable, pool melakukan tindakan berikut: Search for an available Reusable object and if it was found it will be returned
to the client. If no Reusable object was found then it tries to create a new one. If this actions
succeds the new Reusable object will be returned to the client. If the pool was unable to create a new Reusable, the pool will wait until a
reusable object will be re leased.
The Client is responsible to request the Reusable object as well to release it to the pool. If this action will not be performed the Reusable object will be lost, being considered unavailable by the ResourcePool.
The clients are not aware that they are sharing the Reusable object. From the client poinf of view they are the owners of a new object which comes from the Resource pool in the same way that it comes from a factory or another creational design pattern. The only difference is that the Client should mark the Reusable object as available, after it finishes to use it. It's not about releasing the objects; for example if we work with databases, when a connection is closed it's not necesarely distroyed, it means that it can be reused by another client.
Mengapa menggunakannya?
Pada dasarnya, kita akan menggunakan object pool setiap kali ada beberapa Client yang membutuhkan the same stateless resource yang mahal untuk dibuat.
Penerapan & Contoh
Mari kita mengambil contoh koneksi database. Ini jelas yang membuka terlalu banyak koneksi dapat mempengaruhi kinerja karena beberapa alasan: Menciptakan connection adalah operasi yang mahal. Ketika ada terlalu banyak koneksi dibuka waktu lebih lama untuk membuat
yang baru dan server database akan menjadi kelebihan beban.
Here the object pool comes in to picture to manage the connections and provide a way to reuse and share them. It can also limit the maximum number of objects that can be created.
This pattern provide the following mechaninsm:
Connection - represent the object which is instantiated by the client. From the client perspective this object is instantiated and it handles the database operations and it is the only object visible to the client. The client is not aware that it uses some shared connections. Internally this class does not contain any code for connecting to the database and calls ConnectionPool.aquireImpl to get a ConnectionImpl object and then delegates the request to ConnectionImpl.
ConnectionImpl is the object which implements the database operations which are exposed by Connection for the client.
ConnectionPool is the main actor to manage the connections to the database. It keeps a list of ConnectionImpl objects and instantiates new objects if this is required.
When the client needs to query the database it instantiate a new Connection object specifing the database name and the call the query method which returns a set of records. From the client point of view this is all.
When the Connection.Query methd is called it asks for a ConnectionImpl object from the ConnectionPool. The ConnectionPool tries to find and return an unused object and if it doesn't find it creates one. At this point the maximum number of connections can be limited and if it was reached the pool cand wait until one will be available or return null. In the query method the request is delegated to the ConnectionImpl object returned by the object pool. Since the request is just delegated it's recomended to have the same method signature in Connection and ConnectionImpl.
Specific problems and implementation
Singleton reusable pool - The reusable pool is implemented as a singleton. The reusable pool should be accesible only to the Connection object.
1. Limited number of resources in the pool
The connection pool is responsable for sharing and reusing the resources. Sometimes the resources have to be well managed only because they affects the performace, but there are situations when the number of resources can not exceed a specific limit. In this case the Resource pool check the number of instantiated resources and of the limit is reach it will wait for a resource to be released, it will throw an exception or it will return a null value. In any of the last 2 situations the Client should be notified that the action failed because there are no available resources.
2. Handling situations when creating a new resource fails
There are many reasons when the ResourcePool.acquireConnectionImpl method fails to return a resource. It might happens because there are not available resources or some exception occured. Either way the client should be notified about his.
3. Sinkronisasi
In order to work in a multithreading environment the methods that are used by differnt threads should be synchronized. There are only three methonds in the ResourcePool object that have to be synchronized:- getInstance should be synchronized or should contain a synchronized block.
For details check the singleton multithreading implementation.
- acquireConnectionImpl - this menthod returns a resource and should be synchronized not to return the same resource to two different clients running tin different threads.
- releaseConnectionImpl - this method release a resource. Ussually it doesn't have to be synchronized a resource is allocated only by one client. Internally some blocks might need to be synchronized(depending on the method implementation and the internal structures used to keep the pool.).
4. Sumber daya Expired (tidak terpakai tapi masih milik)Masalah utama untuk Pola Pool Object adalah bahwa obyek harus dibebaskan oleh Client ketika selesai menggunakan mereka. Ada banyak contoh ketika Client "lupa" untuk melepaskan sumber daya. Mari kita ambil contoh yang koneksi database ketika koneksi tidak ditutup / dibebaskan setelah mereka digunakan. Hal ini tampaknya masalah kecil tetapi ada banyak aplikasi menghancurkan untuk alasan ini. Di pool objek dapat menerapkan mekanisme untuk memeriksa ketika sumber daya tertentu digunakan terakhir kali dan jika waktu berakhir, untuk mengembalikannya ke pool sumber daya yang tersedia.
Hot Poin
- Bila pola Pool Obyek digunakan obyek harus ditandai sebagai tersedia (dirilis) oleh Client setelah mereka digunakan, sehingga pool akan menyadari tentang hal ini. Ini adalah kelemahan utama karena Client harus melakukan ini dan itu adalah situasi umum ketika koneksi database tidak dirilis setelah mereka digunakan. Untuk mengatasi hal ini mekanisme dapat diimplementasikan untuk melepaskan sumber daya jika mereka tidak digunakan untuk jangka waktu.
- Menciptakan sumber daya akan gagal dan kasus ini harus ditangani dengan hati-hati. Ketika tidak ada sumber daya yang tersedia (beacause jumlah terbatas atau membuat yang baru gagal) Client harus diberitahu tentang hal itu.
KesimpulanAlthrough pool obyek menangani Instansiasi objek itu tujuan utama adalah untuk menyediakan cara untuk Client untuk menggunakan kembali objek seperti mereka adalah objek baru, tanpa dibagi dan digunakan kembali.
III. Behavioral Patterns3.1. Chain of Responsibility
Motivasi
Dalam menulis aplikasi apapun, sering terjadi bahwa event yang dihasilkan oleh satu objek harus ditangani oleh yang lain. And, to make our work even harder, we also happen to be denied access to the object which needs to handle the
event. In this case there are two possibilities: there is the beginner/lazy approach of making everything public, creating reference to every object and continuing from there and then there is the expert approach of using the Chain of Responsibility.
Rantai pola Tanggung Jawab desain memungkinkan objek untuk mengirim perintah tanpa mengetahui apa yang objek akan menerima dan menanganinya. Permintaan dikirim dari satu objek ke yang lain membuat mereka bagian dari rantai dan setiap objek dalam rantai ini dapat menangani perintah, menyebarkannya atau melakukan keduanya. Contoh yang paling umum dari mesin menggunakan Rantai Tanggung jawab adalah slot mesin koin vending: daripada memiliki slot untuk setiap jenis koin, mesin hanya memiliki satu slot untuk mereka semua. Koin menjatuhkan diarahkan ke tempat penyimpanan yang sesuai yang ditentukan oleh penerima perintah.
Maksud: It avoids attaching the sender of a request to its receiver, giving this way
other objects the possibility of handling the request too. The objects become parts of a chain and the request is sent from one object to
another across the chain until one of the objects will handle it.
Diagram UML dari kelas bawah ini akan membantu kita lebih memahami cara Rantai bekerja.
Beberapa penjelasan tentang role dari setiap class dalam diagram di atas: Handler - defines an interface for handling requests RequestHandler - handles the requests it is responsible for if it can handle
the request it does so, otherwise it sends the request to its successor Client - sends commands to the first object in the chain that may handle the
command.
Here is how sending a request works in the application using the Chain of Responsibility: the Client in need of a request to be handled sends it to the chain of handlers, which are classes that extend the Handler class. Each of the handlers in the chain takes its turn at trying to handle the request it receives from the client. If ConcreteHandler_i can handle it, then the request is handled, if not it is sent to the handler ConcreteHandler_i+1, the next one in the chain.
The classic example of the Chain of Responsibility's implementation is presented for us below:
public class Request {private int m_value;private String m_description;
// Setup Chain of ResponsibilityHandler h1 = new ConcreteHandlerOne();Handler h2 = new ConcreteHandlerTwo();Handler h3 = new ConcreteHandlerThree();h1.setSuccessor(h2);h2.setSuccessor(h3);
// Send requests to the chainh1.handleRequest(new Request("Negative Value ", -1));h1.handleRequest(new Request("Negative Value ", 0));h1.handleRequest(new Request("Negative Value ", 1));h1.handleRequest(new Request("Negative Value ", 2));h1.handleRequest(new Request("Negative Value ", -5));
}}
Penerapan & Contoh
Having so many design patterns to choose from when writing an application, it's hard to decide on which one to use, so here are a few situations when using the Chain of Responsibility is more effective: More than one object can handle a command The handler is not known in advance The handler should be determined automatically It’s wished that the request is addressed to a group of objects without
explicitly specifying its receiver The group of objects that may handle the command must be specified in a
dynamic wayHere are some real situations in which the Chain of Responsibility is used:
Example 1
In designing the software for a system that approves the purchasing requests.
In this case, the values of purchase are divided into categories, each having its own approval authority. The approval authority for a given value could change at any time and the system should be flexible enough to handle the situation.
The Client in the example above is the system in need of the answer to the approval. It sends a request about it to an purchase approval authority. Depending on the value of the purchase, this authority may approve the request or forward it to the next authority in the chain.
For example let’s say a request is placed for the purchase of a new keyboard for an office. The value of the purchase is not that big, so the request is sent from the head of the office to the head of the department and then to the materials department where it stops, being handled locally. But if equipment for the whole department is needed then the request goes form the head of the department, to materials department, to the purchase office and even to the manager if the value is too big.
Example 2
In designing the software that uses a set of GUI classes where it is needed to propagate GUI events from one object to another.
When an event, such as the pressing of a key or the click of the mouse, the event is needed to be sent to the object that has generated it and also to the object or objects that will handle it.
The Client is, of course, the object that has generated the event, the request is the event and the handlers are the objects that can handle it. So, if we have a handler for the click of the mouse, a handler for the pressing of the ‘Enter’ key and a handler for the pressing of the ‘Delete’ key, that is the chain of handlers that take care of the events that are generated.
Example 3
In designing a shipping system for electronic orders.
The steps to complete and handle the order differs form one order to another based on the customer, the size of the order, the way of shipment, destination
and more other reasons. The business logic changes also as special cases appear, needing the system to be able to handle all cases.
The Client, the electronic order in process, requests shipping based on a set of pieces of information. Its request is turned by the system into a specific form, combining the steps to completing and the details of handling, based on the input information. The system will send this type of request through a chain of order-handlers until the input information that it comes with matches the input the order-handles takes. When special cases appear, all that is needed is a new handler to be added in the chain.
Specific problems and implementation
The classic implementation of the Chain of Responsibility is just the first step in applying the pattern to our own application. Improvements based on the type of commands we are handling are needed, in order to make the use of this pattern effective.Representing requests
In real life each handler represents a system. And each system can handle specific requests or requests common to more handlers. We should take this issue in consideration when we implement this pattern. In the classical samples of the CoR found on the net you can see that the request is generally represented by an integer. Of course in real life we can not use primary data types as a request.
A clever design should be a flexible one. The best solution here is to create an interface a super class Request (or and interface) where to the default behavior. Then if we need to add a new handler and a specific request all we need is to extend the Request base class.
Of course this is not the only approach. Let’s consider the shipping system example. Each request will have to contain a large amount of data. Creating request examples for this might be difficult. We can take some xml objects containing the data, generated during the application flow (let’s assume we already have the code implemented for that) and pass them to each handler.
Or since the data was already saved in the database (let’s assume that also) we can pass only the id’s of the involved objects and then each handler will take the data required from db.
Unhandled requests
Unfortunately, the Chain doesn't guarantee that every command is handled, which makes the problem worse, since unhandled commands propagate through the full length of the chain, slowing down the application. One way to solve this is by checking if, at the end of the chain, the request has been handled at least once, otherwise we will have to implement handlers for all the possible requests that may appear.
Broken Chain
Sometimes we could forget to include in the implementation of the handleRequest method the call to the successor, causing a break in the chain. The request isn’t sent forward from the broken link and so it ends up unhandled. A variation of the pattern can be made to send the request to all the handlers by removing the condition from the handler and always calling the successor.
The following implementation eliminates the Broken Chain problem. The implementation moves the code to traverse the chain into the base class keeping the request handling in a different method in the subclasses. The handleRequest method is declared as final in the base class and is responsible to traverse the chain. Each Handler have to implement the handleRequestImpl method, declared as abstract in the super class.
public abstract class Handler{private Handler m_successor;
}The above implementation not only that eliminates the broken chain problem, but it also offers an increased level of flexibility. Only by changing the handleRequest method we can change the pattern to send to al handlers regardless the request handling:
For example, an improvement that we could find useful is avoiding sending spam commands. This way, the concrete extension of the HandleRequest function will look like this:
public void HandleRequest(int request) {
if(isSpam(request)) { // if the request is spam take spam-related actions
...}else { // request is not spam.
super.HandleRequest(request); // Pass message to next filter in the chain.
}}Use on existing code
The last, but not least problem that the Chain of Responsibility creates to a programmer is the fact that it is impossible to introduce the pattern into the existing classes without modifying the source code and, even in the case where the pattern is already included in the code, if new operations need to be added to the Handler, it is impossible to do that without modifying the source code. So the basic idea is to decide from the start on whether to use the pattern or not and if we do, what methods we need.
Hot points
The fundamental flaw of the pattern is the fact that it gets easily broken: if the programmer forgets to call the next handler in the concreteHandler the request gets lost on the way. This problem comes from the fact that the execution is not handled entirely by the superclass and the call is triggered in the superclass.When implementing the CoR pattern a special care should be taken for the request representation. The request is not considered a distinctive part of the CoR pattern, but it is still used in all the components of the pattern.Another flaw of the Chain of Responsibility is the fact that some requests may end up unhandled due to the wrong implementation of concrete handler, their propagation slowing down the rest of the application. This means that extra care is needed when taking into account the requests that may appear in the process..
3.2. Command Pattern"Sebuah obyek yang berisi simbol, nama atau kunci yang mewakili daftar
command, tindakan atau penekanan tombol". Ini adalah definisi makro, salah
satu yang harus akrab/dikenal bagi setiap pengguna komputer. Dari ide ini pola
desain command tercipta/telah lahir.
Makro mewakili, pada batas tertentu, sebuah command yang dibangun dari
kumpulan seperangkat command lainnya, dalam urutan tertentu. Sama seperti
makro, pola desain Command merangkum/encapsulate command (metode
panggilan) pada objek yang memungkinkan kita untuk mengeluarkan permintaan
tanpa mengetahui operasi yang diminta atau meminta object. pola Command
desain menyediakan pilihan untuk command antrian (queue), undo / redo actions
dan manipulasi lainnya.
Maksudnya adalah:
- Merangkum permintaan dalam suatu objek.
- memungkinkan parameterisasi klien dengan permintaan yang berbeda.
- memungkinkan menyimpan permintaan dalam sebuah antrian (queue).
Implementasi
Gagasan (ide) dan pelaksanaan (implementasi) pola desain Command cukup
sederhana, seperti yang akan kita lihat dalam diagram di bawah ini, hanya
membutuhkan sedikit class pelaksana (implementasi).
Clases yang berpartisipasi dalam pola desain command adalah:
Command : menyatakan sebuah interface untuk executing operasi;
Concrete Command : memperluas interface Command, menerapkan metode
Execute dengan menerapkan operasi yang sesuai pada Receiver. Ini
mendefinisikan hubungan antara Receiver dan action (tindakan).
Client (klien) : membuat objek ConcreteCommand dan menetapkan
penerima tersebut;
Invoker : meminta command untuk melaksanakan/membawa permintaan
tersebut;
Receiver : mengetahui bagaimana untuk melakukan operasi,
Klien meminta command yang akan dieksekusi. Invoker mengambil/membawa
command, merangkum dan menempatkannya dalam antrian (queue), dalam
kasus tersebut ada hal lain yang harus dilakukan pertama kali, dan
ConcreteCommand yang bertanggung jawab atas command yang diminta,
mengirim hasilnya ke Receiver.
Berikut ini adalah pola kode sampel implementasi klasik menempatkan pesanan
untuk membeli dan menjual saham:
Klien menciptakan beberapa pesanan untuk membeli dan menjual saham
(ConcreteCommands). Kemudian pesanan akan dikirim ke agen (Invoker). Agen
mengambil pesanan dan menempatkan mereka ke sistem StockTrade (Receiver).
Agen menyimpan antrian internal dengan pesanan untuk ditempatkan. Mari kita
menganggap bahwa sistem StockTrade ditutup setiap hari Senin, namun agen
tetap menerima pesanan, dan antrian untuk diproses di kemudian hari.
public interface Order { public abstract void execute ( );}
// Receiver class.class StockTrade { public void buy() { System.out.println("You want to buy stocks"); } public void sell() { System.out.println("You want to sell stocks "); }}
// Invoker.class Agent { private m_ordersQueue = new ArrayList();