Desain Kelas Berorientasi Objek

12

Saya bertanya-tanya tentang desain kelas berorientasi objek yang baik. Secara khusus, saya kesulitan menentukan antara opsi-opsi ini:

  1. metode statis vs contoh
  2. metode tanpa parameter atau nilai balik vs metode dengan parameter dan nilai balik
  3. tumpang tindih vs fungsionalitas metode yang berbeda
  4. metode pribadi vs publik

Contoh 1:

Implementasi ini menggunakan metode instan, tanpa nilai balik atau parameter, tanpa fungsionalitas yang tumpang tindih, dan semua metode publik

XmlReader reader = new XmlReader(url);
reader.openUrl();
reader.readXml();
Document result = reader.getDocument();

Contoh 2:

Implementasi ini menggunakan metode statis, dengan nilai dan parameter pengembalian, dengan fungsionalitas yang tumpang tindih dan metode pribadi

Document result = XmlReader.readXml(url); 

Dalam contoh satu, semua metode adalah contoh publik, yang membuatnya mudah untuk unit test. Meskipun semua metode berbeda, readXml () bergantung pada openUrl () di openUrl itu () harus dipanggil terlebih dahulu. Semua data dideklarasikan dalam bidang contoh, jadi tidak ada nilai balik atau parameter dalam metode apa pun, kecuali di konstruktor dan pengakses.

Dalam contoh dua, hanya satu metode yang bersifat publik, sisanya adalah statis privat, yang membuatnya sulit untuk diuji unit. Metode tumpang tindih dalam readXml () yang memanggil openUrl (). Tidak ada bidang, semua data dilewatkan sebagai parameter dalam metode dan hasilnya dikembalikan segera.

Prinsip apa yang harus saya ikuti untuk melakukan pemrograman berorientasi objek yang tepat?

siamii
sumber
3
Hal-hal statis buruk ketika Anda melakukan multi-threading. Suatu hari, saya punya XMLWriter statis, seperti XMLWriter.write (data, fileurl). Namun, karena ia memiliki FileStream statis pribadi, menggunakan kelas ini dari beberapa utas pada saat yang sama, menyebabkan utas kedua menimpa Utas pertama FileStream, menyebabkan kesalahan yang akan sangat sulit ditemukan. Kelas statis dengan anggota statis + multi-threading adalah resep untuk bencana.
Per Alexandersson
1
@ Maxinum. Masalah yang Anda gambarkan adalah masalah keadaan, bukan masalah "statis". Jika Anda menggunakan singleton dengan anggota non-statis, Anda akan memiliki masalah yang sama dengan multi-threading.
mike30
2
@Per Alexandersson Metode statis tidak buruk dalam kaitannya dengan konkurensi. Keadaan statis buruk. Inilah sebabnya mengapa pemrograman fungsional, di mana semua metode statis, bekerja dengan sangat baik dalam situasi bersamaan.
Yuli Bonner

Jawaban:

11

Contoh 2 cukup buruk untuk pengujian ... dan saya tidak bermaksud bahwa Anda tidak dapat menguji internal. Anda juga tidak dapat mengganti XmlReaderobjek Anda dengan objek tiruan karena Anda tidak memiliki objek sama sekali.

Contoh 1 sulit untuk digunakan. Bagaimana dengan

XmlReader reader = new XmlReader(url);
Document result = reader.getDocument();

yang tidak lebih sulit untuk digunakan daripada metode statis Anda.

Hal-hal seperti membuka URL, membaca XML, mengonversi byte ke string, parsing, soket penutup, dan apa pun, tidak menarik. Membuat objek dan menggunakannya adalah penting.

Jadi IMHO, Desain OO yang tepat adalah untuk membuat hanya dua hal yang bersifat publik (kecuali jika Anda benar-benar membutuhkan langkah-langkah perantara untuk beberapa alasan). Statis itu jahat.

