Sebagai contoh, suatu kelas biasanya memiliki anggota dan metode kelas, misalnya:
public class Cat{
private String name;
private int weight;
private Image image;
public void printInfo(){
System.out.println("Name:"+this.name+",weight:"+this.weight);
}
public void draw(){
//some draw code which uses this.image
}
}
Tetapi setelah membaca tentang prinsip tanggung jawab tunggal dan prinsip tertutup terbuka, saya lebih suka memisahkan kelas menjadi kelas DTO dan pembantu dengan metode statis saja, misalnya:
public class CatData{
public String name;
public int weight;
public Image image;
}
public class CatMethods{
public static void printInfo(Cat cat){
System.out.println("Name:"+cat.name+",weight:"+cat.weight);
}
public static void draw(Cat cat){
//some draw code which uses cat.image
}
}
Saya pikir ini sesuai dengan prinsip tanggung jawab tunggal karena sekarang tanggung jawab CatData adalah hanya menyimpan data, tidak peduli dengan metode (juga untuk CatMethods). Dan itu juga sesuai dengan prinsip tertutup terbuka karena menambahkan metode baru tidak perlu mengubah kelas CatData.
Pertanyaan saya adalah, apakah ini baik atau anti-pola?
object-oriented
static-methods
dto
ocomfd
sumber
sumber
Jawaban:
Anda telah menunjukkan dua ekstrem ("semuanya pribadi dan semua (mungkin tidak terkait) metode dalam satu objek" vs "semuanya publik dan tidak ada metode di dalam objek"). Pemodelan OO yang baik IMHO bukan dari mereka , sweet spot adalah suatu tempat di tengah.
Satu uji lakmus dari metode atau logika apa yang termasuk dalam kelas, dan apa yang termasuk di luar, adalah untuk melihat dependensi yang akan diperkenalkan oleh metode tersebut. Metode yang tidak memperkenalkan dependensi tambahan baik-baik saja, asalkan cocok dengan abstraksi objek yang diberikan . Metode yang memang memerlukan dependensi eksternal tambahan (seperti perpustakaan gambar atau perpustakaan I / O) jarang cocok. Bahkan jika Anda akan membuat dependensi menghilang dengan menggunakan injeksi dependensi, saya masih akan berpikir dua kali jika menempatkan metode seperti itu di dalam kelas domain benar-benar diperlukan.
Jadi Anda tidak boleh membuat setiap anggota menjadi publik, Anda juga tidak perlu menerapkan metode untuk setiap operasi pada objek di dalam kelas. Berikut ini adalah saran alternatif:
Sekarang
Cat
objek menyediakan cukup logika untuk membiarkan kode di sekitarnya dengan mudah diimplementasikanprintInfo
dandraw
, tanpa memaparkan semua atribut di depan umum. Tempat yang tepat untuk kedua metode ini kemungkinan besar bukan kelas dewaCatMethods
(karenaprintInfo
dandraw
kemungkinan besar adalah keprihatinan yang berbeda, jadi saya pikir sangat tidak mungkin mereka termasuk dalam kelas yang sama).Saya bisa membayangkan
CatDrawingController
yang mengimplementasikandraw
(dan mungkin menggunakan injeksi ketergantungan untuk mendapatkan objek Kanvas). Saya juga bisa membayangkan kelas lain yang mengimplementasikan beberapa keluaran dan penggunaan konsolgetInfo
(jadiprintInfo
mungkin menjadi usang dalam konteks ini). Tetapi untuk membuat keputusan yang masuk akal mengenai hal ini, kita perlu mengetahui konteks dan bagaimanaCat
kelas akan benar-benar digunakan.Itulah sebenarnya cara saya menafsirkan kritik Model Anemic Domain Fowler - untuk logika yang umumnya dapat digunakan kembali (tanpa ketergantungan eksternal) kelas domain itu sendiri adalah tempat yang baik, sehingga mereka harus digunakan untuk itu. Tetapi itu tidak berarti menerapkan logika apa pun di sana, justru sebaliknya.
Perhatikan juga contoh di atas masih menyisakan ruang untuk membuat keputusan tentang (im) mutabilitas. Jika
Cat
kelas tidak akan mengekspos setter apa pun, danImage
tidak dapat diubah sendiri, desain ini akan memungkinkan untuk membuatCat
tidak berubah (yang tidak akan dilakukan oleh pendekatan DTO). Tetapi jika Anda berpikir ketidakberdayaan tidak diperlukan atau tidak membantu untuk kasus Anda, Anda juga bisa menuju ke sana.sumber
getInfo()
mungkin menyesatkan seseorang yang mengajukan pertanyaan ini. Ini secara halus mencampur tanggung jawab presentasi sepertiprintInfo()
halnya, mengalahkan tujuanDrawingController
kelasDrawingController
. Saya setuju itugetInfo()
danprintInfo()
nama-nama yang mengerikan. PertimbangkantoString()
danprintTo(Stream stream)
Jawaban terlambat tetapi saya tidak bisa menolak.
Dalam kebanyakan kasus, sebagian besar aturan, diterapkan tanpa berpikir, sebagian besar akan salah besar (termasuk yang ini).
Izinkan saya menceritakan sebuah kisah tentang kelahiran suatu benda di tengah kekacauan beberapa kode prosedur yang benar, cepat dan kotor, yang terjadi, bukan karena desain, tetapi karena putus asa.
Magang saya dan saya adalah pasangan pemrograman untuk dengan cepat membuat beberapa kode membuang untuk mengikis halaman web. Kami sama sekali tidak punya alasan untuk mengharapkan kode ini akan berumur panjang, jadi kami hanya mengeluarkan sesuatu yang berhasil. Kami mengambil seluruh halaman sebagai string dan memotong barang-barang yang kami butuhkan dengan cara yang paling rapuh yang bisa Anda bayangkan. Jangan menilai. Berhasil.
Sekarang sambil melakukan ini saya membuat beberapa metode statis untuk melakukan pemotongan. Magang saya menciptakan kelas DTO yang sangat mirip dengan Anda
CatData
.Ketika saya pertama kali melihat DTO itu menyadap saya. Tahun-tahun kerusakan yang telah dilakukan Jawa pada otak saya membuat saya mundur di bidang-bidang publik. Tapi kami bekerja di C #. C # tidak perlu pengambil dan setter prematur untuk mempertahankan hak Anda untuk membuat data tidak berubah, atau dienkapsulasi nanti. Tanpa mengubah antarmuka, Anda dapat menambahkannya kapan saja. Mungkin agar Anda dapat mengatur breakpoint. Semua tanpa memberi tahu klien Anda tentang hal itu. Ya C #. Boo Java.
Jadi saya memegang lidah saya. Saya menyaksikan ketika dia menggunakan metode statis saya untuk menginisialisasi hal ini sebelum menggunakannya. Kami memiliki sekitar 14 di antaranya. Itu jelek, tapi kami tidak punya alasan untuk peduli.
Kemudian kami membutuhkannya di tempat lain. Kami mendapati diri kami ingin menyalin dan menempelkan kode. 14 baris inisialisasi dilemparkan. Itu mulai terasa menyakitkan. Dia ragu-ragu dan meminta saya untuk ide.
Dengan enggan saya bertanya, "apakah Anda akan mempertimbangkan objek?"
Dia melihat kembali DTO-nya dan mengacaukan wajahnya. "Ini adalah objek".
"Maksudku, benda yang nyata"
"Hah?"
"Biarkan aku menunjukkan sesuatu padamu. Kamu memutuskan apakah itu berguna"
Saya memilih nama baru dan cepat-cepat mengambil sesuatu yang tampak seperti ini:
Ini tidak melakukan apa pun yang belum dilakukan metode statis. Tapi sekarang saya mengisap 14 metode statis ke dalam kelas di mana mereka bisa sendirian dengan data yang mereka kerjakan.
Saya tidak memaksa magang saya untuk menggunakannya. Saya hanya menawarkannya dan membiarkannya memutuskan apakah dia ingin tetap menggunakan metode statis. Saya pulang ke rumah berpikir dia mungkin akan tetap pada apa yang sudah dia kerjakan. Hari berikutnya saya menemukan dia menggunakannya di banyak tempat. Itu mendeklarasikan sisa kode yang masih jelek dan prosedural tetapi kompleksitas ini sekarang tersembunyi dari kita di belakang suatu objek. Itu sedikit lebih baik.
Sekarang yakin setiap kali Anda mengaksesnya ini melakukan sedikit pekerjaan yang adil. DTO adalah nilai cache cepat yang bagus. Saya khawatir tentang itu tetapi menyadari bahwa saya bisa menambahkan caching jika kita perlu tanpa menyentuh salah satu kode yang digunakan. Jadi saya tidak akan repot sampai kita peduli.
Apakah saya mengatakan Anda harus selalu menempel pada objek OO di atas DTO? Tidak. DTO bersinar ketika Anda harus melewati batas yang membuat Anda tidak bisa bergerak. DTO punya tempat mereka.
Tapi begitu juga objek OO. Pelajari cara menggunakan kedua alat. Pelajari berapa biaya masing-masing. Belajarlah untuk membiarkan masalah, situasi, dan magang memutuskan. Dogma bukan temanmu di sini.
Karena jawaban saya sudah lama sekali, izinkan saya membebaskan Anda dari beberapa kesalahpahaman dengan meninjau kode Anda.
Di mana konstruktor Anda? Ini tidak cukup menunjukkan kepada saya untuk mengetahui apakah itu berguna.
Anda dapat melakukan banyak hal konyol atas nama Prinsip Tanggung Jawab Tunggal. Saya bisa berpendapat bahwa Cat Strings dan Cat int harus dipisahkan. Metode menggambar dan Gambar itu semua harus memiliki kelas sendiri. Bahwa program Anda yang sedang berjalan adalah tanggung jawab tunggal sehingga Anda hanya boleh memiliki satu kelas. : P
Bagi saya, cara terbaik untuk mengikuti Prinsip Tanggung Jawab Tunggal adalah dengan menemukan abstraksi yang baik yang memungkinkan Anda memasukkan kerumitan dalam sebuah kotak sehingga Anda dapat menyembunyikannya. Jika Anda bisa memberikannya nama baik yang membuat orang tidak terkejut dengan apa yang mereka temukan ketika mereka melihat ke dalam, Anda telah mengikutinya dengan cukup baik. Mengharapkannya untuk mendikte lebih banyak keputusan maka itu meminta masalah. Jujur, kedua daftar kode Anda melakukan itu jadi saya tidak melihat mengapa SRP penting di sini.
Baik tidak Prinsip buka tutup bukan tentang menambahkan metode baru. Ini tentang bisa mengubah implementasi metode lama dan tidak perlu mengedit apa pun. Tidak ada yang menggunakan Anda dan bukan metode lama Anda. Sebaliknya Anda menulis beberapa kode baru di tempat lain. Suatu bentuk polimorfisme akan melakukannya dengan baik. Jangan lihat itu di sini.
Nah, bagaimana saya bisa tahu? Lihat, melakukannya bagaimanapun memiliki manfaat dan biaya. Ketika Anda memisahkan kode dari data, Anda dapat mengubah salah satu tanpa harus mengkompilasi ulang yang lain. Mungkin itu sangat penting bagi Anda. Mungkin itu hanya membuat kode Anda rumit tanpa tujuan.
Jika itu membuat Anda merasa lebih baik, Anda tidak jauh dari sesuatu yang Martin Fowler sebut sebagai objek parameter . Anda tidak harus hanya mengambil primitif ke objek Anda.
Apa yang saya ingin Anda lakukan adalah mengembangkan rasa untuk melakukan pemisahan Anda, atau tidak, dalam gaya pengkodean. Karena percaya atau tidak Anda tidak dipaksa untuk memilih gaya. Anda hanya harus hidup dengan pilihan Anda.
sumber
Anda telah menemukan topik perdebatan yang telah menciptakan argumen di antara pengembang selama lebih dari satu dekade. Pada tahun 2003, Martin Fowler menciptakan ungkapan "Anemic Domain Model" (ADM) untuk menggambarkan pemisahan data dan fungsionalitas ini. Dia - dan orang lain yang setuju dengannya - berpendapat bahwa "Rich Domain Models" (pencampuran data dan fungsionalitas) adalah "OO yang tepat", dan bahwa pendekatan ADM adalah anti-pola non-OO.
Selalu ada orang-orang yang menolak argumen ini dan sisi argumen tersebut telah tumbuh lebih keras dan lebih berani dalam beberapa tahun terakhir dengan adopsi oleh lebih banyak pengembang teknik pengembangan fungsional. Pendekatan itu secara aktif mendorong pemisahan data dan masalah fungsi. Data harus berubah sebanyak mungkin, sehingga enkapsulasi dari keadaan yang bisa berubah menjadi tidak menjadi perhatian. Tidak ada manfaatnya melampirkan fungsi secara langsung ke data tersebut dalam situasi seperti itu. Maka apakah "bukan OO" atau tidak sama sekali tidak menarik bagi orang-orang seperti itu.
Terlepas dari sisi mana Anda duduk di pagar (saya duduk sangat kuat di sisi "Martin Fowler berbicara banyak tosh" BTW), Anda menggunakan metode statis untuk
printInfo
dandraw
hampir universal disukai. Metode statis sulit untuk mengejek ketika menulis unit test. Jadi jika mereka memiliki efek samping (seperti mencetak atau menggambar ke beberapa layar atau perangkat lain), mereka tidak boleh statis, atau harus memiliki lokasi keluaran yang dilewatkan sebagai parameter.Jadi Anda bisa memiliki antarmuka:
Dan implementasi yang disuntikkan ke seluruh sistem Anda saat runtime (dengan implementasi lain yang digunakan untuk pengujian):
Atau tambahkan parameter tambahan untuk menghilangkan efek samping dari metode tersebut:
sumber
DTO - Objek Transport Data
berguna untuk hal itu. Jika Anda akan men-shunt data antar program atau sistem dari yang diinginkan DTO karena mereka memberikan sub-set yang dikelola dari objek yang hanya berkaitan dengan struktur data dan format. Keuntungannya adalah Anda tidak harus menyinkronkan pembaruan ke metode kompleks pada beberapa sistem (selama data yang mendasarinya tidak berubah).
Inti dari OO adalah untuk mengikat data dan kode yang bekerja pada data tersebut secara berdekatan. Memisahkan Obyek yang logis ke dalam kelas yang terpisah biasanya merupakan ide yang buruk.
sumber
Banyak pola desain dan prinsip yang bertentangan, dan pada akhirnya hanya penilaian Anda sebagai programmer yang baik yang akan membantu Anda memutuskan pola mana yang harus diterapkan untuk masalah tertentu.
Menurut pendapat saya, prinsip Do The Simplest Thing That Could Possiblely seharusnya menang secara default kecuali Anda memiliki alasan kuat untuk membuat desain lebih rumit. Jadi, dalam contoh spesifik Anda, saya akan memilih desain pertama, yang merupakan pendekatan berorientasi objek yang lebih tradisional dengan data dan fungsi bersama-sama di kelas yang sama.
Berikut adalah beberapa pertanyaan yang dapat Anda tanyakan pada diri sendiri tentang aplikasi:
Menjawab ya untuk semua ini dapat mengarahkan Anda ke desain yang lebih kompleks dengan DTO dan kelas fungsional yang terpisah.
sumber
Apakah ini pola yang baik?
Anda mungkin akan mendapatkan jawaban berbeda di sini karena orang mengikuti aliran pemikiran yang berbeda. Jadi bahkan sebelum saya mulai: Jawaban ini sesuai dengan pemrograman berorientasi objek seperti yang dijelaskan oleh Robert C. Martin dalam buku "Clean Code".
"Benar" OOP
Sejauh yang saya ketahui, ada 2 interpretasi OOP. Bagi kebanyakan orang, itu bermuara pada "objek adalah kelas".
Namun, saya menemukan aliran pemikiran lain yang lebih bermanfaat: "Objek adalah sesuatu yang melakukan sesuatu". Jika Anda bekerja dengan kelas, setiap kelas akan menjadi salah satu dari dua hal: " pemegang data " atau objek . Pemegang data menyimpan data (duh), benda melakukan hal-hal. Itu tidak berarti bahwa pemegang data tidak dapat memiliki metode! Pikirkan
list
: Alist
tidak benar - benar melakukan apa - apa, tetapi ia memiliki metode, untuk menegakkan cara ia menyimpan data .Pemegang data
Anda
Cat
adalah contoh utama dari pemegang data. Tentu, di luar program Anda, kucing adalah sesuatu yang melakukan sesuatu , tetapi di dalam program Anda, aCat
adalah Stringname
, int,weight
dan Imageimage
.Bahwa pemegang data tidak melakukan apa - apa juga tidak berarti bahwa mereka tidak mengandung logika bisnis! Jika
Cat
kelas Anda membutuhkan konstruktorname
,weight
danimage
, Anda telah berhasil merangkum aturan bisnis yang dimiliki setiap kucing. Jika Anda bisa mengubahweight
setelahCat
kelas dibuat, itu aturan bisnis lain yang dienkapsulasi. Ini adalah hal-hal yang sangat mendasar, tetapi itu artinya sangat penting untuk tidak membuat kesalahan - dan dengan cara ini, Anda memastikan bahwa tidak mungkin melakukan kesalahan itu.Benda
Objek melakukan sesuatu. Jika Anda ingin objek Clean *, kita dapat mengubahnya menjadi "Objects do one thing".
Mencetak info kucing adalah pekerjaan suatu objek. Misalnya, Anda dapat memanggil objek ini "
CatInfoLogger
", dengan metode publikLog(Cat)
. Hanya itu yang perlu kita ketahui dari luar: Objek ini mencatat info kucing dan untuk melakukannya, diperlukan aCat
.Di bagian dalam, objek ini akan memiliki referensi ke apa pun yang dibutuhkan untuk memenuhi tanggung jawab tunggal dari penebangan kucing. Dalam hal ini, itu hanya referensi
System
untukSystem.out.println
, tetapi dalam kebanyakan kasus, itu akan menjadi referensi pribadi ke objek lain. Jika logika untuk memformat output sebelum mencetaknya menjadi terlalu kompleks,CatInfoLogger
bisa saja mendapatkan referensi ke objek baruCatInfoFormatter
. Jika nanti, setiap log juga harus ditulis ke file, Anda dapat menambahkanCatToFileLogger
objek yang melakukan itu.Pemegang data vs objek - ringkasan
Sekarang, mengapa Anda melakukan semua itu? Karena sekarang mengubah kelas hanya mengubah satu hal ( cara kucing masuk dalam contoh). Jika Anda mengubah suatu objek, Anda hanya mengubah cara tindakan tertentu dilakukan; jika Anda mengubah pemegang data, Anda hanya mengubah data apa yang disimpan dan / atau dengan cara apa data tersebut disimpan.
Mengubah data mungkin berarti cara sesuatu dilakukan harus berubah juga, tetapi perubahan itu tidak akan terjadi sampai Anda membuatnya sendiri dengan mengubah objek yang bertanggung jawab.
Berlebihan?
Solusi ini mungkin tampak sedikit berlebihan. Biarkan aku jujur: Ini adalah . Jika keseluruhan program Anda hanya sebesar contoh, jangan lakukan salah satu di atas - cukup pilih salah satu cara yang Anda usulkan dengan lemparan koin. Tidak ada bahaya membingungkan kode lain jika ada yang tidak ada kode lain.
Namun, perangkat lunak "serius" (= lebih dari satu skrip atau 2) mungkin cukup besar sehingga akan mendapat untung dari struktur seperti ini.
Menjawab
Mengubah "sebagian besar kelas" (tanpa kualifikasi lebih lanjut) menjadi DTO / pemegang data, bukan merupakan pola anti, karena sebenarnya bukan pola apa pun - lebih atau kurang acak.
Namun, memisahkan data dan objek dianggap sebagai cara yang baik untuk menjaga kode Anda Bersih *. Kadang-kadang Anda hanya perlu DTO datar, "anemia" dan hanya itu. Di lain waktu, Anda memerlukan metode untuk menegakkan cara data disimpan. Hanya saja, jangan menaruh fungsionalitas program Anda dengan data.
* Itu "Bersih" dengan huruf C seperti judul buku, karena - ingat paragraf pertama - ini adalah tentang satu aliran pemikiran, sementara ada juga yang lain.
sumber