Penggunaan mixin di Dart

Beberapa kali pertanyaan diajukan dari rekan kerja bahwa tidak jelas mengapa mixin (pengotor) dalam bahasa Dart diperlukan sama sekali. Saya memutuskan untuk melihat apa yang ada di Internet tentang masalah ini. Banyak hal yang menyebalkan dari artikel yang dapat ditemukan, mereka terutama berbicara tentang cara menggunakan kotoran, tetapi mereka tidak menjelaskan mengapa mereka diperlukan, dalam hal mana penggunaannya lebih disukai daripada warisan biasa atau implementasi antarmuka. Artikel ini adalah upaya untuk mengisi celah ini.


Terlepas dari kenyataan bahwa ada cukup banyak artikel di Internet tentang masalah ketidakmurnian di Dart dan Flutter, mereka tidak membawa kejelasan dalam pendapat saya karena contoh yang diberikan menunjukkan mekanisme murni membangun kelas dengan kotoran, yang jauh dari masuk akal dan karena itu tidak menunjukkan ruang lingkup aplikasi mereka yang sebenarnya. . Secara khusus, saya bertemu dengan contoh seperti itu . Kami memiliki:


class Animal {} class Dog {} class Cat {} 

Dan untuk beberapa alasan kami ingin mendapatkan hewan yang memiliki sifat kucing dan anjing secara bersamaan. Dalam hal ini, kita dapat melakukan ini:


 class CatDog extends Animal with Cat, Dog {} 

Setidaknya ada dua pertanyaan untuk contoh ini:


  • mengapa kita membutuhkan persilangan antara kucing dan anjing?
  • Mengapa kucing dan anjing tidak mewarisi dari Animal ? Bukankah mereka binatang?

Pada saat yang sama, mengapa kotoran tetap dibutuhkan? Itu tetap menjadi misteri.


Menurut pendapat saya yang sederhana, untuk memahami makna ketidakmurnian, seseorang harus mulai mempertimbangkan masalah ini dengan hubungan warisan. Poin utama dari pewarisan dalam OOP adalah bahwa satu entitas adalah variasi dari entitas lain. Misalnya, adalah variasi dari atau adalah variasi dari . Dan inilah tepatnya yang seharusnya menjadi faktor penentu dalam membangun hirarki kelas.


Jika kita melihat warisan dari sudut pandang yang berbeda, kita akan melihat bahwa mewarisi sifat-sifat , dan mewarisi sifat-sifat . Jika Anda tidak memperhatikan logika, maka, murni secara teknis, Anda mungkin ingin mewarisi properti beberapa entitas yang berbeda. Untuk melakukan ini, beberapa bahasa pemrograman mendukung banyak pewarisan .


Multiple inheritance dikritik karena sejumlah kekurangan (lihat Wikipedia ), sehingga banyak bahasa pemrograman sama sekali tidak menggunakan multiple inheritance, tetapi menggunakan mekanisme untuk mengimplementasikan antarmuka dan / atau ketidakmurnian. Dan, dari sudut pandang logika, konstruksi yang dihasilkan dari multiple inheritance tidak mudah untuk dipahami.


Untuk memahami materi berikut ini, perlu untuk mengingat beberapa konsep dari logika elementer. Secara khusus, konsep properti esensial dan non-esensial . Sifat-sifat esensial dari objek adalah yang karena keberadaannya yang mengacu pada kelas objek tertentu. Properti non-esensial dari suatu objek adalah mereka yang kehadiran, ketidakhadiran atau nilai-nilai spesifiknya tidak mempengaruhi objek milik kelas objek tertentu. Misalnya, bentuk persegi panjang adalah properti penting dari gambar ini, karena jika kita mengubah bentuk ini (menghapus atau menambahkan sisi atau mengubah sudut), maka persegi panjang akan berhenti menjadi persegi panjang. Tetapi jika Anda mengubah ukuran persegi panjang, maka itu akan tetap persegi panjang. Oleh karena itu, dimensi adalah properti yang tidak signifikan.


Membangun hierarki kelas biasanya didasarkan pada menambahkan properti penting apa pun ke kelas induk. Sebagai contoh


 abstract class Shape { void draw(); } class Rectangle extends Shape { @override void draw() { print('Draw rectangle'); } } class Circle extends Shape { @override void draw() { print('Draw circle'); } } 

Basis hierarki ini adalah sifat esensial dari bentuk gambar.