maaartinus
sumber
-1. ANDA benar-benar BISA mengganti XmlReader dengan objek tiruan. Bukan dengan kerangka moick sumber terbuka mati otak, tetapi dengan kelas industri yang baik Anda bisa;) Biaya beberapa ratus USD per pengembang tetapi bekerja dengan baik untuk menguji fungsionalitas disegel di API yang Anda publikasikan.
TomTom
2
+1 karena tidak mendaftar ke promosi penjualan TomTom. Ketika saya menginginkan satu liner, saya lebih suka menulis sesuatu seperti Document result = new XmlReader(url).getDocument();Mengapa? Sehingga saya dapat memutakhirkannya Document result = whateverReader.getDocument();dan telah whateverReadermenyerahkannya kepada saya dengan sesuatu yang lain.
candied_orange
6

Inilah masalahnya - tidak ada jawaban yang benar, dan tidak ada definisi absolut dari "desain berorientasi objek yang tepat" (beberapa orang akan menawarkan Anda satu, tetapi mereka naif ... beri mereka waktu).

Semuanya bermuara pada TUJUAN Anda.

Anda seorang seniman, dan kertasnya kosong. Anda dapat menggambar potret sisi hitam dan putih yang halus, dengan pensil halus, atau lukisan abstrak dengan luka besar campuran neon. Atau apa pun di antaranya.

Jadi, apa yang benar untuk masalah yang Anda selesaikan? Apa keluhan orang-orang yang perlu menggunakan kelas Anda untuk bekerja dengan xml? Apa yang sulit dari pekerjaan mereka? Kode apa yang mereka coba tulis yang mengelilingi panggilan ke perpustakaan Anda, dan bagaimana Anda bisa membantu agar mengalir lebih baik untuk mereka?

Apakah mereka mau lebih ringkas? Apakah mereka ingin menjadi sangat pintar dalam mencari tahu nilai default untuk parameter, sehingga mereka tidak perlu menentukan banyak (atau apa pun) dan itu menebak dengan benar? Bisakah Anda mengotomatiskan tugas pengaturan dan pembersihan yang dibutuhkan perpustakaan Anda sehingga mustahil bagi mereka untuk melupakan langkah-langkah itu? Apa lagi yang bisa Anda lakukan untuk mereka?

Sial, yang mungkin perlu Anda lakukan adalah mengkodekannya dengan 4 atau 5 cara berbeda, lalu mengenakan topi konsumen Anda dan menulis kode yang menggunakan semua 5, dan melihat mana yang terasa lebih baik. Jika Anda tidak bisa melakukan itu untuk seluruh pustaka Anda, maka lakukan untuk subset. Dan Anda perlu menambahkan beberapa alternatif tambahan ke daftar Anda juga - bagaimana dengan antarmuka yang lancar, atau pendekatan yang lebih fungsional, atau parameter bernama, atau sesuatu yang didasarkan pada DynamicObject sehingga Anda dapat membuat "metode semu" yang bermakna yang membantu mereka di luar?

Mengapa jQuery raja sekarang? Karena Resig dan tim mengikuti proses ini, sampai mereka menemukan prinsip sintaksis yang sangat mengurangi jumlah kode JS yang diperlukan untuk bekerja dengan dom dan acara. Prinsip sintaksis itu tidak jelas bagi mereka atau siapa pun ketika mereka mulai. Mereka menemukannya.

Sebagai seorang programmer, itulah panggilan tertinggi Anda. Anda meraba-raba dalam gelap mencoba barang sampai Anda menemukannya. Ketika Anda melakukannya, Anda akan tahu. Dan Anda akan memberi pengguna Anda lompatan produktivitas yang sangat besar . Dan itulah yang dimaksud dengan desain (dalam bidang perangkat lunak).

