Lebih lanjut tentang getter dan setter

Ini adalah prinsip desain berorientasi objek (OO) berusia 25 tahun bahwa Anda tidak boleh mengekspos implementasi objek ke kelas lain dalam program. Program ini tidak perlu sulit dipertahankan saat Anda mengekspos implementasi, terutama karena mengubah objek yang mengekspos implementasinya mengharuskan perubahan ke semua kelas yang menggunakan objek tersebut.

Sayangnya, idiom pengambil / penyetel yang oleh banyak programmer dianggap berorientasi objek melanggar prinsip OO fundamental ini secara sekop. Pertimbangkan contoh Moneykelas yang memiliki getValue()metode di atasnya yang mengembalikan "nilai" dalam dolar. Anda akan memiliki kode seperti berikut di seluruh program Anda:

double orderTotal; Jumlah uang = ...; // ... orderTotal + = jumlah.getValue (); // orderTotal harus dalam dolar

Masalah dengan pendekatan ini adalah bahwa kode di atas membuat asumsi besar tentang bagaimana Moneykelas diimplementasikan (bahwa "nilai" disimpan dalam a double). Kode yang membuat asumsi implementasi rusak saat implementasi berubah. Jika, misalnya, Anda perlu menginternasionalkan aplikasi Anda untuk mendukung mata uang selain dolar, maka getValue()tidak ada yang berarti. Anda dapat menambahkan getCurrency(), tetapi itu akan membuat semua kode yang mengelilingi getValue()panggilan menjadi jauh lebih rumit, terutama jika Anda tetap menggunakan strategi pengambil / penyetel untuk mendapatkan informasi yang Anda perlukan untuk melakukan pekerjaan itu. Implementasi tipikal (cacat) mungkin terlihat seperti ini:

Jumlah uang = ...; // ... nilai = jumlah.getValue (); mata uang = jumlah.getCurrency (); konversi = CurrencyTable.getConversionFactor (mata uang, USDOLLARS); total + = nilai * konversi; // ...

Perubahan ini terlalu rumit untuk ditangani dengan pemfaktoran ulang otomatis. Selain itu, Anda harus membuat perubahan semacam ini di mana pun dalam kode Anda.

Solusi tingkat logika bisnis untuk masalah ini adalah melakukan pekerjaan di objek yang memiliki informasi yang diperlukan untuk melakukan pekerjaan tersebut. Alih-alih mengekstrak "nilai" untuk melakukan beberapa operasi eksternal di atasnya, Anda harus meminta Moneykelas melakukan semua operasi yang berhubungan dengan uang, termasuk konversi mata uang. Objek yang terstruktur dengan baik akan menangani total seperti ini:

Total uang = ...; Jumlah uang = ...; total.increaseBy (jumlah);

The add()Metode akan mencari tahu mata uang operan, melakukan konversi mata uang yang diperlukan (yang, benar, operasi pada uang ), dan memperbarui total. Jika Anda menggunakan strategi objek-yang-memiliki-informasi-melakukan-pekerjaan ini untuk memulai, gagasan mata uang dapat ditambahkan ke Moneykelas tanpa perubahan apa pun yang diperlukan dalam kode yang menggunakan Moneyobjek. Artinya, pekerjaan refactoring satu dolar saja untuk implementasi internasional akan terkonsentrasi di satu tempat: Moneykelas.

Masalah

Sebagian besar pemrogram tidak mengalami kesulitan untuk memahami konsep ini pada tingkat logika bisnis (meskipun perlu upaya untuk secara konsisten berpikir seperti itu). Masalah mulai muncul, bagaimanapun, ketika antarmuka pengguna (UI) memasuki gambar. Masalahnya bukan karena Anda tidak dapat menerapkan teknik seperti yang baru saja saya jelaskan untuk membangun UI, tetapi banyak programmer yang terkunci dalam mentalitas pengambil / penyetel ketika berhubungan dengan antarmuka pengguna. Saya menyalahkan masalah ini pada alat konstruksi kode yang pada dasarnya prosedural seperti Visual Basic dan klonnya (termasuk pembuat Java UI) yang memaksa Anda masuk ke cara berpikir prosedural, pengambil / penyetel ini.

(Penggalian: Beberapa dari Anda akan menolak pernyataan sebelumnya dan berteriak bahwa VB didasarkan pada arsitektur Model-View-Controller (MVC) yang suci, begitu pula sakral. Ingatlah bahwa MVC dikembangkan hampir 30 tahun yang lalu. Pada awalnya 1970-an, superkomputer terbesar setara dengan desktop saat ini. Sebagian besar mesin (seperti DEC PDP-11) adalah komputer 16-bit, dengan memori 64 KB, dan kecepatan clock diukur dalam puluhan megahertz. Antarmuka pengguna Anda mungkin a tumpukan kartu berlubang. Jika Anda cukup beruntung memiliki terminal video, Anda mungkin telah menggunakan sistem input / output (I / O) konsol berbasis ASCII. Kami telah belajar banyak dalam 30 tahun terakhir. Bahkan Java Swing harus mengganti MVC dengan arsitektur "model-terpisah" yang serupa, terutama karena MVC murni tidak cukup mengisolasi lapisan UI dan model domain.)