Contoh lain:


 abstract class Widget { void render(); } class Container extends Widget { @override void render() { print('Renders container'); } } class Text extends Widget { @override void render('Render text'); } 

Properti penting di sini adalah tujuan dari widget.


Sekarang anggaplah kita perlu menambahkan beberapa properti yang tidak penting ke entitas kita. Properti seperti itu, misalnya, adalah warna. Mari kita sekarang ingin mewarnai beberapa bentuk dan widget.


Untuk melakukan ini, Anda bisa, tentu saja, menggunakan warisan dan mengimplementasikan kelas PaintableShape dan PaintableWidget . Tapi ini tidak nyaman, karena, pertama, kita harus menduplikasi implementasi fungsi pewarnaan di kedua hierarki, dan, kedua, untuk setiap gambar dan widget yang ingin kita warnai, kita harus mengimplementasikan kelas baru, misalnya, PaintableRect dan PaintableContainer .


Anda dapat menggunakan mekanisme untuk mengimplementasikan antarmuka. Lalu kita mendapatkan sesuatu seperti ini:


 enum Color {red, yellow, green} abstract class Paintable { void paint(Color color); Color get color; } class PaintableRect extends Rectangle implements Paintable { Color _color; @override void paint(Color color) {_color = color;} @override Color get color => _color; } class PaintableContainer extends Container implements Paintable { Color _color; @override void paint(Color color) {_color = color;} @override Color get color => _color; } 

Seperti yang Anda lihat, ini juga bukan solusi terbaik, karena kami harus menduplikasi kode yang sama untuk setiap entitas yang dapat diselesaikan.


Tetapi semua masalah ini dapat diselesaikan jika fungsional yang terkait dengan properti tidak signifikan dihapus sebagai campuran terpisah (mixin):


 enum Color {red, yellow, green} mixin PaintableMixin { Color _color; void paint(Color color) {_color = color;} Color get color => _color; } class PaintableRect extends Rectangle with PaintableMixin { @override void draw() { print('Draw rectangle with color $color'); } } class PaintableContainer extends Container with PaintableMixin { @override void render() { print('Render container with color $color'); } } 

Sekarang Anda dapat menggunakannya:


 main() { PaintableRect() ..paint(Color.red) ..draw(); PaintableContainer() ..paint(Color.yellow) ..render(); } 

Untuk meringkas hal di atas, seseorang dapat menentukan dengan cara berikut kapan akan lebih mudah menggunakan pengotor: jika ada beberapa hierarki berbeda yang perlu menambahkan fungsional yang sama yang mendefinisikan beberapa properti yang tidak esensial untuk entitas hierarki ini. Atau bisa jadi satu hierarki, tetapi kita berurusan dengan cabang-cabangnya yang berbeda. Sebagai contoh, pertimbangkan widget kerangka Flutter.


Misalkan kita perlu menambahkan fungsionalitas yang terkait dengan properti yang sama ke beberapa widget. Widget dalam Flutter dibuat sebagai berikut:


 class MyStatelessWidget extends StatelessWidget {} 

atau


 class MyStatefulWidget extends StatefulWidget {} 

Untuk menambahkan properti melalui warisan, Anda harus menerapkan setidaknya dua kelas:


 class StatelessWidgetWithProperty extends StatelessWidget {} class StatefulWidgetWithPropery extends StatefulWidget {} 

pada saat yang sama, seperti yang Anda lihat lagi, Anda harus menduplikasi fungsi yang terkait dengan properti yang ditambahkan.


Saat menggunakan kotoran, masalah terpecahkan:


 mixin Property {} class MyStatelessWidget extends StatelessWidget with Propery {} class MyStatefulWidget extends StatefulWidget with Property {} 

Bagi mereka yang terbiasa dengan pola desain, penggunaan kotoran dalam beberapa kasus dapat menggantikan penggunaan pola Bridge .


Sebagai kesimpulan, perlu dicatat bahwa dengan cara ini seseorang dapat mencampur fungsional beberapa properti yang berbeda sekaligus dalam kombinasi yang sewenang-wenang.


Artikel ini tidak dimaksudkan untuk secara mendalam mendefinisikan penggunaan kotoran. Mungkin pikiran yang ingin tahu dari pengembang akan dapat menemukan lebih banyak kegunaan yang indah untuk mereka. Saya akan senang jika opsi ini untuk menggunakan kotoran muncul di komentar pada artikel ini.

Source: https://habr.com/ru/post/id484206/


All Articles