Charlie Flowers
sumber
1
Ini menyiratkan bahwa tidak ada perbedaan antara praktik terbaik dan praktik lain selain dari bagaimana rasanya. Ini adalah bagaimana Anda berakhir dengan bola lumpur yang tidak dapat dipelihara - karena bagi banyak pengembang, menjangkau melintasi batas-batas Kelas, dll., "Terasa" benar-benar hebat.
Amy Blankenship
@Amy Blankenship, saya akan mengatakan dengan tegas bahwa tidak ada satu "cara terbaik" untuk membuat pilihan yang ditanyakan OP. Itu tergantung pada sejuta hal, dan ada sejuta derajat kebebasan. Namun, saya pikir ada tempat untuk "Best Practices," dan itu adalah dalam tim lingkungan, di mana pilihan-pilihan tertentu telah dibuat, dan kita membutuhkan seluruh tim untuk tetap konsisten dengan pilihan-pilihan sebelumnya. Dengan kata lain, dalam konteks tertentu , mungkin ada alasan untuk melabeli hal-hal tertentu "praktik terbaik." Namun OP belum memberikan konteks apa pun. Dia sedang membangun sesuatu dan ...
Charlie Flowers
... dia menghadapi semua pilihan yang mungkin ini. Tidak ada satu "jawaban yang tepat" untuk pilihan-pilihan itu. Ini didorong oleh tujuan dan titik sakit sistem. Saya menjamin Anda bahwa programmer Haskell tidak berpikir semua metode harus menjadi metode instan. Dan programmer kernel Linux tidak berpikir membuat hal-hal yang dapat diakses oleh TDD sangat penting sekali. Dan programmer game C ++ sering lebih suka menggabungkan data mereka ke dalam struktur data yang ketat dalam memori daripada merangkum semuanya menjadi objek. Setiap "Praktik Terbaik" hanyalah "praktik terbaik" dalam konteks tertentu, dan merupakan anti-pola dalam beberapa konteks lain.
Charlie Flowers
@AmyBlankenship Satu hal lagi: Saya tidak setuju bahwa menjangkau melintasi batas-batas Kelas "terasa sangat luar biasa." Ini mengarah ke bola lumpur yang tidak dapat dipelihara, yang terasa mengerikan . Saya pikir Anda mencoba menyelesaikan masalah bahwa beberapa pekerja ceroboh / tidak termotivasi / sangat tidak berpengalaman. Dalam hal itu, seseorang yang berhati-hati, termotivasi & berpengalaman harus membuat pilihan kunci, & menyebutnya "Praktik Terbaik." Tetapi, orang yang memilih "Praktik Terbaik" itu masih membuat pilihan berdasarkan apa yang "terasa benar," dan tidak ada jawaban yang tepat. Anda hanya mengendalikan siapa yang membuat pilihan.
Charlie Flowers
Saya telah bekerja dengan beberapa programer yang menganggap diri mereka sebagai tingkat senior dan dianggap demikian oleh manajemen yang sangat percaya bahwa statika dan Lajang benar-benar cara yang tepat untuk menangani masalah komunikasi. Bagian statis dari pertanyaan ini bahkan tidak akan ditanyakan jika menjangkau melintasi batas-batas Kelas seperti itu "terasa" salah bagi pengembang, juga tidak akan jawaban yang menganjurkan alternatif statis menerima suara naik.
Amy Blankenship
3

Opsi kedua lebih baik karena lebih mudah bagi orang untuk menggunakan (bahkan jika itu hanya Anda), jauh lebih sederhana.

Untuk pengujian unit, saya hanya akan menguji antarmuka bukan internal jika Anda benar-benar ingin kemudian memindahkan internal dari pribadi ke yang dilindungi.


sumber
2
Anda juga dapat menjadikan paket metode Anda pribadi (default), jika kotak uji Anda dalam paket yang sama, untuk keperluan pengujian unit.
Poin bagus - itu lebih baik.
3

1. Statis vs contoh