Jadi, mari kita definisikan masalah secara singkat:

Jika sebuah objek tidak dapat mengekspos informasi implementasi (melalui metode get / set atau dengan cara lain), maka masuk akal bahwa objek entah bagaimana harus membuat antarmuka penggunanya sendiri. Artinya, jika cara atribut objek direpresentasikan disembunyikan dari program lainnya, Anda tidak dapat mengekstrak atribut tersebut untuk membuat UI.

Perhatikan, omong-omong, Anda tidak menyembunyikan fakta bahwa ada atribut. (Saya mendefinisikan atribut , di sini, sebagai karakteristik penting dari objek.) Anda tahu bahwa Employeeharus memiliki atribut gaji atau upah, jika tidak maka tidak akan menjadi Employee. (Ini akan menjadi a Person, a Volunteer, a Vagrant, atau sesuatu yang lain yang tidak memiliki gaji.) Apa yang tidak Anda ketahui — atau ingin Anda ketahui — adalah bagaimana gaji tersebut direpresentasikan di dalam objek. Ini bisa menjadi double, seorang String, sebuah skala long, atau kode-biner desimal. Ini bisa berupa atribut "sintetik" atau "turunan", yang dihitung pada waktu proses (dari nilai gaji atau jabatan, misalnya, atau dengan mengambil nilai dari database). Meskipun metode get memang bisa menyembunyikan beberapa detail implementasi ini,seperti yang kita lihat denganMoney Misalnya, tidak cukup menyembunyikan.

Jadi, bagaimana sebuah objek menghasilkan UI-nya sendiri dan tetap dapat dipelihara? Hanya objek paling sederhana yang dapat mendukung sesuatu seperti displayYourself()metode. Objek realistis harus:

  • Menampilkan dirinya sendiri dalam format yang berbeda (XML, SQL, nilai yang dipisahkan koma, dll.).
  • Menampilkan tampilan berbeda dari dirinya sendiri (satu tampilan mungkin menampilkan semua atribut; yang lain mungkin hanya menampilkan subset atribut; dan tampilan ketiga mungkin menampilkan atribut dengan cara yang berbeda).
  • Menampilkan dirinya sendiri di lingkungan yang berbeda (sisi klien ( JComponent) dan disajikan-ke-klien (HTML), misalnya) dan menangani masukan dan keluaran di kedua lingkungan.

Beberapa pembaca artikel pengambil / penyetel saya sebelumnya melompat ke kesimpulan bahwa saya menganjurkan agar Anda menambahkan metode ke objek untuk mencakup semua kemungkinan ini, tetapi "solusi" itu jelas tidak masuk akal. Tidak hanya objek kelas berat yang dihasilkan terlalu rumit, Anda harus terus memodifikasinya untuk menangani persyaratan UI baru. Praktisnya, sebuah objek tidak bisa membangun semua kemungkinan antarmuka pengguna untuk dirinya sendiri, jika tidak ada alasan lain selain banyak dari UI tersebut bahkan tidak dipahami saat kelas dibuat.

Bangun solusi

Solusi masalah ini adalah memisahkan kode UI dari objek bisnis inti dengan meletakkannya ke dalam kelas objek yang terpisah. Artinya, Anda harus memisahkan beberapa fungsi yang mungkin ada di objek menjadi objek yang terpisah seluruhnya.

Percabangan metode objek ini muncul dalam beberapa pola desain. Anda kemungkinan besar akrab dengan Strategi, yang digunakan dengan berbagai java.awt.Containerkelas untuk melakukan tata letak. Anda bisa memecahkan masalah tata letak dengan solusi derivasi: FlowLayoutPanel, GridLayoutPanel, BorderLayoutPanel, dll, tapi itu mandat terlalu banyak kelas dan banyak kode digandakan di kelas-kelas. Solusi tunggal kelas berat (menambahkan metode ke Containersuka layOutAsGrid(), layOutAsFlow()dll.) Juga tidak praktis karena Anda tidak dapat mengubah kode sumber Containerhanya karena Anda memerlukan tata letak yang tidak didukung. Dalam pola Strategi, Anda membuat Strategyantarmuka ( LayoutManager) dilaksanakan oleh beberapa Concrete Strategykelas ( FlowLayout, GridLayout, dll). Anda kemudian memberi tahu sebuah Contextobjek (aContainer) bagaimana melakukan sesuatu dengan memberikan Strategyobjek. (Anda melewati Containersuatu LayoutManageryang mendefinisikan strategi tata letak.)

Pola Builder mirip dengan Strategi. Perbedaan utamanya adalah bahwa Builderkelas mengimplementasikan strategi untuk membangun sesuatu (seperti JComponentaliran XML atau yang mewakili status objek). Builderobjek biasanya membangun produk mereka menggunakan proses multistage juga. Artinya, panggilan ke berbagai metode Builderdiperlukan untuk menyelesaikan proses konstruksi, dan Builderbiasanya tidak mengetahui urutan panggilan akan dibuat atau berapa kali salah satu metodenya akan dipanggil. Karakteristik Builder yang paling penting adalah bahwa objek bisnis (disebut Context) tidak tahu persis apa yang Buildersedang dibangun oleh objek tersebut. Pola tersebut mengisolasi objek bisnis dari representasi.

