Katakanlah saya memiliki prosedur yang berfungsi :
void doStuff(initalParams) {
...
}
Sekarang saya menemukan bahwa "melakukan hal-hal" adalah operasi compex. Prosedurnya menjadi besar, saya membaginya menjadi beberapa prosedur yang lebih kecil dan segera saya menyadari bahwa memiliki semacam keadaan akan berguna saat melakukan hal-hal, sehingga saya perlu melewati lebih sedikit parameter antara prosedur kecil. Jadi, saya memfaktorkannya ke dalam kelasnya sendiri:
class StuffDoer {
private someInternalState;
public Start(initalParams) {
...
}
// some private helper procedures here
...
}
Dan saya menyebutnya seperti ini:
new StuffDoer().Start(initialParams);
atau seperti ini:
new StuffDoer(initialParams).Start();
Dan inilah yang terasa salah. Saat menggunakan .NET atau Java API, saya selalu tidak pernah menelepon new SomeApiClass().Start(...);
, yang membuat saya curiga saya salah melakukannya. Tentu, saya dapat menjadikan konstruktor StuffDoer pribadi dan menambahkan metode pembantu statis:
public static DoStuff(initalParams) {
new StuffDoer().Start(initialParams);
}
Tapi kemudian saya memiliki kelas yang antarmuka eksternal hanya terdiri dari satu metode statis, yang juga terasa aneh.
Maka pertanyaan saya: Apakah ada pola yang mapan untuk jenis kelas ini itu
- hanya memiliki satu titik masuk dan
- tidak memiliki status "dikenali secara eksternal", yaitu, keadaan instance hanya diperlukan selama pelaksanaan satu titik masuk itu?
bool arrayContainsSomestring = new List<string>(stringArray).Contains("somestring");
ketika semua yang saya pedulikan adalah bahwa informasi tertentu dan metode ekstensi LINQ tidak tersedia. Berfungsi dengan baik, dan pas di dalamif()
kondisi tanpa harus melompat melalui lingkaran. Tentu saja, Anda ingin bahasa yang dikumpulkan sampah jika Anda menulis kode seperti itu.Jawaban:
Ada pola yang disebut Metode Objek di mana Anda memfaktorkan metode tunggal yang besar dengan banyak variabel / argumen sementara ke dalam kelas yang terpisah. Anda melakukan ini daripada hanya mengekstraksi bagian-bagian metode ke dalam metode terpisah karena mereka memerlukan akses ke keadaan lokal (parameter dan variabel sementara) dan Anda tidak dapat berbagi keadaan lokal menggunakan variabel contoh karena mereka akan bersifat lokal untuk metode itu (dan metode diekstraksi dari itu) hanya dan tidak akan digunakan oleh sisa objek.
Jadi alih-alih, metode menjadi kelasnya sendiri, parameter dan variabel sementara menjadi variabel instan dari kelas baru ini, dan kemudian metode tersebut dipecah menjadi metode yang lebih kecil dari kelas baru. Kelas yang dihasilkan biasanya hanya memiliki metode instance publik tunggal yang melakukan tugas yang dienkapsulasi oleh kelas.
sumber
Saya akan mengatakan bahwa kelas yang dimaksud (menggunakan titik masuk tunggal) dirancang dengan baik. Sangat mudah digunakan dan diperluas. Cukup PADAT.
Hal yang sama untuk
no "externally recognizable" state
. Enkapsulasi yang baik.Saya tidak melihat masalah dengan kode seperti:
sumber
doer
lagi, itu berkurang menjadivar result = new StuffDoer(initialParams).Calculate(extraParams);
.StringComparer
kelas yang Anda gunakannew StringComparer().Compare( "bleh", "blah" )
juga dirancang? Bagaimana dengan menciptakan keadaan ketika sama sekali tidak perlu? Oke perilakunya dienkapsulasi dengan baik (well, setidaknya untuk pengguna kelas, lebih banyak tentang itu dalam jawaban saya ), tapi itu tidak menjadikannya 'desain yang bagus' secara default. Sebagai contoh 'hasil pelaku' Anda. Ini hanya masuk akal jika Anda akan menggunakandoer
terpisah dariresult
.one reason to change
. Perbedaannya mungkin tampak halus. Anda harus mengerti apa artinya itu. Itu tidak akan pernah membuat satu kelas metodeDari sudut pandang prinsip SOLID , jawaban jgauffin masuk akal. Namun, Anda jangan lupa tentang prinsip-prinsip desain umum, misalnya menyembunyikan informasi .
Saya melihat beberapa masalah dengan pendekatan yang diberikan:
Beberapa referensi terkait
Mungkin argumen utama terletak pada sejauh mana merentangkan Prinsip Tanggung Jawab Tunggal . "Jika Anda membawanya ke ekstrem itu dan membangun kelas yang memiliki satu alasan untuk ada, Anda mungkin berakhir dengan hanya satu metode per kelas. Ini akan menyebabkan bentangan besar kelas bahkan untuk proses yang paling sederhana, menyebabkan sistem menjadi sulit dimengerti dan sulit diubah. "
Referensi lain yang relevan terkait dengan topik ini: "Bagi program Anda menjadi metode yang melakukan satu tugas yang dapat diidentifikasi. Simpan semua operasi dalam metode pada tingkat abstraksi yang sama ." - Kent Beck Key di sini adalah "tingkat abstraksi yang sama". Itu tidak berarti "satu hal", seperti yang sering ditafsirkan. Level abstraksi ini sepenuhnya tergantung pada konteksnya yang Anda rancang.
Jadi apa pendekatan yang tepat?
Tanpa mengetahui case konkret Anda, sulit untuk mengatakannya. Ada skenario di mana saya kadang-kadang (tidak sering) menggunakan pendekatan serupa. Ketika saya ingin memproses dataset, tanpa ingin membuat fungsionalitas ini tersedia untuk seluruh ruang kelas. Saya menulis posting blog tentang itu, bagaimana lambdas dapat lebih meningkatkan enkapsulasi . Saya juga memulai pertanyaan tentang topik di sini di Programmer . Berikut ini adalah contoh terbaru di mana saya menggunakan teknik ini.
Karena kode sangat 'lokal' dalam
ForEach
tidak digunakan kembali di tempat lain, saya dapat menyimpannya di lokasi yang tepat di mana ia relevan. Menguraikan kode sedemikian rupa sehingga kode yang bergantung satu sama lain sangat dikelompokkan bersama membuatnya lebih mudah dibaca menurut pendapat saya.Alternatif yang mungkin
sumber
Saya akan mengatakan itu cukup bagus, tetapi API Anda masih harus menjadi metode statis pada kelas statis, karena itulah yang diharapkan pengguna. Fakta bahwa metode statis digunakan
new
untuk membuat objek pembantu Anda dan melakukan pekerjaan adalah detail implementasi yang harus disembunyikan dari siapa pun yang memanggilnya.sumber
Ada masalah dengan pengembang yang tidak mengerti yang dapat menggunakan instance bersama dan menggunakannya
Jadi ini membutuhkan dokumentasi eksplisit bahwa itu bukan thread aman dan jika ringan (membuatnya cepat, tidak mengikat sumber daya eksternal atau banyak memori) bahwa itu OK untuk instantiate dengan cepat.
Menyembunyikannya dalam metode statis membantu dengan ini, karena menggunakan kembali contoh tidak pernah terjadi.
Jika memiliki inisialisasi yang mahal, itu bisa (tidak selalu) bermanfaat untuk mempersiapkan inisialisasi sekali dan menggunakannya beberapa kali dengan membuat kelas lain yang hanya akan berisi status dan membuatnya dapat dikloning. Inisialisasi akan membuat status yang akan disimpan
doer
.start()
akan mengkloningnya dan meneruskannya ke metode internal.Ini juga akan memungkinkan untuk hal-hal lain seperti kondisi eksekusi parsial yang berkelanjutan. (Jika perlu waktu lama dan faktor-faktor eksternal, misalnya kegagalan pasokan listrik, dapat mengganggu pelaksanaan.) Tetapi hal-hal mewah tambahan ini biasanya tidak diperlukan, jadi tidak sepadan.
sumber
Selain jawaban saya yang lebih terperinci dan dipersonalisasi , saya merasa pengamatan berikut ini layak mendapat jawaban terpisah.
Ada indikasi bahwa Anda mungkin mengikuti pola anti-Poltergeists .
sumber
Ada beberapa pola yang dapat ditemukan pada katalog P of EAA Martin Fowler ;
sumber