Kode Bersih dan Objek Hibrid dan Fitur Cemburu


10

Jadi saya baru-baru ini membuat beberapa refactoring utama pada kode saya. Salah satu hal utama yang saya coba lakukan adalah membagi kelas saya menjadi objek data dan objek pekerja. Ini terinspirasi, antara lain, oleh bagian dari Kode Bersih ini :

Hibrida

Kebingungan ini kadang-kadang menyebabkan struktur data hibrida yang tidak menguntungkan yang setengah objek dan setengah struktur data. Mereka memiliki fungsi yang melakukan hal-hal signifikan, dan mereka juga memiliki variabel publik atau pengakses publik dan mutator yang, untuk semua maksud dan tujuan, membuat variabel pribadi menjadi publik, menggoda fungsi eksternal lainnya untuk menggunakan variabel-variabel itu seperti cara program prosedural menggunakan suatu struktur data.

Hibrida seperti itu membuat sulit untuk menambahkan fungsi baru tetapi juga membuatnya sulit untuk menambahkan struktur data baru. Mereka adalah yang terburuk dari kedua dunia. Hindari membuatnya. Mereka adalah indikasi dari desain kacau yang penulis tidak yakin - atau lebih buruk, tidak tahu - apakah mereka membutuhkan perlindungan dari fungsi atau jenis.

Baru-baru ini saya melihat kode ke salah satu objek pekerja saya (yang kebetulan menerapkan Pola Pengunjung ) dan melihat ini:

@Override
public void visit(MarketTrade trade) {
    this.data.handleTrade(trade);
    updateRun(trade);
}

private void updateRun(MarketTrade newTrade) {
    if(this.data.getLastAggressor() != newTrade.getAggressor()) {
        this.data.setRunLength(0);
        this.data.setLastAggressor(newTrade.getAggressor());
    }
    this.data.setRunLength(this.data.getRunLength() + newTrade.getLots());
}

Saya langsung berkata pada diri saya sendiri "fitur iri! Logika ini harus di Datakelas - khususnya dalam handleTrademetode. handleTradeDan updateRunharus selalu terjadi bersama". Tetapi kemudian saya berpikir "kelas data hanyalah publicstruktur data, jika saya mulai melakukan itu, maka akan muncul Objek Hibrid!"

Apa yang lebih baik dan mengapa? Bagaimana Anda memutuskan mana yang harus dilakukan?


2
Mengapa 'data' harus menjadi struktur data sama sekali. Ini memiliki perilaku yang jelas. Jadi matikan saja semua getter dan setter, sehingga tidak ada objek yang dapat memanipulasi keadaan internal.
Cormac Mulhall

Jawaban:


9

Teks yang Anda kutip memiliki saran yang bagus, meskipun saya akan mengganti "struktur data" dengan "catatan", dengan asumsi bahwa sesuatu seperti struct dimaksudkan. Catatan hanyalah kumpulan data yang bodoh. Sementara mereka mungkin bisa berubah (dan dengan demikian stateful dalam pola pikir fungsional-pemrograman), mereka tidak memiliki keadaan internal, tidak ada invarian yang harus dilindungi. Ini benar-benar valid untuk menambahkan operasi ke catatan yang membuat penggunaannya lebih mudah.

Sebagai contoh, kita dapat berpendapat bahwa vektor 3D adalah catatan bodoh. Namun, itu seharusnya tidak mencegah kita menambahkan metode seperti add, yang membuat menambahkan vektor lebih mudah. Menambahkan perilaku tidak mengubah rekaman (tidak begitu bodoh) menjadi hibrida.

Baris ini dilintasi ketika antarmuka publik dari suatu objek memungkinkan kita untuk memecah enkapsulasi: Ada beberapa internal yang dapat kita akses secara langsung, sehingga membawa objek ke keadaan tidak valid. Bagi saya yang Datamemang memiliki keadaan, dan itu dapat dibawa ke keadaan tidak valid:

  • Setelah menangani perdagangan, agresor terakhir mungkin tidak diperbarui.
  • Agresor terakhir dapat diperbarui bahkan ketika tidak ada perdagangan baru terjadi.
  • Panjang run mungkin mempertahankan nilai lamanya bahkan ketika agresor diperbarui.
  • dll.

Jika ada negara yang valid untuk data Anda, maka semuanya baik-baik saja dengan kode Anda, dan Anda dapat melanjutkan. Sebaliknya: DataKelas bertanggung jawab atas konsistensi datanya sendiri. Jika menangani perdagangan selalu melibatkan memperbarui agresor, perilaku ini harus menjadi bagian dari Datakelas. Jika mengubah agresor melibatkan pengaturan panjang lari ke nol, perilaku ini harus menjadi bagian dari Datakelas. Datatidak pernah menjadi catatan bodoh. Anda telah membuatnya menjadi hybrid dengan menambahkan setter publik.