Saya pikir ada pedoman yang sangat jelas tentang apa yang desain OO baik dan apa yang tidak. Masalahnya adalah bahwa blogosphere membuatnya sulit untuk memisahkan yang baik dari yang buruk dan yang buruk. Anda dapat menemukan beberapa jenis referensi yang mendukung bahkan praktek terburuk yang dapat Anda pikirkan.

Dan praktik terburuk yang dapat saya pikirkan adalah Global State, termasuk statika yang Anda sebutkan dan Singleton favorit semua orang. Beberapa kutipan dari artikel klasik Misko Hevery tentang masalah ini .

Untuk benar-benar memahami dependensi, pengembang harus membaca setiap baris kode. Ini menyebabkan Aksi Seram pada Jarak: ketika menjalankan test suite, keadaan global yang dimutasi dalam satu tes dapat menyebabkan tes berikutnya atau paralel gagal secara tak terduga. Hentikan ketergantungan statis menggunakan injeksi ketergantungan manual atau Guice.

Aksi seram di kejauhan adalah ketika kita menjalankan satu hal yang kita yakini terisolasi (karena kita tidak memberikan referensi), tetapi interaksi tak terduga dan perubahan keadaan terjadi di lokasi yang jauh dari sistem yang tidak kita beri tahu objeknya. Ini hanya dapat terjadi melalui negara global.

Anda mungkin tidak pernah berpikir seperti ini sebelumnya, tetapi setiap kali Anda menggunakan keadaan statis, Anda membuat saluran komunikasi rahasia dan tidak membuatnya jelas di API. Aksi Seram di Jarak memaksa pengembang untuk membaca setiap baris kode untuk memahami interaksi potensial, menurunkan produktivitas pengembang, dan membingungkan anggota tim baru.

Apa intinya adalah bahwa Anda tidak boleh memberikan referensi statis untuk apa pun yang memiliki semacam keadaan tersimpan. Satu-satunya tempat saya menggunakan statika adalah untuk konstanta yang disebutkan, dan saya memiliki keraguan tentang hal itu.

2. Metode dengan parameter input dan nilai balik vs. metode tanpa metode

Hal yang perlu Anda sadari adalah bahwa metode yang tidak memiliki parameter input dan parameter output tidak cukup dijamin untuk beroperasi pada semacam keadaan yang disimpan secara internal (jika tidak, apa yang mereka lakukan?). Ada seluruh bahasa yang dibangun berdasarkan gagasan untuk menghindari keadaan tersimpan.

Setiap kali Anda menyimpan status, Anda memiliki kemungkinan untuk efek samping, jadi pastikan Anda selalu menggunakannya dengan penuh pertimbangan. Ini menyiratkan bahwa Anda harus memilih fungsi dengan input dan / atau output yang ditentukan.

Dan, pada kenyataannya, fungsi yang telah menentukan input dan output jauh lebih mudah untuk diuji - Anda tidak harus menjalankan fungsi di sini dan melihat ke sana untuk melihat apa yang terjadi, dan Anda tidak perlu mengatur properti di suatu tempat lain sebelum Anda menjalankan fungsi yang sedang diuji.

Anda juga dapat menggunakan fungsi jenis ini dengan aman sebagai statika. Namun, saya tidak akan melakukannya, karena jika saya kemudian ingin menggunakan implementasi yang sedikit berbeda dari fungsi itu di suatu tempat, daripada memberikan contoh yang berbeda dengan implementasi baru, saya terjebak dengan tidak ada cara untuk mengganti fungsi.

3. Tumpang tindih vs Berbeda

Saya tidak mengerti pertanyaannya. Apa keuntungannya dalam 2 metode yang tumpang tindih?

4. Pribadi vs. Publik

Jangan memaparkan apa pun yang tidak perlu Anda ungkapkan. Namun, saya juga bukan penggemar pribadi. Saya bukan pengembang C #, tetapi pengembang ActionScript. Saya telah menghabiskan banyak waktu dalam kode Kerangka Flex Adobe, yang ditulis sekitar tahun 2007. Dan mereka membuat beberapa pilihan yang sangat buruk tentang apa yang harus dijadikan pribadi, yang membuatnya menjadi semacam mimpi buruk ketika mencoba memperluas Kelas mereka.