Cara terbaik untuk melihat bagaimana pembangun sederhana bekerja adalah dengan melihatnya. Pertama mari kita lihat Context, objek bisnis yang perlu mengekspos antarmuka pengguna. Kode 1 menunjukkan Employeekelas sederhana . The Employeememiliki name, id, dan salaryatribut. (Rintisan untuk kelas-kelas ini ada di bagian bawah daftar, tetapi rintisan ini hanyalah pengganti untuk hal yang nyata. Anda dapat — saya harap — dengan mudah membayangkan bagaimana kelas-kelas ini akan bekerja.)

Khusus ini Contextmenggunakan apa yang saya anggap sebagai pembangun dua arah. Gang klasik Empat Builder berjalan dalam satu arah (keluaran), tetapi saya juga telah menambahkan Buildersebuah Employeeobjek yang dapat digunakan untuk menginisialisasi dirinya sendiri. Dua Builderantarmuka diperlukan. The Employee.Exporterantarmuka (Listing 1, baris 8) menangani arah output. Ini mendefinisikan antarmuka ke Builderobjek yang membangun representasi dari objek saat ini. The Employeedelegasi UI konstruksi sebenarnya ke Builderdalam export()metode (on line 31). Itu Buildertidak melewati bidang sebenarnya, melainkan menggunakan Strings untuk meneruskan representasi bidang tersebut.

Daftar 1. Karyawan: Konteks Builder

1 impor java.util.Locale; 2 3 kelas publik Karyawan 4 {Nama pribadi nama; 5 id EmployeeId pribadi; 6 gaji uang pribadi; 7 8 antarmuka publik Eksportir 9 {void addName (Nama string); 10 batal addID (String id); 11 batal addSalary (String gaji); 12} 13 14 importir antarmuka publik 15 {String providerName (); 16 String providerID (); 17 String giveSalary (); 18 kosong terbuka (); 19 batal dekat (); 20} 21 22 Karyawan publik (Importir pembangun) 23 {builder.open (); 24 this.name = Nama baru (builder.provideName ()); 25 this.id = new EmployeeId (builder.provideID ()); 26 this.salary = Uang baru (builder.provideSalary (), 27 baru Lokal ("en", "US")); 28 builder.close (); 29} 30 31 ekspor public void (Eksportir builder) 32 {builder.addName (name.toString ()); 33 builder.addID (id.toString ()); 34 builder.addSalary (gaji.toString ()); 35} 36 37// ... 38} 39 // ---------------------------------------- ------------------------------ 40 // Barang uji unit 41 // 42 kelas Nama 43 {private String value; 44 Nama publik (Nilai string) 45 {this.value = value; 46} 47 public String toString () {nilai kembali; }; 48} 49 50 kelas EmployeeId 51 {private String value; 52 EmployeeId publik (nilai string) 53 {this.value = value; 54} 55 public String toString () {nilai kembali; } 56} 57 58 kelas Uang 59 {private String value; 60 Uang publik (Nilai string, Lokasi Lokal) 61 {this.value = value; 62} 63 public String toString () {nilai kembali; } 64}

Mari kita lihat contohnya. Kode berikut membangun UI Gambar 1:

Karyawan wilma = ...; JComponentExporter uiBuilder = new JComponentExporter (); // Buat pembangun wilma.export (uiBuilder); // Bangun antarmuka pengguna JComponent userInterface = uiBuilder.getJComponent (); // ... someContainer.add (userInterface);

Kode 2 menunjukkan sumber untuk JComponentExporter. Seperti yang Anda lihat, semua kode yang terkait dengan UI terkonsentrasi di Concrete Builder(the JComponentExporter), dan Context(the Employee) menjalankan proses build tanpa mengetahui secara pasti apa yang sedang dibuatnya.

Daftar 2. Mengekspor ke UI sisi klien

1 impor javax.swing. *; 2 import java.awt. *; 3 import java.awt.event. *; 4 5 kelas JComponentExporter mengimplementasikan Employee.Exporter 6 {private String name, id, gaji; 7 8 public void addName (Nama string) {this.name = name; } 9 addID public void (String id) {this.id = id; } 10 public void addSalary (String gaji) {this.salary = gaji; } 11 12 JComponent getJComponent () 13 {JComponent panel = new JPanel (); 14 panel.setLayout (GridLayout baru (3,2)); 15 panel.add (JLabel baru ("Nama:")); 16 panel.add (JLabel baru (nama)); 17 panel.add (JLabel baru ("ID Karyawan:")); 18 panel.add (JLabel baru (id)); 19 panel.add (JLabel baru ("Gaji:")); 20 panel.add (JLabel baru (gaji)); 21 panel kembali; 22} 23}