Mengapa seseorang menggunakan pola Publish / Subscribe (di JS / jQuery)?


103

Jadi, seorang kolega memperkenalkan saya pada pola publish / subscribe (di JS / jQuery), tetapi saya mengalami kesulitan untuk memahami mengapa seseorang akan menggunakan pola ini di atas JavaScript / jQuery 'normal'.

Misalnya, sebelumnya saya memiliki kode berikut ...

$container.on('click', '.remove_order', function(event) {
    event.preventDefault();
    var orders = $(this).parents('form:first').find('div.order');
    if (orders.length > 2) {
        orders.last().remove();
    }
});

Dan saya bisa melihat manfaat melakukan ini sebagai gantinya, misalnya ...

removeOrder = function(orders) {
    if (orders.length > 2) {
        orders.last().remove();
    }
}

$container.on('click', '.remove_order', function(event) {
    event.preventDefault();
    removeOrder($(this).parents('form:first').find('div.order'));
});

Karena itu memperkenalkan kemampuan untuk menggunakan kembali removeOrderfungsionalitas untuk berbagai acara, dll.

Tetapi mengapa Anda memutuskan untuk menerapkan pola terbitkan / berlangganan dan melakukan hal yang sama, jika itu melakukan hal yang sama? (FYI, saya menggunakan jQuery tiny pub / sub )

removeOrder = function(e, orders) {
    if (orders.length > 2) {
        orders.last().remove();
    }
}

$.subscribe('iquery/action/remove-order', removeOrder);

$container.on('click', '.remove_order', function(event) {
    event.preventDefault();
    $.publish('iquery/action/remove-order', $(this).parents('form:first').find('div.order'));
});

Saya sudah membaca tentang polanya dengan pasti, tetapi saya tidak bisa membayangkan mengapa ini perlu. Tutorial yang pernah saya lihat menjelaskan cara menerapkan pola ini hanya mencakup contoh dasar seperti milik saya.

Saya membayangkan bahwa kegunaan pub / sub akan terlihat dengan sendirinya dalam aplikasi yang lebih kompleks, tetapi saya tidak dapat membayangkannya. Saya khawatir saya benar-benar kehilangan intinya; tapi saya ingin tahu intinya jika ada!

Bisakah Anda menjelaskan secara ringkas mengapa dan dalam situasi apa pola ini menguntungkan? Apakah layak menggunakan pola pub / sub untuk cuplikan kode seperti contoh saya di atas?

Jawaban:


222

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.

Modul 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.


3
Terima kasih, itu masuk akal. Saya terbiasa dengan pola MVC karena saya menggunakannya sepanjang waktu dengan PHP, tetapi saya tidak memikirkannya dalam hal pemrograman berbasis peristiwa. :)
Maccath

2
Terima kasih untuk uraian ini. Benar-benar membantu saya memahami konsep tersebut.
flybear

1
Itu jawaban yang sangat bagus. Tidak bisa menahan diri untuk memilih ini :)
Naveed Butt

1
Penjelasan bagus, banyak contoh, saran bacaan lebih lanjut. A ++.
Carson

16

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:

class Relay {
    function RelaySignal(signal) {
        //do something we don't care about right now
    }
}

class Radio {
    function ReceiveSignal(signal) {
        //how do I send this signal to other relays?
    }
}

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:

class Radio {
    var relayList = [];

    function AddRelay(relay) {
        relayList.add(relay);
    }

    function ReceiveSignal(signal) {
        for(relay in relayList) {
            relay.Relay(signal);
        }
    }

}

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 ...)

class Speakers {
    function PlaySignal(signal) {
        //do something with the signal to create sounds
    }
}

Kita bisa mengulangi polanya lagi:

class Radio {
    var relayList = [];
    var speakerList = [];

    function AddRelay(relay) {
        relayList.add(relay);
    }

    function AddSpeaker(speaker) {
        speakerList.add(speaker)
    }

    function ReceiveSignal(signal) {

        for(relay in relayList) {
            relay.Relay(signal);
        }

        for(speaker in speakerList) {
            speaker.PlaySignal(signal);
        }

    }

}

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:

class RadioMast {

    var receivers = [];

    //this is the "subscribe"
    function RegisterReceivers(signaltype, receiverMethod) {
        //if no list for this type of signal exits, create it
        if(receivers[signaltype] == null) {
            receivers[signaltype] = [];
        }
        //add a subscriber to this signal type
        receivers[signaltype].add(receiverMethod);
    }