Jadi, kecuali jika Anda berpikir Anda adalah arsitek yang lebih baik daripada pengembang Adobe sekitar tahun 2007 (dari pertanyaan Anda, saya akan mengatakan Anda memiliki beberapa tahun lagi sebelum Anda memiliki kesempatan untuk membuat klaim itu), Anda mungkin ingin secara default dilindungi saja. .


Ada beberapa masalah dengan contoh kode Anda yang berarti bahwa mereka tidak dirancang dengan baik, sehingga tidak mungkin untuk memilih A atau B.

Untuk satu hal, Anda mungkin harus memisahkan pembuatan objek dari penggunaannya . Jadi Anda biasanya tidak akan memiliki new XMLReader()hak di sebelah tempat itu digunakan.

Juga, seperti yang dikatakan @djna, Anda harus merangkum metode yang digunakan dalam penggunaan Pustaka XML Anda, sehingga API Anda (contoh instance) mungkin disederhanakan menjadi:

_document Document = reader.read(info);

Saya tidak tahu cara kerja C #, tetapi karena saya telah bekerja dengan sejumlah teknologi web, saya akan curiga bahwa Anda tidak akan selalu dapat langsung mengembalikan dokumen XML (kecuali mungkin sebagai janji atau jenis yang akan datang) objek), tapi saya tidak bisa memberi Anda saran tentang cara menangani beban Asynchronous di C #.

Perhatikan bahwa dengan pendekatan ini, Anda bisa membuat beberapa implementasi yang dapat mengambil parameter yang memberi tahu mereka di mana / apa yang harus dibaca dan mengembalikan objek XML, dan menukar mereka berdasarkan kebutuhan proyek Anda. Misalnya, Anda mungkin membaca langsung dari database, dari toko lokal, atau, seperti dalam contoh asli Anda, dari URL. Anda tidak dapat melakukannya jika menggunakan metode statis.

Amy Blankenship
sumber
2

Fokus pada perspektif klien.

IReader reader = new XmlReader.readXml(url);  // or injection, or factory or ...
Document document = reader.read();

Metode statis cenderung membatasi evolusi di masa depan, klien kami bekerja dalam hal antarmuka yang disediakan oleh banyak implementasi yang mungkin berbeda.

Masalah utama dengan idiom buka / baca Anda adalah bahwa klien perlu mengetahui urutan metode panggilan, ketika dia hanya ingin menyelesaikan pekerjaan sederhana. Jelas di sini, tetapi di Kelas yang lebih besar jauh dari jelas.

Metode prinsip untuk menguji adalah read (). Metode internal dapat dibuat terlihat untuk menguji program dengan membuatnya tidak publik atau pribadi dan menempatkan tes dalam paket yang sama - tes masih dapat disimpan terpisah dari kode yang dirilis.

djna
sumber
Apakah metode dengan visibilitas default masih terlihat, jika test suite berada di proyek yang berbeda?
siamii
Java tidak tahu tentang proyek. Proyek adalah konstruksi IDE. Compiler dan JVM melihat paket yang diuji dan kelas tester dalam paket yang sama, visibilitas default diizinkan. Di Eclipse saya menggunakan satu proyek, dengan dua direktori sumber yang berbeda. Saya baru saja mencoba dengan dua proyek, itu berhasil.
2

metode statis vs contoh

Dalam praktiknya Anda akan menemukan metode statis umumnya terbatas pada kelas utilitas dan tidak boleh mengacaukan objek domain Anda, manajer, pengontrol atau DAO. Metode statis paling berguna ketika semua referensi yang diperlukan dapat secara wajar diteruskan sebagai parameter dan mengemukakan beberapa fungsi yang dapat digunakan kembali di banyak kelas. Jika Anda menemukan diri Anda menggunakan metode statis sebagai solusi untuk memiliki referensi ke objek contoh, tanyakan pada diri sendiri mengapa Anda tidak hanya memiliki referensi itu saja.

