Pola Builder tidak memecahkan "masalah" banyak argumen. Tetapi mengapa banyak argumen yang bermasalah?
- Mereka mengindikasikan kelas Anda mungkin melakukan terlalu banyak . Namun, ada banyak jenis yang secara sah berisi banyak anggota yang tidak dapat dikelompokkan dengan bijaksana.
- Menguji dan memahami suatu fungsi dengan banyak input menjadi lebih rumit secara eksponensial - secara harfiah!
- Ketika bahasa tidak menawarkan parameter bernama, panggilan fungsi tidak mendokumentasikan diri . Membaca panggilan fungsi dengan banyak argumen cukup sulit karena Anda tidak tahu apa yang seharusnya dilakukan parameter ke-7. Anda bahkan tidak akan memperhatikan jika argumen ke-5 dan ke-6 bertukar secara tidak sengaja, terutama jika Anda menggunakan bahasa yang diketik secara dinamis atau semuanya terjadi sebagai string, atau ketika parameter terakhir adalah
true
karena suatu alasan.
Memalsukan parameter bernama
The Builder Pola alamat hanya satu dari masalah ini, yaitu kekhawatiran pemeliharaan panggilan fungsi dengan banyak argumen * . Jadi panggilan fungsi suka
MyClass o = new MyClass(a, b, c, d, e, f, g);
mungkin menjadi
MyClass o = MyClass.builder()
.a(a).b(b).c(c).d(d).e(e).f(f).g(g)
.build();
Pattern Pola Builder pada awalnya dimaksudkan sebagai pendekatan representasi-agnostik untuk merakit objek komposit, yang merupakan aspirasi yang jauh lebih besar daripada sekadar menyebutkan argumen untuk parameter. Secara khusus, pola pembangun tidak memerlukan antarmuka yang lancar.
Ini menawarkan sedikit keamanan ekstra karena akan meledak jika Anda memanggil metode pembangun yang tidak ada, tetapi jika tidak, Anda tidak akan mendapatkan apa pun yang tidak dimiliki komentar dalam panggilan konstruktor. Selain itu, membuat pembangun secara manual memerlukan kode, dan lebih banyak kode selalu mengandung lebih banyak bug.
Dalam bahasa yang mudah mendefinisikan tipe nilai baru, saya telah menemukan bahwa lebih baik menggunakan tipe microtyping / kecil untuk mensimulasikan argumen bernama. Dinamai demikian karena tipenya sangat kecil, tetapi akhirnya Anda mengetik lebih banyak ;-)
MyClass o = new MyClass(
new MyClass.A(a), new MyClass.B(b), new MyClass.C(c),
new MyClass.D(d), new MyClass.E(e), new MyClass.F(f),
new MyClass.G(g));
Jelas, nama-nama jenis A
, B
, C
, ... harus nama mendokumentasikan diri yang menggambarkan arti dari parameter, sering nama yang sama seperti yang Anda akan memberikan variabel parameter. Dibandingkan dengan idiom pembangun-untuk-nama-argumen, implementasi yang diperlukan jauh lebih sederhana, dan karenanya cenderung mengandung bug. Misalnya (dengan sintaks Java-ish):
class MyClass {
...
public static class A {
public final int value;
public A(int a) { value = a; }
}
...
}
Kompiler membantu Anda menjamin bahwa semua argumen diberikan; dengan Builder Anda harus memeriksa secara manual argumen yang hilang, atau menyandikan mesin negara ke dalam sistem jenis bahasa host - keduanya kemungkinan mengandung bug.
Ada pendekatan umum lain untuk mensimulasikan argumen bernama: objek parameter abstrak tunggal yang menggunakan sintaks kelas inline untuk menginisialisasi semua bidang. Di Jawa:
MyClass o = new MyClass(new MyClass.Arguments(){{ argA = a; argB = b; argC = c; ... }});
class MyClass {
...
public static abstract class Arguments {
public int argA;
public String ArgB;
...
}
}
Namun, adalah mungkin untuk melupakan bidang, dan ini adalah solusi yang cukup spesifik untuk bahasa (saya telah melihat kegunaan dalam JavaScript, C #, dan C).
Untungnya, konstruktor masih dapat memvalidasi semua argumen, yang tidak terjadi ketika objek Anda dibuat dalam keadaan sebagian-dibangun, dan mengharuskan pengguna untuk memberikan argumen lebih lanjut melalui setter atau init()
metode - yang memerlukan upaya pengkodean paling sedikit, tetapi buatlah lebih sulit untuk menulis program yang benar .
Jadi sementara ada banyak pendekatan untuk mengatasi "banyak parameter yang tidak disebutkan namanya membuat kode sulit untuk mempertahankan masalah", masalah lain tetap ada.
Mendekati masalah root
Misalnya masalah testability. Ketika saya menulis tes unit, saya perlu kemampuan untuk menyuntikkan data uji, dan untuk menyediakan implementasi tes untuk menghilangkan ketergantungan dan operasi yang memiliki efek samping eksternal. Saya tidak bisa melakukan itu ketika Anda membuat instance kelas dalam konstruktor Anda. Kecuali jika tanggung jawab kelas Anda adalah penciptaan objek lain, itu tidak boleh instantiate kelas non-sepele. Ini sejalan dengan masalah tanggung jawab tunggal. Semakin fokus tanggung jawab kelas, semakin mudah untuk menguji (dan sering lebih mudah digunakan).
Pendekatan termudah dan sering terbaik adalah untuk konstruktor untuk mengambil dependensi yang sepenuhnya dibangun sebagai parameter , meskipun ini mendorong tanggung jawab mengelola dependensi kepada penelepon - tidak ideal juga, kecuali dependensi adalah entitas independen dalam model domain Anda.
Kadang-kadang pabrik (abstrak) atau kerangka kerja injeksi ketergantungan penuh digunakan sebagai gantinya, meskipun ini mungkin berlebihan dalam sebagian besar kasus penggunaan. Secara khusus, ini hanya mengurangi jumlah argumen jika banyak dari argumen ini adalah objek kuasi-global atau nilai konfigurasi yang tidak berubah di antara instance objek. Misal jika parameter a
dan d
global-ish, kita akan dapatkan
Dependencies deps = new Dependencies(a, d);
...
MyClass o = deps.newMyClass(b, c, e, f, g);
class MyClass {
MyClass(Dependencies deps, B b, C c, E e, F f, G g) {
this.depA = deps.newDepA(b, c);
this.depB = deps.newDepB(e, f);
this.g = g;
}
...
}
class Dependencies {
private A a;
private D d;
public Dependencies(A a, D d) { this.a = a; this.d = d; }
public DepA newDepA(B b, C c) { return new DepA(a, b, c); }
public DepB newDepB(E e, F f) { return new DepB(d, e, f); }
public MyClass newMyClass(B b, C c, E e, F f, G g) {
return new MyClass(deps, b, c, e, f, g);
}
}
Bergantung pada aplikasinya, ini mungkin merupakan game-changer di mana metode pabrik pada akhirnya hampir tidak memiliki argumen karena semua dapat disediakan oleh manajer dependensi, atau mungkin sejumlah besar kode yang mempersulit instantiasi tanpa manfaat yang jelas. Pabrik-pabrik semacam itu jauh lebih berguna untuk memetakan antarmuka ke tipe-tipe konkret daripada untuk mengatur parameter. Namun, pendekatan ini mencoba untuk mengatasi masalah root dari terlalu banyak parameter daripada hanya menyembunyikannya dengan antarmuka yang cukup lancar.