    //this is the "publish"
    function Broadcast(signaltype, signal) {
        //loop through all receivers for this type of signal
        //and call them with the signal
        for(receiverMethod in receivers[signaltype]) {
            receiverMethod(signal);
        }
    }
}

Sekarang kita memiliki pola yang kita pahami dan kita dapat menggunakannya untuk sejumlah dan jenis kelas selama mereka:

  • mengetahui RadioMast (kelas yang menangani semua pesan yang lewat)
  • mengetahui tanda tangan metode untuk mengirim / menerima pesan

Jadi kami mengubah kelas Radio menjadi bentuk terakhirnya yang sederhana:

class Radio {
    function ReceiveSignal(signal) {
        RadioMast.Broadcast("specialradiosignal", signal);
    }
}

Dan kami menambahkan speaker dan relai ke daftar penerima RadioMast untuk jenis sinyal ini:

RadioMast.RegisterReceivers("specialradiosignal", speakers.PlaySignal);
RadioMast.RegisterReceivers("specialradiosignal", relay.RelaySignal);

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.


Sangat menyenangkan memiliki contoh konkret yang menunjukkan bagaimana menerapkan pola pub / sub bisa lebih baik daripada menggunakan metode 'normal'! Terima kasih!
Maccath

1
Sama-sama! Secara pribadi saya sering menemukan bahwa otak saya tidak 'mengklik' ketika sampai pada pola / metodologi baru sampai saya menyadari masalah sebenarnya yang dipecahkannya untuk saya. Pola sub / pub sangat bagus dengan arsitektur yang digabungkan secara erat secara konseptual tetapi kami tetap ingin memisahkannya sebanyak mungkin. Bayangkan sebuah permainan di mana Anda memiliki ratusan objek yang semuanya harus bereaksi terhadap hal-hal yang terjadi di sekitarnya misalnya, dan objek ini dapat berupa segalanya: pemain, peluru, pohon, geometri, gui, dll.
Anders Arpi

3
JavaScript tidak memiliki classkata kunci. Harap tekankan fakta ini, misalnya. dengan mengklasifikasikan kode Anda sebagai pseudo-code.
Rob W

Sebenarnya di ES6 ada kata kunci class.
Minko Gechev

5

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:

$.subscribe('iquery/action/remove-order', removeOrder);

$container.on('click', '.remove_order', function(event) {
    event.preventDefault();
    $.publish('iquery/action/remove-order', $(this).parents('form:first').find('div.order'));
});

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:

$.subscribe('iquery/action/remove-order-requested', handleRemoveOrderRequest);

$container.on('click', '.remove_order', function(event) {
    event.preventDefault();
    $.publish('iquery/action/remove-order-requested', $(this).parents('form:first').find('div.order'));
});

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:

handleRemoveOrderRequest = function(e, orders) {
    logAction(e, "remove order requested");
    if( !isUserLoggedIn()) {
        adviseUser("You need to be logged in to remove orders");
    } else if (isOkToRemoveOrders(orders)) {
        orders.last().remove();
        adviseUser("Your last order has been removed");
        logAction(e, "order removed OK");
    } else {
        adviseUser("Your order was not removed");
        logAction(e, "order not removed");
    }
    remindUserToFloss();
    increaseProgrammerBrowniePoints();
    //etc...
}

Perbedaan antara perintah dan pemberitahuan adalah perbedaan yang berguna untuk dibuat dengan pola ini, IMO.


jika 2 fungsi ( remindUserToFloss& increaseProgrammerBrowniePoints) terakhir Anda berada di modul terpisah, apakah Anda akan memublikasikan 2 acara satu per satu tepat di sana handleRemoveOrderRequestatau apakah Anda akan flossModulemempublikasikan acara ke browniePointsmodul setelah remindUserToFloss()selesai?
Bryan P

4

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

Sistem yang digabungkan erat cenderung menunjukkan karakteristik perkembangan berikut, yang sering dianggap sebagai kerugian:

  1. Perubahan dalam satu modul biasanya memaksa efek riak perubahan pada modul lain.
  2. Perakitan modul mungkin membutuhkan lebih banyak usaha dan / atau waktu karena ketergantungan antar modul meningkat.
  3. Modul tertentu mungkin lebih sulit untuk digunakan kembali dan / atau diuji karena modul dependen harus disertakan.