metode tanpa parameter atau nilai balik vs metode dengan parameter dan nilai balik

Jika Anda tidak membutuhkan parameter pada metode ini, jangan menambahkannya. Sama halnya dengan nilai pengembalian. Dengan mengingat hal ini akan menyederhanakan kode Anda dan memastikan Anda tidak melakukan pengkodean untuk banyak skenario yang akhirnya tidak pernah terjadi.

tumpang tindih vs fungsionalitas metode yang berbeda

Merupakan ide bagus untuk mencoba menghindari fungsi yang tumpang tindih. Kadang-kadang bisa sulit, tetapi ketika perubahan logika diperlukan, itu jauh lebih mudah untuk mengubah satu metode yang digunakan kembali, daripada mengubah sejumlah metode dengan fungsi yang sama

metode pribadi vs publik

Biasanya getter, setter dan konstruktor harus publik. Segala hal lain yang ingin Anda coba jaga kerahasiaannya kecuali ada kasus di mana kelas lain perlu menjalankannya. Menjaga metode default ke pribadi akan membantu mempertahankan Enkapsulasi . Sama berlaku untuk bidang, biasakan untuk pribadi sebagai default

Erich
sumber
1

Saya tidak akan menjawab pertanyaan Anda, tetapi saya pikir ada masalah yang diciptakan oleh istilah yang Anda gunakan. Misalnya

XmlReader.read => twice "read"

Saya pikir Anda memerlukan XML jadi saya akan membuat objek XML yang dapat dibuat dari tipe teks (Saya tidak tahu C # ... di Jawa itu disebut String). Misalnya

class XML {
    XML(String text) { [...] }
}

Anda dapat mengujinya dan itu jelas. Kemudian jika Anda memerlukan pabrik, Anda dapat menambahkan metode pabrik (dan itu bisa statis seperti contoh 2 Anda). Misalnya

class XML {
    XML(String text) { [...] }

    static XML fromUrl(url) { [...] }

}
Sixro
sumber
0

Anda dapat mengikuti beberapa aturan sederhana. Ini membantu jika Anda memahami alasan aturan tersebut.

metode statis vs contoh

Untuk metode Anda tidak harus membuat keputusan ini secara sadar. Jika tampaknya metode Anda tidak menggunakan anggota bidang apa pun (penganalisa favorit Anda seharusnya memberi tahu Anda), Anda harus menambahkan kata kunci statis.

metode tanpa parameter atau nilai balik vs metode dengan parameter dan nilai balik

Opsi kedua lebih baik karena ruang lingkup. Anda harus selalu menjaga ruang lingkup Anda ketat. Menjangkau barang yang Anda butuhkan buruk, Anda harus memiliki input, mengerjakannya secara lokal, dan mengembalikan hasilnya. Pilihan pertama Anda buruk karena alasan yang sama variabel global biasanya buruk: mereka bermakna hanya sebagian dari kode Anda tetapi mereka terlihat (dan karenanya berisik) di tempat lain dan dapat dirusak dari mana saja. Ini membuatnya sulit untuk mendapatkan gambaran lengkap tentang logika Anda.

tumpang tindih vs fungsionalitas metode yang berbeda

Saya rasa ini bukan masalah. Metode memanggil metode lain baik-baik saja jika ini memotong tugas menjadi potongan-potongan fungsional yang lebih kecil.

metode pribadi vs publik

Jadikan semuanya pribadi kecuali Anda membutuhkannya untuk umum. Pengguna kelas Anda dapat melakukannya tanpa suara, dia hanya ingin melihat apa yang penting baginya.

Martin Maat
sumber