Saya telah bekerja pada refactoring beberapa kode, dan saya pikir saya mungkin telah mengambil langkah pertama ke lubang kelinci. Saya menulis contoh di Jawa, tapi saya kira itu bisa menjadi agnostik.
Saya memiliki antarmuka yang Foo
didefinisikan sebagai
public interface Foo {
int getX();
int getY();
int getZ();
}
Dan implementasi sebagai
public final class DefaultFoo implements Foo {
public DefaultFoo(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
private final int x;
private final int y;
private final int z;
}
Saya juga memiliki antarmuka MutableFoo
yang menyediakan mutator yang cocok
/**
* This class extends Foo, because a 'write-only' instance should not
* be possible and a bit counter-intuitive.
*/
public interface MutableFoo extends Foo {
void setX(int newX);
void setY(int newY);
void setZ(int newZ);
}
Ada beberapa implementasi MutableFoo
yang bisa ada (saya belum mengimplementasikannya). Salah satunya adalah
public final class DefaultMutableFoo implements MutableFoo {
/**
* A DefaultMutableFoo is not conceptually constructed
* without all values being set.
*/
public DefaultMutableFoo(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
public int getX() {
return x;
}
public void setX(int newX) {
this.x = newX;
}
public int getY() {
return y;
}
public void setY(int newY) {
this.y = newY;
}
public int getZ() {
return z;
}
public void setZ(int newZ) {
this.z = newZ;
}
private int x;
private int y;
private int z;
}
Alasan saya membaginya adalah karena keduanya sama-sama memungkinkan untuk digunakan. Artinya, kemungkinan besar seseorang yang menggunakan kelas-kelas ini akan menginginkan instance yang tidak dapat diubah, karena mereka akan menginginkan yang bisa berubah.
Kasus penggunaan utama yang saya miliki adalah sebuah antarmuka yang disebut StatSet
yang mewakili detail pertempuran tertentu untuk sebuah game (hitpoint, serangan, pertahanan). Namun, statistik "efektif", atau statistik aktual, adalah hasil dari statistik dasar, yang tidak pernah dapat diubah, dan statistik terlatih, yang dapat ditingkatkan. Keduanya terkait dengan
/**
* The EffectiveStats can never be modified independently of either the baseStats
* or trained stats. As such, this StatSet must never provide mutators.
*/
public StatSet calculateEffectiveStats() {
int effectiveHitpoints =
baseStats.getHitpoints() + (trainedStats.getHitpoints() / 4);
int effectiveAttack =
baseStats.getAttack() + (trainedStats.getAttack() / 4);
int effectiveDefense =
baseStats.getDefense() + (trainedStats.getDefense() / 4);
return StatSetFactory.createImmutableStatSet(effectiveHitpoints, effectiveAttack, effectiveDefense);
}
Statistik terlatih meningkat setelah setiap pertempuran seperti
public void grantExperience() {
int hitpointsReward = 0;
int attackReward = 0;
int defenseReward = 0;
final StatSet enemyStats = enemy.getEffectiveStats();
final StatSet currentStats = player.getEffectiveStats();
if (enemyStats.getHitpoints() >= currentStats.getHitpoints()) {
hitpointsReward++;
}
if (enemyStats.getAttack() >= currentStats.getAttack()) {
attackReward++;
}
if (enemyStats.getDefense() >= currentStats.getDefense()) {
defenseReward++;
}
final MutableStatSet trainedStats = player.getTrainedStats();
trainedStats.increaseHitpoints(hitpointsReward);
trainedStats.increaseAttack(attackReward);
trainedStats.increaseDefense(defenseReward);
}
tetapi mereka tidak bertambah setelah pertempuran. Menggunakan barang-barang tertentu, menggunakan taktik tertentu, penggunaan medan perang yang cerdas semua bisa memberikan pengalaman yang berbeda.
Sekarang untuk pertanyaan saya:
- Apakah ada nama untuk memisahkan antarmuka oleh pengakses dan mutator menjadi antarmuka terpisah?
- Apakah membelah mereka dengan cara ini sebagai pendekatan yang 'benar' jika mereka sama-sama cenderung digunakan, atau apakah ada pola yang berbeda, lebih diterima yang harus saya gunakan sebagai gantinya (mis.
Foo foo = FooFactory.createImmutableFoo();
Yang bisa kembaliDefaultFoo
atauDefaultMutableFoo
tetapi disembunyikan karenacreateImmutableFoo
kembaliFoo
)? - Apakah ada kelemahan langsung untuk menggunakan pola ini, kecuali untuk memperumit hierarki antarmuka?
Alasan saya mulai mendesain dengan cara ini adalah karena saya memiliki pola pikir bahwa semua pelaksana antarmuka harus mematuhi antarmuka yang paling sederhana yang mungkin, dan tidak memberikan apa-apa lagi. Dengan menambahkan setter ke antarmuka, sekarang statistik efektif dapat dimodifikasi secara terpisah dari bagian-bagiannya.
Membuat kelas baru untuk EffectiveStatSet
itu tidak masuk akal karena kami tidak memperluas fungsionalitas dengan cara apa pun. Kita dapat mengubah implementasinya, dan membuat EffectiveStatSet
gabungan dua yang berbeda StatSets
, tetapi saya merasa itu bukan solusi yang tepat;
public class EffectiveStatSet implements StatSet {
public EffectiveStatSet(StatSet baseStats, StatSet trainedStats) {
// ...
}
public int getHitpoints() {
return baseStats.getHitpoints() + (trainedStats.getHitpoints() / 4);
}
}
Jawaban:
Bagi saya sepertinya Anda punya solusi mencari masalah.
Ini mungkin sedikit memprovokasi, tetapi sebenarnya saya akan menyebutnya "overdesigning things" atau "overcomplicating things". Dengan menawarkan varian yang dapat berubah dan tidak berubah dari kelas yang sama, Anda menawarkan dua solusi yang setara secara fungsional untuk masalah yang sama, yang hanya berbeda dalam aspek non-fungsional seperti perilaku kinerja, API, dan keamanan terhadap efek samping. Saya kira itu karena Anda takut membuat keputusan mana yang lebih disukai, atau karena Anda mencoba menerapkan fitur "const" dari C ++ di C #. Saya kira dalam 99% dari semua kasus itu tidak akan membuat perbedaan besar jika pengguna memilih varian yang dapat berubah atau tidak berubah, ia dapat menyelesaikan masalahnya dengan yang lain atau yang lain. Dengan demikian "kemungkinan kelas untuk digunakan"
Pengecualiannya adalah ketika Anda mendesain bahasa pemrograman baru atau kerangka kerja multi guna yang akan digunakan oleh sekitar sepuluh ribu programmer atau lebih. Maka itu memang dapat skala yang lebih baik ketika Anda menawarkan varian tipe data tujuan umum yang tidak berubah dan bisa berubah. Tapi ini adalah situasi di mana Anda akan memiliki ribuan skenario penggunaan yang berbeda - yang mungkin bukan masalah yang Anda hadapi, saya kira?
"Pola yang lebih diterima" disebut KISS - jadikan itu sederhana dan bodoh. Buat keputusan untuk atau tidak berubah-ubah untuk kelas / antarmuka tertentu di perpustakaan Anda. Misalnya, jika "StatSet" Anda memiliki selusin atribut atau lebih, dan sebagian besar diubah secara individual, saya lebih suka varian yang bisa berubah dan tidak mengubah statistik dasar yang tidak boleh dimodifikasi. Untuk sesuatu seperti
Foo
kelas dengan atribut X, Y, Z (vektor tiga dimensi), saya mungkin lebih suka varian yang tidak berubah.Desain yang terlalu rumit membuat perangkat lunak lebih sulit untuk diuji, lebih sulit untuk dipertahankan, lebih sulit untuk berkembang.
sumber
Mungkin ada nama untuk ini jika pemisahan ini bermanfaat dan memberikan manfaat yang tidak saya lihat . Jika pemisahan tidak memberikan manfaat, dua pertanyaan lainnya tidak masuk akal.
Bisakah Anda memberi tahu kami kasus penggunaan bisnis di mana dua Antarmuka terpisah memberi kami manfaat atau apakah pertanyaan ini masalah akademis (YAGNI)?
Saya bisa memikirkan berbelanja dengan kereta yang bisa berubah-ubah (Anda bisa memasukkan lebih banyak artikel) yang dapat menjadi pesanan di mana artikel tidak dapat diubah lagi oleh pelanggan. Status pesanan masih bisa berubah.
Implementasi yang saya lihat tidak memisahkan antarmuka
Versi ReadOnly menggunakan "WritableInterface" dan melemparkan pengecualian jika metode penulisan uesed
sumber
Pemisahan antarmuka koleksi yang dapat diubah dan antarmuka koleksi hanya-baca-kontrak adalah contoh prinsip pemisahan antarmuka. Saya tidak berpikir ada nama khusus yang diberikan untuk setiap penerapan prinsip.
Perhatikan beberapa kata di sini: "read-only-contract" dan "collection".
Kontrak hanya-baca berarti bahwa kelas
A
memberi kelasB
akses hanya-baca, tetapi tidak menyiratkan bahwa objek koleksi yang mendasarinya sebenarnya tidak dapat diubah. Abadi berarti bahwa itu tidak akan pernah berubah di masa depan, tidak peduli oleh agen apa pun. Kontrak hanya baca hanya mengatakan bahwa penerima tidak diperbolehkan mengubahnya; orang lain (khususnya, kelasA
) diizinkan untuk mengubahnya.Untuk membuat suatu objek tidak berubah, ia harus benar-benar tidak dapat diubah - ia harus menolak upaya untuk mengubah datanya terlepas dari agen yang memintanya.
Pola ini kemungkinan besar diamati pada objek yang mewakili kumpulan data - daftar, urutan, file (aliran), dll.
Kata "immutable" menjadi iseng, tetapi konsepnya bukan hal baru. Dan ada banyak cara menggunakan kekekalan, serta banyak cara untuk mencapai desain yang lebih baik menggunakan sesuatu yang lain (yaitu para pesaingnya).
Inilah pendekatan saya (tidak didasarkan pada keabadian).
hitpoints
,attack
,defense
.A
perlu meneruskan DTO ke kelasB
, ia membuat salinannya dan meneruskan salinannya. Jadi,B
dapat menggunakan DTO namun suka (menulis untuk itu), tanpa mempengaruhi DTO yangA
berpegang pada.grantExperience
fungsi yang akan dipecah menjadi dua:calculateNewStats
increaseStats
calculateNewStats
akan mengambil input dari dua DTO, satu mewakili statistik pemain dan satu lagi mewakili statistik musuh, dan melakukan perhitungan.hitpoints
,attack
,defense
) toko jumlah yang akan bertambah karena kemampuannya itu.increaseStats
adalah metode pada pemain (bukan pada DTO) yang mengambil "jumlah untuk meningkatkan" DTO, dan menerapkan kenaikan pada DTO yang dimiliki oleh pemain dan mewakili DTO yang dapat dilatih pemain.Dalam kasus
calculateNewStats
ditemukan tidak bergantung pada informasi pemain atau musuh lain (di luar nilai-nilai dalam DTO dua input), metode ini dapat berpotensi berada di mana saja dalam proyek.Jika
calculateNewStats
ditemukan memiliki ketergantungan penuh pada pemain dan objek musuh (yaitu, pemain masa depan dan objek musuh mungkin memiliki properti baru, dancalculateNewStats
harus diperbarui untuk mengkonsumsi sebanyak mungkin properti baru mereka), makacalculateNewStats
harus menerima kedua objek, bukan hanya DTO. Namun, hasil perhitungannya akan tetap menjadi DTO kenaikan, atau objek transfer data sederhana apa pun yang membawa informasi yang digunakan untuk melakukan peningkatan / peningkatan.sumber
Ada masalah besar di sini: hanya karena struktur data tidak berubah, tidak berarti kita tidak perlu versi modifikasi itu . The nyata versi berubah dari struktur data tidak menyediakan
setX
,setY
, dansetZ
metode - mereka hanya mengembalikan baru struktur bukan memodifikasi objek yang memanggil mereka pada.Jadi, bagaimana Anda memberikan satu bagian dari sistem Anda kemampuan untuk mengubahnya sambil membatasi bagian lain? Dengan objek yang bisa berubah yang berisi referensi ke instance dari struktur yang tidak dapat diubah. Pada dasarnya, alih-alih kelas pemain Anda dapat mengubah objek statistiknya sembari memberikan setiap kelas lainnya pandangan tentang statistiknya, statistiknya tidak dapat diubah dan pemain dapat berubah. Dari pada:
Anda akan memiliki:
Lihat perbedaannya? Objek stats benar-benar tidak berubah, tetapi statistik pemain saat ini adalah referensi yang bisa berubah ke objek yang tidak bisa diubah. Tidak perlu membuat dua antarmuka sama sekali - cukup buat struktur data benar-benar tidak dapat diubah dan kelola referensi yang dapat berubah ke tempat Anda menggunakannya untuk menyimpan status.
Ini berarti objek lain di sistem Anda tidak dapat bergantung pada referensi ke objek statistik - objek statistik yang mereka miliki tidak akan menjadi objek statistik yang sama dengan yang dimiliki pemain setelah pemain memperbarui statistik mereka.
Ini lebih masuk akal, karena secara konseptual bukan statistik yang berubah, ini statistik pemain saat ini . Bukan objek statistik. The referensi untuk statistik objek objek pemain memiliki. Jadi bagian lain dari sistem Anda tergantung pada referensi yang semuanya harus secara eksplisit merujuk
player.currentStats()
atau semacam itu alih-alih mendapatkan benda statistik yang mendasari pemain, menyimpannya di suatu tempat, dan bergantung padanya memperbarui melalui mutasi.sumber
Wow ... ini benar-benar membawaku kembali. Saya mencoba ide yang sama ini beberapa kali. Saya terlalu keras kepala untuk menyerah karena saya pikir akan ada sesuatu yang baik untuk dipelajari. Saya telah melihat orang lain mencoba ini dalam kode juga. Sebagian besar implementasi yang saya lihat disebut antarmuka Read-Only seperti milik Anda
FooViewer
atauReadOnlyFoo
dan antarmuka Write-OnlyFooEditor
atauWriteableFoo
atau di Jawa saya pikir sayaFooMutator
pernah melihatnya . Saya tidak yakin ada kosa kata resmi atau bahkan umum untuk melakukan hal-hal seperti ini.Ini tidak pernah melakukan sesuatu yang berguna bagi saya di tempat saya mencobanya. Saya akan menghindarinya sama sekali. Saya akan melakukan apa yang orang lain sarankan dan mengambil langkah mundur dan mempertimbangkan apakah Anda benar-benar membutuhkan gagasan ini dalam ide Anda yang lebih besar. Saya tidak yakin ada cara yang tepat untuk melakukan ini karena saya tidak pernah benar-benar menyimpan kode yang saya buat saat mencoba ini. Setiap kali saya mundur setelah banyak upaya dan hal-hal yang disederhanakan. Dan maksud saya adalah sesuatu yang sejalan dengan apa yang orang lain katakan tentang YAGNI dan KISS dan DRY.
Satu kemungkinan downside: pengulangan. Anda harus membuat antarmuka ini untuk banyak kelas yang berpotensi dan bahkan hanya untuk satu kelas Anda harus memberi nama setiap metode dan menggambarkan setiap tanda tangan setidaknya dua kali. Dari semua hal yang membakar saya dalam pengkodean, harus mengubah banyak file dengan cara ini membuat saya dalam kesulitan terbesar. Saya akhirnya lupa membuat perubahan di satu tempat atau di tempat lain. Setelah Anda memiliki kelas yang disebut Foo dan antarmuka yang disebut FooViewer dan FooEditor, jika Anda memutuskan akan lebih baik jika Anda menyebutnya Bar alih-alih Anda harus refactor-> ganti nama tiga kali kecuali Anda memiliki IDE yang benar-benar pintar. Saya bahkan menemukan ini menjadi kasus ketika saya memiliki sejumlah besar kode yang berasal dari generator kode.
Secara pribadi, saya tidak suka membuat antarmuka untuk hal-hal yang hanya memiliki satu implementasi juga. Itu berarti ketika saya melakukan perjalanan dalam kode saya tidak bisa hanya melompat ke satu - satunya implementasi secara langsung, saya harus melompat ke antarmuka dan kemudian ke implementasi antarmuka atau setidaknya tekan beberapa tombol tambahan untuk sampai ke sana. Semua itu bertambah.
Dan kemudian ada komplikasi yang Anda sebutkan. Saya ragu saya akan pernah menggunakan cara ini dengan kode saya lagi. Bahkan untuk tempat ini paling sesuai dengan keseluruhan rencana saya untuk kode (ORM yang saya buat) saya menarik ini dan menggantinya dengan sesuatu yang lebih mudah untuk dikodekan.
Saya benar-benar akan lebih memikirkan komposisi yang Anda sebutkan. Saya ingin tahu mengapa Anda merasa itu adalah ide yang buruk. Saya akan mengharapkan untuk
EffectiveStats
membuat dan berubahStats
dan sesuatu sepertiStatModifiers
yang selanjutnya akan menyusun beberapa setStatModifier
yang mewakili apa pun yang dapat mengubah statistik (efek sementara, item, lokasi di beberapa bidang peningkatan, kelelahan) tetapi AndaEffectiveStats
tidak perlu memahami karenaStatModifiers
akan kelola apa saja hal-hal itu dan seberapa banyak efek dan apa yang akan mereka miliki pada statistik mana. ItuStatModifier
akan menjadi antarmuka untuk hal-hal yang berbeda dan dapat mengetahui hal-hal seperti "apakah saya di zona", "kapan obatnya habis", dll ... tetapi tidak harus membiarkan benda lain mengetahui hal-hal seperti itu. Itu hanya perlu mengatakan stat mana dan bagaimana mengubahnya sekarang. Lebih baik lagiStatModifier
dapat dengan mudah memaparkan metode untuk menghasilkan sesuatu yang tidak berubahStats
berdasarkan pada yang lainStats
yang akan berbeda karena telah diubah dengan tepat. Anda kemudian bisa melakukan sesuatu seperticurrentStats = statModifier2.modify(statModifier1.modify(baseStats))
dan semuaStats
bisa berubah. Saya bahkan tidak akan kode itu secara langsung, saya mungkin akan mengulangi semua pengubah dan menerapkan masing-masing ke hasil pengubah sebelumnya dimulai denganbaseStats
.sumber