Pertimbangkan sesuatu seperti objek yang merangkum data bisnis. Ini memiliki panggilan metode kode keras untuk memperbarui halaman setiap kali usia ditetapkan:

var person = {
    name: "John",
    age: 23,

    setAge: function( age ) {
        this.age = age;
        showAge( age );
    }
};

//Different module

function showAge( age ) {
    $("#age").text( age );
}

Sekarang saya tidak dapat menguji objek person tanpa menyertakan showAgefungsinya. 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.


Saya tidak memahami konsep 'ketergantungan' di sini; di mana ketergantungan dalam contoh kedua saya, dan di mana itu hilang dari ketiga saya? Saya tidak dapat melihat perbedaan praktis antara cuplikan kedua dan ketiga saya - sepertinya menambahkan 'lapisan' baru antara fungsi dan acara tanpa alasan yang sebenarnya. Saya mungkin buta, tapi saya pikir saya membutuhkan lebih banyak petunjuk. :(
Maccath

1
Dapatkah Anda memberikan contoh kasus penggunaan yang mempublikasikan / berlangganan akan lebih sesuai daripada hanya membuat fungsi yang melakukan hal yang sama?
Jeffrey Sweeney

@Maccath Sederhananya: dalam contoh ketiga, Anda tidak tahu atau harus tahu bahwa removeOrderbahkan ada, jadi Anda tidak dapat bergantung padanya. Pada contoh kedua, Anda harus tahu.
Esailija

Meskipun saya masih merasa ada cara yang lebih baik untuk melakukan apa yang Anda jelaskan di sini, setidaknya saya yakin bahwa metodologi ini memiliki tujuan, terutama di lingkungan dengan banyak pengembang lain. +1
Jeffrey Sweeney

1
@Esailija - Terima kasih, saya rasa saya mengerti sedikit lebih baik. Jadi ... jika saya menghapus pelanggan sepenuhnya, itu tidak akan error atau apa pun, itu tidak akan melakukan apa-apa? Dan apakah menurut Anda ini mungkin berguna dalam kasus di mana Anda ingin melakukan tindakan, tetapi belum tentu mengetahui fungsi mana yang paling relevan pada saat publikasi, tetapi pelanggan dapat berubah bergantung pada faktor lain?
Maccath

1

Implementasi PubSub biasanya terlihat di mana ada -

  1. Ada implementasi seperti portlet di mana ada beberapa portlet yang berkomunikasi dengan bantuan bus acara. Ini membantu dalam menciptakan arsitektur aync.
  2. Dalam sistem yang dirusak oleh kopling ketat, pubsub adalah mekanisme yang membantu dalam berkomunikasi antara berbagai modul.

Kode contoh -

var pubSub = {};
(function(q) {

  var messages = [];

  q.subscribe = function(message, fn) {
    if (!messages[message]) {
      messages[message] = [];
    }
    messages[message].push(fn);
  }

  q.publish = function(message) {
    /* fetch all the subscribers and execute*/
    if (!messages[message]) {
      return false;
    } else {
      for (var message in messages) {
        for (var idx = 0; idx < messages[message].length; idx++) {
          if (messages[message][idx])
            messages[message][idx]();
        }
      }
    }
  }
})(pubSub);

pubSub.subscribe("event-A", function() {
  console.log('this is A');
});

pubSub.subscribe("event-A", function() {
  console.log('booyeah A');
});

pubSub.publish("event-A"); //executes the methods.

1

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.

  1. Decoupling spasi. Para pihak yang berinteraksi tidak perlu saling mengenal. Penerbit tidak tahu siapa yang mendengarkan, berapa banyak yang mendengarkan, atau apa yang mereka lakukan dengan acara tersebut. Pelanggan tidak tahu siapa yang memproduksi acara ini, ada berapa produser, dll.
  2. Pemisahan waktu. Pihak yang berinteraksi tidak perlu aktif pada saat yang sama selama interaksi. Misalnya, pelanggan mungkin terputus saat penerbit menerbitkan beberapa acara, tetapi ia dapat bereaksi ketika itu online.
  3. Pemisahan sinkronisasi. Penayang tidak diblokir saat memproduksi acara dan pelanggan dapat diberi tahu secara asinkron melalui panggilan balik setiap kali acara yang mereka langgani tiba.

0

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.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.