Pola Desain Perilaku
Berfokus pada bagaimana objek berkomunikasi satu sama lain dan menetapkan tanggung jawab kepada mereka.
1. Rantai Tanggung Jawab (Chain of Responsibility)
Rantai Tanggung Jawab adalah pola desain perilaku, dan tujuannya adalah mengambil permintaan dan meneruskannya melalui serangkaian penangan. Ketika permintaan melewati rantai, setiap penangan akan memutuskan apakah akan memproses permintaan atau meneruskannya ke penangan berikutnya dalam rantai. Pola ini memungkinkan beberapa penangan untuk menangani permintaan tanpa pengirim perlu tahu penangan mana yang akan memprosesnya.
1.1. Komponen Rantai Tanggung Jawab
Permintaan (Request)
Permintaan hanyalah objek yang dikirim oleh klien yang perlu diproses. Permintaan melewati rantai penangan hingga diproses atau mencapai akhir rantai.
Interface/Kelas Penangan Abstrak (Abstract Handler)
Ini adalah antarmuka/kelas dasar yang mendefinisikan metode yang akan digunakan untuk menangani permintaan. Antarmuka penangan berisi logika untuk urutan rantai dan bagaimana permintaan dilewati.
Penangan Konkret (Concrete Handlers)
Ini adalah metode/kelas yang mengimplementasikan penangan abstrak. Setiap penangan konkret berisi logika untuk menangani jenis permintaan tertentu. Jika penangan konkret dapat memproses permintaan, maka akan melakukannya; jika tidak, maka akan meneruskan permintaan ke penangan berikutnya.
1.2. Manfaat Rantai Tanggung Jawab
Kemudahan Penggunaan
Pengirim tidak perlu mengetahui metode khusus untuk memproses permintaan, pengirim hanya perlu meneruskannya ke rantai. Ini memudahkan pengirim untuk memproses permintaan.
Fleksibilitas dan Perluasan
Penangan baru dapat ditambahkan ke rantai atau dihapus dari rantai tanpa mengubah kode pengirim. Ini memungkinkan modifikasi dinamis urutan pemrosesan.
Mempromosikan Segregasi yang Bertanggung Jawab
Penangan bertanggung jawab untuk memproses jenis permintaan tertentu berdasarkan logika yang ditentukan. Ini mempromosikan pemisahan tanggung jawab yang jelas, sehingga lebih mudah mengelola dan memelihara setiap penangan secara independen.
Pemrosesan Permintaan Berurutan
Pola ini memastikan bahwa setiap permintaan diproses secara berurutan melalui rantai penangan. Setiap penangan dapat memilih untuk memproses permintaan atau meneruskannya ke penangan berikutnya. Ini dapat sangat berguna dalam skenario di mana permintaan perlu diproses dalam urutan tertentu.
1.3. Contoh
// Handler interface
class CoffeeHandler {
constructor() {
this.nextHandler = null;
}
setNext(handler) {
this.nextHandler = handler;
}
processRequest(request) {
throw new Error(
"Metode `processRequest` harus diimplementasikan oleh subclass (kelas turunan)."
);
}
}
// Konkrit handler untuk memesan kopi
class OrderCoffeeHandler extends CoffeeHandler {
processRequest(request) {
if (request === "Coffee") {
return "Pesanan untuk segelas kopi telah ditempatkan.";
} else if (this.nextHandler) {
return this.nextHandler.processRequest(request);
} else {
return "Maaf, kami tidak menyajikan " + request + ".";
}
}
}
// Konkrit handler untuk menyiapkan kopi
class PrepareCoffeeHandler extends CoffeeHandler {
processRequest(request) {
if (request === "PrepareCoffee") {
return "Kopi sedang disiapkan.";
} else if (this.nextHandler) {
return this.nextHandler.processRequest(request);
} else {
return "Tidak bisa menyiapkan " + request + ".";
}
}
}
// Kode klien
const orderHandler = new OrderCoffeeHandler();
const prepareHandler = new PrepareCoffeeHandler();
// Menyiapkan rantai (chain)
orderHandler.setNext(prepareHandler);
// Memesan kopi
console.log(orderHandler.processRequest("Coffee")); // Hasil: Pesanan ditempatkan untuk segelas kopi.
// Menyiapkan kopi
console.log(orderHandler.processRequest("PrepareCoffee")); // Hasil: Kopi sedang disiapkan.
// Coba pesan yang lain
console.log(orderHandler.processRequest("Tea")); // Hasil: Maaf, kami tidak menyajikan Teh.
2. Command
Pola desain perintah adalah pola desain perilaku yang memungkinkan Anda untuk mengkapsulasi permintaan sebagai objek. Objek tersebut akan berisi semua informasi yang diperlukan untuk eksekusi permintaan. Pola ini memungkinkan untuk memarameterisasi dan mengantri permintaan serta memberikan kemampuan untuk membatalkan operasi.
2.1. Komponen-Komponen Pola Komando
Invoker
Objek yang meminta eksekusi perintah. Invoker memiliki referensi ke perintah dan dapat mengeksekusi perintah dengan memanggil metodenya execute
. Invoker tidak perlu mengetahui detail bagaimana perintah tersebut dieksekusi. Ia hanya memicu perintah.
Command
Ini adalah antarmuka atau kelas abstrak yang mendeklarasikan metode execute
. Ini mendefinisikan metode umum yang harus diimplementasikan oleh kelas perintah konkret.
Receiver
Ini adalah objek yang melakukan pekerjaan sebenarnya ketika metode execute
dari perintah dipanggil. Penerima tahu bagaimana melaksanakan tindakan yang terkait dengan perintah tertentu.
2.2. Manfaat Pola Komando
Fleksibilitas dan Kemudahan Perluasan
Pola ini memungkinkan penambahan perintah baru dengan mudah tanpa perlu memodifikasi invoker atau penerima.
Operasi Pembatalan dan Pengulangan
Pola perintah memfasilitasi implementasi operasi pembatalan dan pengulangan. Setiap objek perintah dapat melacak status sebelumnya, memungkinkan pembatalan tindakan yang dieksekusi.
Parameterisasi dan Penyusunan Antrian
Perintah dapat diparameterisasi dengan argumen, memungkinkan penyesuaian tindakan saat runtime. Selain itu, pola ini memungkinkan penyusunan antrian dan penjadwalan permintaan, memberikan kendali atas urutan eksekusi.
Riwayat dan Pencatatan
Memungkinkan untuk mempertahankan riwayat perintah yang dieksekusi, yang dapat berguna untuk audit, pencatatan, atau pelacakan tindakan pengguna.
2.3. Contoh
class Command {
constructor(receiver) {
this.receiver = receiver;
}
execute() {
throw new Error("Metode execute() harus diimplementasikan");
}
}
class ConcreteCommand extends Command {
constructor(receiver, parameter) {
super(receiver);
this.parameter = parameter;
}
execute() {
this.receiver.action(this.parameter);
}
}
class Receiver {
action(parameter) {
console.log(
`Receiver sedang menjalankan aksi dengan parameter: ${parameter}`
);
}
}
class Invoker {
constructor() {
this.commands = [];
}
addCommand(command) {
this.commands.push(command);
}
executeCommands() {
this.commands.forEach((command) => command.execute());
this.commands = []; // Menghapus perintah yang sudah dieksekusi
}
}
// Penggunaan
const receiver = new Receiver();
const command1 = new ConcreteCommand(receiver, "Parameter Perintah 1");
const command2 = new ConcreteCommand(receiver, "Parameter Perintah 2");
const invoker = new Invoker();
invoker.addCommand(command1);
invoker.addCommand(command2);
invoker.executeCommands();
3. Interpolator
Pola desain interperator digunakan untuk menentukan tata bahasa (grammar) untuk suatu bahasa dan menyediakan sebuah penerjemah (interpreter) untuk mengartikan kalimat-kalimat dalam bahasa tersebut. Biasanya digunakan untuk membuat penerjemah bahasa atau pemroses bahasa, tetapi Anda juga dapat menggunakannya dalam aplikasi Anda. Bayangkan Anda memiliki aplikasi desktop yang kompleks, Anda dapat merancang bahasa skrip dasar yang memungkinkan pengguna akhir untuk memanipulasi aplikasi Anda melalui instruksi sederhana.
3.1. Komponen-komponen dari Interpolator
Konteks
Sebuah keadaan global atau konteks yang digunakan oleh Interpolator untuk menginterpretasikan ekspresi-ekspresi. Konteks ini sering berisi informasi yang relevan selama interpretasi ekspresi.
Ekspresi-ekspresi Abstrak
Mendefinisikan antarmuka untuk semua jenis ekspresi dalam tata bahasa (grammar) bahasa tersebut. Ekspresi-ekspresi ini biasanya diwakili sebagai kelas abstrak atau antarmuka.
Ekspresi-e Ekspresi Terminal
Mewakili simbol-simbol terminal dalam tata bahasa (grammar). Ini adalah daun-daun dari pohon ekspresi. Ekspresi terminal mengimplementasikan antarmuka yang didefinisikan oleh ekspresi abstrak.
Ekspresi-e Ekspresi Non-Terminal
Mewakili simbol-simbol non-terminal dalam tata bahasa (grammar). Ekspresi non-terminal menggunakan ekspresi-ekspresi terminal dan/atau ekspresi-e non-terminal lainnya untuk mendefinisikan ekspresi-e kompleks dengan menggabungkan atau menggabungkan mereka.
Pohon Ekspresi (Expression Tree)
Mewakili struktur hirarkis ekspresi bahasa tersebut. Pohon ekspresi dibangun dengan menggabungkan ekspresi terminal dan ekspresi non-terminal berdasarkan aturan tata bahasa bahasa tersebut.
Penerjemah (Interpreter)
Mendefinisikan antarmuka atau kelas yang menginterpretasikan pohon sintaks abstrak yang dibuat oleh pohon ekspresi. Biasanya mencakup metode interpret
yang mengambil konteks dan menginterpretasikan ekspresi berdasarkan konteks yang diberikan.
klien
Membangun pohon sintaks abstrak menggunakan ekspresi-e terminal dan non-terminal berdasarkan tata bahasa bahasa. Klien kemudian menggunakan penerjemah untuk menginterpretasikan ekspresi.
3.2. Manfaat Interpreters
Kemudahan Interpretasi Tata Bahasa (Grammar)
Pola ini menyederhanakan interpretasi aturan tata bahasa yang kompleks dengan memecahnya menjadi ekspresi-e yang lebih kecil dan lebih mudah dikelola. Setiap jenis ekspresi menangani interpretasinya sendiri, menjadikan proses interpretasi secara keseluruhan lebih mudah dikelola.
Penanganan Kesalahan yang Lebih Baik
Karena setiap jenis ekspresi menangani interpretasinya sendiri, penanganan kesalahan dapat disesuaikan dengan ekspresi tertentu. Ini memungkinkan pesan kesalahan yang lebih tepat dan bermakna saat memparsing atau menginterpretasikan masukan.
Fleksibilitas dan Pemuaian (Extensibility)
Pola interpeter menyediakan cara fleksibel untuk mendefinisikan dan memperluas aturan tata bahasa dan ekspresi bahasa tanpa mengubah logika inti interpreter. Anda dapat dengan mudah menambahkan ekspresi-e baru dengan membuat kelas-kelas ekspresi terminal dan non-terminal yang baru.
Integrasi dengan Pola Desain Lainnya
Pola Interpolator dapat digabungkan dengan pola desain lain, seperti Composite, untuk membangun hierarki ekspresi yang kompleks. Ini memungkinkan pembuatan interpreter yang kuat dan kaya fitur.
3.3. Contoh
// Ekspresi Abstrak
class Ekspresi {
tafsirkan(konteks) {
// Akan digantikan oleh subclass
}
}
// Ekspresi Terminal: EkspresiAngka
class EkspresiAngka extends Ekspresi {
constructor(angka) {
super();
this.angka = angka;
}
tafsirkan(konteks) {
return this.angka;
}
}
// Ekspresi Terminal: EkspresiVariabel
class EkspresiVariabel extends Ekspresi {
constructor(variabel) {
super();
this.variabel = variabel;
}
tafsirkan(konteks) {
return konteks[this.variabel] || 0;
}
}
// Ekspresi Non-terminal: EkspresiTambah
class EkspresiTambah extends Ekspresi {
constructor(ekspresi1, ekspresi2) {
super();
this.ekspresi1 = ekspresi1;
this.ekspresi2 = ekspresi2;
}
tafsirkan(konteks) {
return (
this.ekspresi1.tafsirkan(konteks) + this.ekspresi2.tafsirkan(konteks)
);
}
}
// Ekspresi Non-terminal: EkspresiKurang
class EkspresiKurang extends Ekspresi {
constructor(ekspresi1, ekspresi2) {
super();
this.ekspresi1 = ekspresi1;
this.ekspresi2 = ekspresi2;
}
tafsirkan(konteks) {
return (
this.ekspresi1.tafsirkan(konteks) - this.ekspresi2.tafsirkan(konteks)
);
}
}
// Kode Klien
const konteks = { a: 10, b: 5, c: 2 };
const ekspresi = new EkspresiKurang(
new EkspresiTambah(new EkspresiVariabel("a"), new EkspresiVariabel("b")),
new EkspresiVariabel("c")
);
const hasil = ekspresi.tafsirkan(konteks);
console.log("Hasil:", hasil); // Output: Hasil: 13
4. Iterator
Pola Iterator memungkinkan klien untuk secara efektif mengulangi koleksi objek
4.1. Komponen dari Iterator
Iterator
Mengimplementasikan antarmuka atau kelas Iterator dengan metode seperti first()
,next()
. Iterator melacak posisi saat ini saat melintasi koleksi.
Item
Ini adalah objek individu dari koleksi yang akan dijelajahi oleh Iterator.
4.2. Manfaat dari iterator
Kompatibilitas dengan Struktur Data yang Berbeda
Pola Iterator memungkinkan logika iterasi yang sama diterapkan pada struktur data yang berbeda.
Dukungan untuk Iterasi Bersamaan
Iterator dapat mendukung iterasi bersamaan atas koleksi yang sama tanpa saling mengganggu, ini memungkinkan klien untuk mengulangi koleksi dengan cara yang berbeda secara bersamaan.
Pemuatan Malas (Lazy Loading)
Iterator dapat dirancang untuk memuat elemen sesuai permintaan, yang bermanfaat untuk koleksi besar di mana memuat semua elemen sekaligus mungkin tidak praktis atau memakan banyak sumber daya. Elemen dapat diambil sesuai kebutuhan, mengoptimalkan penggunaan memori.
Antarmuka yang Disederhanakan
Pola Iterator menyediakan antarmuka yang bersih dan konsisten untuk mengakses elemen dalam koleksi tanpa mengekspos struktur internal koleksi. Ini menyederhanakan penggunaan dan pemahaman koleksi.
4.3. Contoh
// Kelas Mobil yang Mewakili Sebuah Mobil
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
}
getInfo() {
return `${this.make} ${this.model}`;
}
}
// Antarmuka Iterator
class Iterator {
constructor(collection) {
this.collection = collection;
this.index = 0;
}
next() {
return this.collection[this.index++];
}
hasNext() {
return this.index < this.collection.length;
}
}
// Koleksi Mobil
class CarCollection {
constructor() {
this.cars = [];
}
addCar(car) {
this.cars.push(car);
}
createIterator() {
return new Iterator(this.cars);
}
}
// Penggunaan
const carCollection = new CarCollection();
carCollection.addCar(new Car("Toyota", "Corolla"));
carCollection.addCar(new Car("Honda", "Civic"));
carCollection.addCar(new Car("Ford", "Mustang"));
const iterator = carCollection.createIterator();
while (iterator.hasNext()) {
const car = iterator.next();
console.log(car.getInfo());
}
5. Mediator
Pola Mediator berperan sebagai perantara antara sekelompok objek dengan mengkapsulasi cara objek-objek ini berinteraksi. Mediator memfasilitasi komunikasi antara komponen-komponen yang berbeda dalam suatu sistem tanpa mereka perlu merujuk langsung satu sama lain.
5.1. Komponen dari Mediator
Mediator
Mediator mengelola kontrol pusat atas operasi. Ini berisi antarmuka untuk berkomunikasi dengan objek-objek Kolaborator dan memiliki referensi ke setiap objek Kolaborator.
Kolaborator
Kolaborator adalah objek-objek yang dimediasi, setiap kolaborator memiliki referensi ke mediator.
5.2. Manfaat dari Mediator
Kontrol Terpusat
Pusatkan komunikasi dalam Mediator memungkinkan pengendalian dan koordinasi yang lebih baik antara komponen-komponen. Mediator dapat mengelola distribusi pesan, mengutamakan pesan, dan menerapkan logika khusus berdasarkan kebutuhan sistem.
Pemudahan Komunikasi
Mediator menyederhanakan logika komunikasi, karena komponen mengirim pesan ke Mediator daripada berurusan dengan kompleksitas komunikasi langsung. Hal ini menyederhanakan interaksi antara komponen dan memungkinkan pemeliharaan dan pembaruan yang lebih mudah.
Dapat Digunakan Kembali Mediator
Mediator dapat digunakan kembali di berbagai komponen dan skenario, memungkinkan satu titik kontrol untuk bagian-bagian yang berbeda dari aplikasi. Dapat digunakannya ini mempromosikan konsistensi dan membantu dalam mengelola aliran komunikasi dengan efisien.
Mendorong Pemeliharaan
Pola Mediator mendorong pemeliharaan dengan mengurangi kompleksitas komunikasi antar komponen yang langsung. Seiring pertumbuhan sistem, perubahan dan pembaruan dapat dilakukan dalam Mediator tanpa memengaruhi komponen-komponen individu, membuat pemeliharaan lebih mudah dan kurang rentan terhadap kesalahan.
var Participant = function (name) {
this.name = name;
this.chatroom = null;
};
Participant.prototype = {
send: function (message, to) {
this.chatroom.send(message, this, to);
},
receive: function (message, from) {
console.log(from.name + " to " + this.name + ": " + message);
},
};
var Chatroom = function () {
var participants = {};
return {
register: function (participant) {
participants[participant.name] = participant;
participant.chatroom = this;
},
send: function (message, from, to) {
if (to) {
// Pesan Tunggal
to.receive(message, from);
} else {
// Pesan Penyiaran
for (key in participants) {
if (participants[key] !== from) {
participants[key].receive(message, from);
}
}
}
},
};
};
function run() {
var yoko = new Participant("Yoko");
var john = new Participant("John");
var paul = new Participant("Paul");
var ringo = new Participant("Ringo");
var chatroom = new Chatroom();
chatroom.register(yoko);
chatroom.register(john);
chatroom.register(paul);
chatroom.register(ringo);
yoko.kirim("Semua yang Anda butuhkan adalah cinta.");
yoko.kirim("Aku mencintaimu John.");
john.kirim("Hei, tidak perlu disiarkan", yoko);
paul.kirim("Ha, aku mendengarnya!");
ringo.kirim("Paul, apa pendapatmu?", paul);
}
6. Memento
Pola desain Memento memungkinkan status objek disimpan dan dipulihkan pada waktu yang akan datang tanpa mengekspos struktur internalnya. Memento adalah objek terpisah yang menyimpan status objek asli.
6.1. Komponen dari Memento
Originator
Ini adalah objek yang disimpan. Itu membuat objek Memento untuk menyimpan status internalnya.
Memento
Memento bertanggung jawab untuk menyimpan status dari Originator. Ini memiliki metode untuk mengambil dan mengatur status Originator tetapi tidak mengekspos struktur internal Originator.
Caretaker
Caretaker bertanggung jawab untuk melacak dan mengelola Memento. Ini tidak mengubah atau memeriksa isi dari Memento.
6.2. Manfaat dari Memento
Pemeliharaan dan Pemulihan Status
Memento memungkinkan status internal objek disimpan dan dipulihkan pada waktu yang akan datang.
Operasi Undo/Redo
Memento memudahkan implementasi fungsi undo dan redo dengan mudah. Karena Memento menyimpan status objek pada berbagai titik waktu, Anda dapat mendukung pembatalan perubahan yang dibuat pada objek atau mengulang perubahan yang dibuat pada objek.
Kinerja yang Ditingkatkan
Menyimpan status objek dalam Memento memungkinkan operasi penyimpanan dan pengambilan yang lebih efisien dibandingkan dengan pendekatan lain.
Desain yang Fleksibel
Ini menyediakan cara fleksibel untuk mengelola sejarah status objek. Caretaker dapat memutuskan status mana yang akan dijaga dan kapan akan mengembalikannya, memungkinkan pendekatan yang dapat disesuaikan berdasarkan kebutuhan aplikasi.
6.3. Contoh
// Kelas Komputer (Originator)
class Computer {
constructor() {
this.os = "";
this.version = "";
}
setOS(os, version) {
this.os = os;
this.version = version;
}
getState() {
return {
os: this.os,
version: this.version,
};
}
restoreState(state) {
this.os = state.os;
this.version = state.version;
}
}
// Caretaker
class Caretaker {
constructor() {
this.mementos = {};
this.nextKey = 1;
}
add(memento) {
const key = this.nextKey++;
this.mementos[key] = memento;
return key;
}
get(key) {
return this.mementos[key];
}
}
function run() {
const computer = new Computer();
const caretaker = new Caretaker();
// Simpan status
const originalState = computer.getState();
const key = caretaker.add(originalState);
// Merusak status
computer.setOS("Windows", "11");
// Pulihkan status asli
const restoredState = caretaker.get(key);
computer.restoreState(restoredState);
console.log(computer.getState()); // Output: { os: '', version: '' }
}
run();
7. Observer
Pola observer memungkinkan banyak objek untuk berlangganan acara yang disiarkan oleh objek lain.
7.1. Komponen Observer
Subjek
Subjek adalah objek yang mengimplementasikan antarmuka yang memungkinkan objek pengamat berlangganan dan mengirim pemberitahuan kepada pengamat saat statusnya berubah.
Pengamat
Pengamat berlangganan subjek dan biasanya memiliki fungsi yang dipanggil saat diberitahu oleh Subjek.
7.2. Manfaat Pengamat
Pemudahan Penanganan Acara
Pola Observer dapat menyederhanakan mekanisme penanganan acara, karena acara dapat diperlakukan sebagai pemberitahuan kepada pengamat tentang perubahan status.
Mengurangi Kode yang Sama
Alih-alih menggandakan kode yang sama untuk merespons perubahan status di beberapa tempat, pola Observer memungkinkan adanya tempat terpusat untuk mengelola respons ini, mempromosikan kode yang lebih bersih dan mudah dikelola.
Dukungan untuk Komunikasi Penyiaran
Pola Observer memfasilitasi model komunikasi "satu-ke-banyak", di mana satu acara memicu tindakan dalam beberapa pengamat. Ini berguna dalam skenario di mana beberapa komponen perlu bereaksi terhadap perubahan status.
Modularitas dan Dapat Digunakan Kembali
Pengamat dapat digunakan kembali di berbagai subjek, mempromosikan modularitas dan dapat digunakan kembali kode. Ini memungkinkan kode yang lebih fleksibel dan mudah dikelola.
7.3. Contoh
function Click() {
this.handlers = []; // observers
}
Click.prototype = {
subscribe: function (fn) {
this.handlers.push(fn);
},
unsubscribe: function (fn) {
this.handlers = this.handlers.filter(function (item) {
if (item !== fn) {
return item;
}
});
},
fire: function (o, thisObj) {
var scope = thisObj || window;
this.handlers.forEach(function (item) {
item.call(scope, o);
});
},
};
function run() {
var clickHandler = function (item) {
console.log("fired: " + item);
};
var click = new Click();
click.subscribe(clickHandler);
click.fire("event #1");
click.unsubscribe(clickHandler);
click.fire("event #2");
click.subscribe(clickHandler);
click.fire("event #3");
}
8. State
Pola State adalah pola desain perilaku yang memungkinkan Anda memiliki objek dasar dan memberikannya fungsionalitas tambahan berdasarkan statusnya. Pola ini sangat berguna ketika sebuah objek perlu mengubah perilakunya berdasarkan status internalnya.
8.1. Komponen dari State
State
Ini adalah objek yang mengkapsulasi nilai-nilai Status dan perilaku yang terkait dengan Status tersebut.
Context
Ini adalah objek yang menjaga referensi ke objek State yang menentukan Status saat ini. Ini juga mencakup antarmuka yang memungkinkan objek State lain untuk mengubah Status saat ini menjadi Status yang berbeda.
8.2. Manfaat dari State
Kode yang Modular dan Terorganisir
Setiap State dikapsulkan dalam kelasnya sendiri, membuat kode menjadi modular dan mudah dikelola.
Tidak Perlu Pernyataan Switch yang Panjang
Pernyataan switch juga dapat digunakan untuk mengubah perilaku objek, tetapi masalah dengan metode ini adalah pernyataan switch bisa menjadi sangat panjang saat proyek Anda berkembang. Pola State memperbaiki masalah ini.
Mempromosikan Dapat Digunakan Kembali
Status dapat digunakan kembali di berbagai konteks, ini mengurangi duplikasi kode.
Membuat Pengujian Lebih Sederhana
Menguji kelas status individual secara terisolasi lebih mudah dan efektif daripada menguji objek monolitik dengan logika kondisional yang kompleks.
8.3. Contoh
class Car {
constructor() {
this.state = new ParkState();
}
setState(state) {
this.state = state;
console.log(`Mengganti status menjadi: ${state.constructor.name}`);
}
park() {
this.state.park(this);
}
drive() {
this.state.drive(this);
}
reverse() {
this.state.reverse(this);
}
}
class ParkState {
park(car) {
console.log("Mobil sudah berada dalam Posisi Parkir");
}
drive(car) {
console.log("Beralih ke Posisi Drive");
car.setState(new DriveState());
}
reverse(car) {
console.log("Beralih ke Posisi Reverse");
car.setState(new ReverseState());
}
}
class DriveState {
park(car) {
console.log("Beralih ke Posisi Parkir");
car.setState(new ParkState());
}
drive(car) {
console.log("Mobil sudah berada dalam Posisi Drive");
}
reverse(car) {
console.log("Beralih ke Posisi Reverse");
car.setState(new ReverseState());
}
}
class ReverseState {
park(car) {
console.log("Beralih ke Posisi Parkir");
car.setState(new ParkState());
}
drive(car) {
console.log("Beralih ke Posisi Drive");
car.setState(new DriveState());
}
reverse(car) {
console.log("Mobil sudah berada dalam Posisi Reverse");
}
}
// Contoh penggunaan
const car = new Car();
car.drive();
car.reverse();
car.drive();
car.park();
car.drive(); // Mencoba untuk mengemudi saat diparkir
9. Strategy
Pola Strategy pada dasarnya adalah pola desain yang memungkinkan Anda memiliki sekelompok algoritma (strategi) yang dapat dipertukarkan.
9.1. Komponen dari Strategy
Strategi
Ini adalah algoritma yang mengimplementasikan antarmuka Strategi.
Konteks
Ini adalah objek yang menjaga referensi ke Strategi saat ini. Ini mendefinisikan antarmuka yang memungkinkan klien untuk mengubah Strategi saat ini menjadi Strategi yang berbeda atau mengambil perhitungan dari Strategi saat ini yang dirujuk.
9.2. Manfaat dari Strategy
Algoritma yang Dapat Dipertukarkan Secara Dinamis
Strategi dapat ditukar saat runtime, memungkinkan pemilihan dinamis algoritma berdasarkan kondisi atau persyaratan yang berbeda. Hal ini sangat berguna ketika algoritma yang tepat dapat bervariasi berdasarkan masukan pengguna, pengaturan konfigurasi, atau faktor lainnya.
Fleksibilitas dan Pemeliharaan
Strategi dapat diubah atau diperluas tanpa mengubah konteks yang menggunakannya. Ini membuat sistem lebih fleksibel dan lebih mudah dikelola karena perubahan dalam satu strategi tidak memengaruhi yang lain.
Membuat Pengujian Lebih Sederhana
Menguji strategi secara terisolasi lebih mudah karena setiap strategi adalah kelas terpisah. Ini memungkinkan pengujian yang terfokus dan memastikan bahwa perubahan dalam satu strategi tidak secara tidak sengaja memengaruhi yang lain.
Dapat Digunakan Kembali
Strategi dapat digunakan kembali dalam berbagai konteks atau aplikasi, mempromosikan penggunaan kode dan mengurangi duplikasi.
9.3. Contoh
class RegularCustomerStrategy {
calculatePrice(bookPrice) {
// Pelanggan reguler mendapatkan diskon tetap 10%
return bookPrice * 0.9;
}
}
class VIPCustomerStrategy {
calculatePrice(bookPrice) {
// Pelanggan VIP mendapatkan diskon tetap 20%
return bookPrice * 0.8;
}
}
class TokoBuku {
constructor(pricingStrategy) {
this.pricingStrategy = pricingStrategy;
}
aturStrategiHarga(pricingStrategy) {
this.pricingStrategy = pricingStrategy;
}
hitungHarga(hargaBuku) {
return this.pricingStrategy.hitungHarga(hargaBuku);
}
}
// Usage
const regularCustomerStrategy = new RegularCustomerStrategy();
const vipCustomerStrategy = new VIPCustomerStrategy();
const bookstore = new BookStore(regularCustomerStrategy);
console.log("Harga pelanggan reguler:", tokoBuku.hitungHarga(50)); // Output: 45 (diskon 10%)
tokoBuku.aturStrategiHarga(new VIPCustomerStrategy());
console.log("Harga pelanggan VIP:", tokoBuku.hitungHarga(50)); // Output: 40 (diskon 20%)
10. Template Method
Pola Template Method adalah pola desain perilaku yang mendefinisikan kerangka program dari sebuah algoritma dalam sebuah metode, namun memungkinkan subclass mengganti langkah-langkah spesifik dari algoritma tanpa mengubah strukturnya.
10.1. Komponen dari Template Method
Kelas Abstrak
Kelas abstrak adalah template untuk algoritma. Ini mendefinisikan antarmuka yang dapat diakses oleh klien untuk memanggil metodenya. Ini juga berisi semua fungsi yang dapat digantikan oleh subclass.
Kelas Konkrit
Mengimplementasikan langkah-langkah sebagaimana yang didefinisikan dalam Kelas Abstrak dan dapat membuat perubahan pada langkah-langkah tersebut.
10.2. Manfaat dari Template Method
Penggunaan Kembali Kode
Pola ini mempromosikan penggunaan kembali kode dengan mendefinisikan kerangka algoritma dalam kelas dasar. Subclass dapat menggunakan struktur ini dan hanya perlu memberikan implementasi untuk langkah-langkah tertentu.
Pemeliharaan yang Mudah
Membuat perubahan pada algoritma menjadi lebih sederhana karena modifikasi hanya perlu dilakukan di satu tempat, yaitu metode template dalam kelas abstrak, daripada di beberapa subclass. Ini mengurangi peluang kesalahan dan memudahkan pemeliharaan.
Ekstensi dan Variasi
Pola ini memungkinkan ekstensi dan variasi algoritma dengan mudah. Subclass dapat mengganti langkah-langkah tertentu untuk memberikan implementasi kustom, efektif memperluas atau mengubah perilaku algoritma tanpa mengubah struktur inti.
Aliran Kontrol
Metode template mendefinisikan aliran kontrol algoritma, membuatnya lebih mudah dikelola dan memahami urutan operasi dalam algoritma.
10.3. Contoh
class Camera {
// Metode template yang mendefinisikan langkah-langkah umum untuk mengambil foto
capturePhoto() {
this.turnOn();
this.initialize();
this.setExposure();
this.capture();
this.turnOff();
}
// Langkah-langkah umum untuk menyalakan kamera
turnOn() {
console.log("Menyalakan kamera");
}
// Metode abstrak untuk menginisialisasi kamera (akan di-override oleh subclass)
initialize() {
throw new Error(
"Metode abstrak: initialize() harus diimplementasikan oleh subclass"
);
}
// Metode abstrak untuk mengatur eksposur (akan di-override oleh subclass)
setExposure() {
throw new Error(
"Metode abstrak: setExposure() harus diimplementasikan oleh subclass"
);
}
// Langkah-langkah umum untuk mengambil foto
capture() {
console.log("Mengambil foto");
}
// Langkah-langkah umum untuk mematikan kamera
turnOff() {
console.log("Mematikan kamera");
}
}
class KameraDSLR extends Camera {
initialize() {
console.log("Menginisialisasi kamera DSLR");
}
setExposure() {
console.log("Mengatur eksposur untuk kamera DSLR");
}
}
class KameraMirrorless extends Camera {
initialize() {
console.log("Menginisialisasi kamera mirrorless");
}
setExposure() {
console.log("Mengatur eksposur untuk kamera mirrorless");
}
}
// Penggunaan
const kameraDSLR = new KameraDSLR();
console.log("Mengambil foto dengan kamera DSLR:");
kameraDSLR.capturePhoto();
console.log("");
const kameraMirrorless = new KameraMirrorless();
console.log("Mengambil foto dengan kamera mirrorless:");
kameraMirrorless.capturePhoto();
11. Visitor
Pola desain Visitor adalah pola desain perilaku yang memungkinkan Anda memisahkan algoritma atau operasi dari objek yang dioperasikan.
11.1. Komponen dari Visitor
ObjectStructure
Menjaga koleksi Elemen yang dapat diiterasi.
Elemen
Elemen berisi metode accept yang menerima objek visitor.
Visitor
Mengimplementasikan metode visit di mana argumen metode adalah elemen yang sedang dikunjungi. Inilah cara perubahan pada elemen dilakukan.
11.2. Manfaat dari Visitor
Prinsip Terbuka/Tertutup
Pola ini sejalan dengan Prinsip Terbuka/Tertutup, yang menyatakan bahwa entitas perangkat lunak (kelas, modul, fungsi) harus terbuka untuk ekstensi tetapi tertutup untuk modifikasi. Anda dapat memperkenalkan operasi baru (visitor baru) tanpa mengubah struktur objek atau elemen yang ada.
Dapat Diperluas
Anda dapat memperkenalkan perilaku atau operasi baru dengan menambahkan implementasi visitor baru tanpa mengubah elemen atau struktur objek yang ada. Ini membuat sistem lebih dapat diperluas, memungkinkan penambahan fitur atau perilaku baru dengan mudah.
Perilaku yang Terpusat
Pola Visitor memusatkan kode terkait perilaku dalam kelas-kelas visitor. Setiap visitor mengkapsulasi perilaku tertentu, yang dapat digunakan kembali di berbagai elemen, mempromosikan penggunaan kode kembali dan modularitas.
Konsistensi dalam Operasi
Dengan pola Visitor, Anda dapat memastikan bahwa operasi tertentu (metode visitor) diterapkan secara konsisten di berbagai elemen, karena metode accept setiap elemen memanggil metode visitor yang sesuai untuk jenis elemen tersebut.
11.3. Contoh
class GymMember {
constructor(name, subscriptionType, fitnessScore) {
this.name = name;
this.subscriptionType = subscriptionType;
this.fitnessScore = fitnessScore;
}
accept(visitor) {
visitor.visit(this);
}
getName() {
return this.name;
}
getSubscriptionType() {
return this.subscriptionType;
}
getFitnessScore() {
return this.fitnessScore;
}
setFitnessScore(score) {
this.fitnessScore = score;
}
}
class FitnessEvaluation {
visit(member) {
member.setFitnessScore(member.getFitnessScore() + 10);
}
}
class DiskonKeanggotaan {
visit(member) {
if (member.getSubscriptionType() === "Premium") {
console.log(
`${member.getName()}: Skor Kebugaran - ${member.getFitnessScore()}, Jenis Keanggotaan - ${member.getSubscriptionType()}, Berhak atas diskon 10%!`
);
} else {
console.log(
`${member.getName()}: Skor Kebugaran - ${member.getFitnessScore()}, Jenis Keanggotaan - ${member.getSubscriptionType()}, Tidak berhak atas diskon.`
);
}
}
}
function jalankan() {
const anggotaGym = [
new AnggotaGym("Alice", "Basic", 80),
new AnggotaGym("Bob", "Premium", 90),
new AnggotaGym("Eve", "Basic", 85),
];
const evaluasiKebugaran = new EvaluasiKebugaran();
const diskonKeanggotaan = new DiskonKeanggotaan();
for (let i = 0; i < anggotaGym.length; i++) {
const anggota = anggotaGym[i];
anggota.accept(evaluasiKebugaran);
anggota.accept(diskonKeanggotaan);
}
}
run();