Ada satu skenario di mana Anda dapat mempertimbangkan untuk melonggarkan tanggung jawab yang ketat ini: Jika Databersifat pribadi untuk proyek Anda dan karenanya bukan bagian dari antarmuka publik, Anda masih dapat memastikan penggunaan kelas yang tepat. Namun, ini menempatkan tanggung jawab untuk menjaga Datakonsistensi di seluruh kode, daripada mengumpulkannya di lokasi pusat.

Baru-baru ini saya menulis jawaban tentang enkapsulasi , yang membahas lebih dalam tentang apa itu enkapsulasi dan bagaimana Anda dapat memastikannya.


5

Fakta bahwa handleTrade()dan updateRun()selalu terjadi bersama-sama (dan metode kedua sebenarnya pada pengunjung dan memanggil beberapa metode lain pada objek data) berbau kopling temporal . Ini berarti bahwa Anda harus memanggil metode dalam urutan tertentu, dan saya kira metode memanggil yang rusak akan merusak sesuatu yang terburuk, atau gagal memberikan hasil yang berarti di terbaik. Tidak baik.

Biasanya cara yang benar untuk memperbaiki ketergantungan yang keluar adalah untuk setiap metode untuk mengembalikan hasil yang dapat dimasukkan ke metode berikutnya atau ditindaklanjuti secara langsung.

Kode lama:

MyObject x = ...;
x.actionOne();
x.actionTwo();
String result = x.actionThree();

Kode baru:

MyObject x = ...;
OneResult r1 = x.actionOne();
TwoResult r2 = r1.actionTwo();
String result = r2.actionThree();

Ini memiliki beberapa keunggulan:

  • Ini memindahkan masalah yang terpisah menjadi objek yang terpisah ( SRP ).
  • Itu menghapus temporal coupling: tidak mungkin untuk memanggil metode rusak, dan tanda tangan metode menyediakan dokumentasi implisit tentang bagaimana memanggil mereka. Pernahkah Anda melihat dokumentasi, melihat objek yang Anda inginkan, dan bekerja mundur? Saya ingin objek Z. Tapi saya butuh Y untuk mendapatkan Z. Untuk mendapatkan Y, saya butuh X. Aha! Saya memiliki W, yang diperlukan untuk mendapatkan X. Rantai semuanya, dan W Anda sekarang dapat digunakan untuk mendapatkan Z.
  • Memisahkan objek seperti ini lebih memungkinkan untuk membuatnya tidak berubah, yang memiliki banyak keunggulan di luar cakupan pertanyaan ini. Pengambilan cepatnya adalah bahwa objek yang tidak berubah cenderung mengarah ke kode yang lebih aman.

Tidak ada sambungan sementara antara kedua pemanggilan metode tersebut. Tukar pesanan mereka dan perilaku tidak berubah.
durron597

1
Saya awalnya juga memikirkan penggabungan sekuensial / temporal ketika membaca pertanyaan, tetapi kemudian memperhatikan bahwa updateRunmetode ini bersifat pribadi . Menghindari penggabungan berurutan adalah saran yang bagus, tetapi itu hanya berlaku untuk desain API / antarmuka publik, dan tidak untuk detail implementasi. Pertanyaan sebenarnya tampaknya apakah updateRunharus di pengunjung atau di kelas data, dan saya tidak melihat bagaimana jawaban ini mengatasi masalah itu.
amon

Visibilitas updateRuntidak relevan, yang penting adalah implementasi this.datayang tidak ada dalam pertanyaan dan objek yang dimanipulasi oleh objek pengunjung.

Jika ada, fakta bahwa pengunjung ini hanya menelepon sekelompok setter dan tidak benar-benar memproses apa pun adalah alasan untuk kopling sementara tidak ada. Kemungkinan besar tidak masalah urutan pemesan apa yang dipanggil.

0

Dari sudut pandang saya kelas harus mengandung "nilai untuk negara (variabel anggota) dan implementasi perilaku (fungsi anggota, metode)".

"Struktur data hibrid yang tidak menguntungkan" muncul jika Anda membuat variabel-variabel anggota negara bagian (atau pengambil / penentu mereka) publik yang tidak boleh publik.

Jadi saya melihat tidak perlu memiliki kelas yang terpisah untuk objek data data dan objek pekerja.

Anda harus dapat menjaga variabel negara-anggota-non-publik (layer database Anda harus mampu menangani variabel-anggota-non-publik)

Cemburu fitur adalah kelas yang menggunakan metode kelas lain secara berlebihan. Lihat Code_smell . Memiliki kelas dengan metode dan status akan menghilangkan ini.

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.