Ini semua tentang kopling longgar dan tanggung jawab tunggal, yang sejalan dengan pola MV * (MVC / MVP / MVVM) di JavaScript yang sangat modern dalam beberapa tahun terakhir.
Kopling longgar adalah prinsip berorientasi objek di mana setiap komponen sistem mengetahui tanggung jawabnya dan tidak peduli dengan komponen lain (atau setidaknya mencoba untuk tidak memedulikannya sebanyak mungkin). Kopling longgar adalah hal yang baik karena Anda dapat dengan mudah menggunakan kembali modul yang berbeda. Anda tidak digabungkan dengan antarmuka modul lain. Menggunakan publish / subscribe Anda hanya digabungkan dengan antarmuka publish / subscribe yang bukan masalah besar - hanya dua metode. Jadi jika Anda memutuskan untuk menggunakan kembali modul dalam proyek yang berbeda Anda cukup menyalin dan menempelkannya dan itu mungkin akan berhasil atau setidaknya Anda tidak perlu banyak usaha untuk membuatnya berfungsi.
Saat berbicara tentang kopling longgar, kita harus menyebutkan pemisahan perhatian. Jika Anda membangun aplikasi menggunakan pola arsitektur MV *, Anda selalu memiliki Model dan View (s). Model adalah bagian bisnis dari aplikasi. Anda dapat menggunakannya kembali di aplikasi yang berbeda, jadi bukan ide yang baik untuk memasangkannya dengan Tampilan dari satu aplikasi, di mana Anda ingin menampilkannya, karena biasanya dalam aplikasi yang berbeda Anda memiliki tampilan yang berbeda. Jadi, sebaiknya gunakan publish / subscribe untuk komunikasi Model-View. Saat Model Anda mengubahnya, ia menerbitkan acara, View menangkapnya dan memperbarui dirinya sendiri. Anda tidak memiliki overhead apapun dari mempublikasikan / berlangganan, ini membantu Anda untuk decoupling tersebut. Dengan cara yang sama Anda dapat menyimpan logika aplikasi Anda di Controller misalnya (MVVM, MVP itu bukan Controller) dan menjaga View sesederhana mungkin. Ketika Tampilan Anda berubah (atau pengguna mengklik sesuatu, misalnya) itu hanya menerbitkan acara baru, Pengendali menangkapnya dan memutuskan apa yang harus dilakukan. Jika Anda sudah familiar dengan filePola MVC atau dengan MVVM dalam teknologi Microsoft (WPF / Silverlight) Anda dapat memikirkan mempublikasikan / berlangganan seperti pola Pengamat . Pendekatan ini digunakan dalam kerangka kerja seperti Backbone.js, Knockout.js (MVVM).
Berikut ini contohnya:
//Model
function Book(name, isbn) {
this.name = name;
this.isbn = isbn;
}
function BookCollection(books) {
this.books = books;
}
BookCollection.prototype.addBook = function (book) {
this.books.push(book);
$.publish('book-added', book);
return book;
}
BookCollection.prototype.removeBook = function (book) {
var removed;
if (typeof book === 'number') {
removed = this.books.splice(book, 1);
}
for (var i = 0; i < this.books.length; i += 1) {
if (this.books[i] === book) {
removed = this.books.splice(i, 1);
}
}
$.publish('book-removed', removed);
return removed;
}
//View
var BookListView = (function () {
function removeBook(book) {
$('#' + book.isbn).remove();
}
function addBook(book) {
$('#bookList').append('<div id="' + book.isbn + '">' + book.name + '</div>');
}
return {
init: function () {
$.subscribe('book-removed', removeBook);
$.subscribe('book-aded', addBook);
}
}
}());
Contoh lain. Jika Anda tidak menyukai pendekatan MV * Anda dapat menggunakan sesuatu yang sedikit berbeda (ada persimpangan antara yang akan saya jelaskan selanjutnya dan yang terakhir disebutkan). Cukup susun aplikasi Anda dalam modul yang berbeda. Misalnya lihat Twitter.
Jika Anda melihat antarmuka Anda hanya memiliki kotak yang berbeda. Anda dapat menganggap setiap kotak sebagai modul yang berbeda. Misalnya Anda bisa memposting tweet. Tindakan ini memerlukan pembaruan beberapa modul. Pertama, ia harus memperbarui data profil Anda (kotak kiri atas) tetapi juga harus memperbarui garis waktu Anda. Tentu saja, Anda dapat menyimpan referensi ke kedua modul dan memperbaruinya secara terpisah menggunakan antarmuka publiknya, tetapi lebih mudah (dan lebih baik) untuk hanya menerbitkan acara. Ini akan membuat modifikasi aplikasi Anda lebih mudah karena kopling yang lebih longgar. Jika Anda mengembangkan modul baru yang bergantung pada tweet baru, Anda cukup berlangganan acara "publish-tweet" dan menanganinya. Pendekatan ini sangat berguna dan dapat membuat aplikasi Anda sangat terpisah. Anda dapat menggunakan kembali modul Anda dengan sangat mudah.
Berikut adalah contoh dasar dari pendekatan terakhir (ini bukan kode twitter asli ini hanya contoh oleh saya):
var Twitter.Timeline = (function () {
var tweets = [];
function publishTweet(tweet) {
tweets.push(tweet);
//publishing the tweet
};
return {
init: function () {
$.subscribe('tweet-posted', function (data) {
publishTweet(data);
});
}
};
}());
var Twitter.TweetPoster = (function () {
return {
init: function () {
$('#postTweet').bind('click', function () {
var tweet = $('#tweetInput').val();
$.publish('tweet-posted', tweet);
});
}
};
}());
Untuk pendekatan ini, ada ceramah bagus dari Nicholas Zakas . Untuk pendekatan MV *, artikel dan buku terbaik yang saya ketahui diterbitkan oleh Addy Osmani .
Kekurangan: Anda harus berhati-hati dengan penggunaan publish / subscribe yang berlebihan. Jika Anda memiliki ratusan acara, akan sangat membingungkan untuk mengelola semuanya. Anda mungkin juga mengalami benturan jika Anda tidak menggunakan namespacing (atau tidak menggunakannya dengan cara yang benar). Implementasi lanjutan dari Mediator yang terlihat seperti mempublikasikan / berlangganan dapat ditemukan di sini https://github.com/ajacksified/Mediator.js . Ini memiliki namespacing dan fitur seperti acara "menggelegak" yang, tentu saja, dapat diinterupsi. Kelemahan lain dari mempublikasikan / berlangganan adalah pengujian unit keras, mungkin menjadi sulit untuk mengisolasi fungsi yang berbeda dalam modul dan mengujinya secara independen.
Tujuan utamanya adalah untuk mengurangi kopling antar kode. Ini adalah cara berpikir yang agak berbasis peristiwa, tetapi "peristiwa" tidak terikat pada objek tertentu.
Saya akan menulis contoh besar di bawah ini dalam beberapa kode pseudo yang terlihat seperti JavaScript.
Katakanlah kita memiliki kelas Radio dan kelas Relay:
Setiap kali radio menerima sinyal, kami menginginkan sejumlah relai untuk menyampaikan pesan dengan cara tertentu. Jumlah dan jenis relai bisa berbeda. Kita bisa melakukannya seperti ini:
Ini bekerja dengan baik. Tetapi sekarang bayangkan kita menginginkan komponen yang berbeda untuk juga mengambil bagian dari sinyal yang diterima kelas Radio, yaitu Speaker:
(maaf jika analoginya bukan yang terbaik ...)
Kita bisa mengulangi polanya lagi:
Kita dapat membuatnya lebih baik dengan membuat antarmuka, seperti "SignalListener", sehingga kita hanya memerlukan satu daftar di kelas Radio, dan selalu dapat memanggil fungsi yang sama pada objek apa pun yang kita miliki yang ingin mendengarkan sinyal. Tapi itu masih menciptakan penggabungan antara antarmuka / kelas dasar / dll yang kami putuskan dan kelas Radio. Pada dasarnya setiap kali Anda mengganti salah satu kelas Radio, Signal atau Relay, Anda harus memikirkan tentang bagaimana hal itu dapat mempengaruhi dua kelas lainnya.
Sekarang mari kita coba sesuatu yang berbeda. Mari buat kelas keempat bernama RadioMast:
Sekarang kita memiliki pola yang kita pahami dan kita dapat menggunakannya untuk sejumlah dan jenis kelas selama mereka:
Jadi kami mengubah kelas Radio menjadi bentuk terakhirnya yang sederhana:
Dan kami menambahkan speaker dan relai ke daftar penerima RadioMast untuk jenis sinyal ini:
Sekarang kelas Speaker dan Relai tidak memiliki pengetahuan apa pun kecuali bahwa mereka memiliki metode yang dapat menerima sinyal, dan kelas Radio, sebagai penerbit, mengetahui RadioMast tempat ia menerbitkan sinyal. Ini adalah tujuan menggunakan sistem penyampaian pesan seperti publish / subscribe.
sumber
class
kata kunci. Harap tekankan fakta ini, misalnya. dengan mengklasifikasikan kode Anda sebagai pseudo-code.Jawaban lainnya telah berhasil dengan baik dalam menunjukkan bagaimana pola tersebut bekerja. Saya ingin menjawab pertanyaan tersirat " apa yang salah dengan cara lama? " Karena saya telah mengerjakan pola ini baru-baru ini, dan saya merasa ini melibatkan pergeseran dalam pemikiran saya.
Bayangkan kita telah berlangganan buletin ekonomi. Buletin tersebut menerbitkan tajuk utama: " Turunkan Dow Jones sebesar 200 poin ". Itu akan menjadi pesan yang aneh dan agak tidak bertanggung jawab untuk dikirim. Namun, jika diterbitkan: " Enron mengajukan perlindungan kebangkrutan bab 11 pagi ini ", maka ini adalah pesan yang lebih berguna. Perhatikan bahwa pesan tersebut dapat menyebabkan Dow Jones turun 200 poin, tetapi itu masalah lain.
Ada perbedaan antara mengirimkan perintah dan menasihati sesuatu yang baru saja terjadi. Dengan mengingat hal ini, ambil versi asli dari pola pub / sub Anda, dengan mengabaikan penangan untuk saat ini:
Sudah ada penggandengan kuat yang tersirat di sini, antara aksi pengguna (klik) dan respons sistem (pesanan dihapus). Secara efektif dalam contoh Anda, tindakan tersebut adalah memberikan perintah. Pertimbangkan versi ini:
Sekarang pawang menanggapi sesuatu yang menarik yang telah terjadi, tetapi tidak berkewajiban untuk menghapus pesanan. Faktanya, pawang dapat melakukan segala macam hal yang tidak berhubungan langsung dengan penghapusan pesanan, tetapi mungkin masih relevan dengan tindakan pemanggilan. Sebagai contoh:
Perbedaan antara perintah dan pemberitahuan adalah perbedaan yang berguna untuk dibuat dengan pola ini, IMO.
sumber
remindUserToFloss
&increaseProgrammerBrowniePoints
) terakhir Anda berada di modul terpisah, apakah Anda akan memublikasikan 2 acara satu per satu tepat di sanahandleRemoveOrderRequest
atau apakah Anda akanflossModule
mempublikasikan acara kebrowniePoints
modul setelahremindUserToFloss()
selesai?Sehingga Anda tidak perlu melakukan panggilan metode / fungsi hardcode, Anda cukup mempublikasikan acara tanpa peduli siapa yang mendengarkan. Hal ini membuat penerbit independen dari pelanggan, mengurangi ketergantungan (atau penggandengan, istilah apa pun yang Anda sukai) antara 2 bagian aplikasi yang berbeda.
Berikut beberapa kelemahan kopling seperti yang disebutkan oleh wikipedia
Pertimbangkan sesuatu seperti objek yang merangkum data bisnis. Ini memiliki panggilan metode kode keras untuk memperbarui halaman setiap kali usia ditetapkan:
Sekarang saya tidak dapat menguji objek person tanpa menyertakan
showAge
fungsinya. Juga, jika saya perlu menunjukkan usia di beberapa modul GUI lainnya juga, saya perlu melakukan hardcode yang dipanggil metode itu.setAge
, dan sekarang ada dependensi untuk 2 modul yang tidak terkait di objek person. Ini juga sulit untuk dipertahankan ketika Anda melihat panggilan tersebut dilakukan dan mereka bahkan tidak berada dalam file yang sama.Perhatikan bahwa di dalam modul yang sama, Anda tentu saja dapat memiliki panggilan metode langsung. Tetapi data bisnis dan perilaku gui yang dangkal tidak boleh berada dalam modul yang sama dengan standar yang masuk akal.
sumber
removeOrder
bahkan ada, jadi Anda tidak dapat bergantung padanya. Pada contoh kedua, Anda harus tahu.Implementasi PubSub biasanya terlihat di mana ada -
Kode contoh -
sumber
Makalah "The Many Faces of Publish / Subscribe" adalah bacaan yang baik dan satu hal yang mereka tekankan adalah decoupling dalam tiga "dimensi". Ini adalah ringkasan kasar saya, tapi mohon referensi makalahnya juga.
sumber
Jawaban sederhana Pertanyaan awal sedang mencari jawaban sederhana. Ini usahaku.
Javascript tidak menyediakan mekanisme apa pun untuk objek kode untuk membuat acara mereka sendiri. Jadi, Anda membutuhkan semacam mekanisme acara. Pola Publikasikan / Berlangganan akan menjawab kebutuhan ini, dan terserah Anda untuk memilih mekanisme yang paling sesuai dengan kebutuhan Anda.
Sekarang kita dapat melihat kebutuhan akan pola pub / sub, lalu apakah Anda lebih suka menangani peristiwa DOM secara berbeda dari cara Anda menangani peristiwa pub / sub? Demi mengurangi kompleksitas, dan konsep lain seperti pemisahan perhatian (SoC), Anda mungkin melihat manfaat dari semuanya menjadi seragam.
Jadi, secara paradoks, lebih banyak kode menciptakan pemisahan perhatian yang lebih baik, yang berskala baik hingga halaman web yang sangat kompleks.
Saya harap seseorang menganggap ini diskusi yang cukup baik tanpa menjelaskan secara detail.
sumber