Apakah "komposisi atas warisan" melanggar "prinsip kering"?

36

Sebagai contoh, pertimbangkan saya memiliki kelas untuk kelas lain untuk diperluas:

public class LoginPage {
    public String userId;
    public String session;
    public boolean checkSessionValid() {
    }
}

dan beberapa subclass:

public class HomePage extends LoginPage {

}

public class EditInfoPage extends LoginPage {

}

Bahkan, subclass tidak memiliki metode untuk menimpa, juga saya tidak akan mengakses HomePage secara umum, yaitu: Saya tidak akan melakukan sesuatu seperti:

for (int i = 0; i < loginPages.length; i++) {
    loginPages[i].doSomething();
}

Saya hanya ingin menggunakan kembali halaman login. Tetapi menurut https://stackoverflow.com/a/53354 , saya harus memilih komposisi di sini karena saya tidak memerlukan antarmuka LoginPage, jadi saya tidak menggunakan warisan di sini:

public class HomePage {
    public LoginPage loginPage;
}

public class EditInfoPage {
    public LoginPage loginPage;
}

tetapi masalahnya ada di sini, pada versi baru, kode:

public LoginPage loginPage;

duplikat ketika kelas baru ditambahkan. Dan jika LoginPage membutuhkan setter dan getter, lebih banyak kode perlu disalin:

public LoginPage loginPage;

private LoginPage getLoginPage() {
    return this.loginPage;
}
private void setLoginPage(LoginPage loginPage) {
    this.loginPage = loginPage;
}

Jadi pertanyaan saya adalah, apakah "komposisi atas warisan" melanggar "prinsip kering"?

ocomfd
sumber
13
Kadang-kadang Anda tidak memerlukan warisan atau komposisi, yang mengatakan bahwa kadang-kadang satu kelas dengan beberapa objek melakukan pekerjaan.
Erik Eidt
23
Mengapa Anda ingin semua halaman Anda menjadi halaman login? Mungkin hanya memiliki halaman login.
Mr Cochese
29
Tetapi, dengan warisan, Anda memiliki duplikat di extends LoginPagesemua tempat. PERIKSA MATE!
el.pescado
2
Jika Anda sering memiliki masalah dalam cuplikan terakhir, Anda mungkin terlalu sering menggunakan getter dan setter. Mereka cenderung melanggar enkapsulasi.
Brian McCutchon
4
Jadi, buat halaman Anda bisa didekorasi dan jadikan LoginPagedekorator Anda. Tidak ada lagi duplikasi, hanya yang sederhana page = new LoginPage(new EditInfoPage())dan Anda selesai. Atau Anda menggunakan prinsip buka-tutup untuk membuat modul otentikasi yang dapat ditambahkan ke halaman mana pun secara dinamis. Ada banyak cara untuk menangani duplikasi kode, terutama yang melibatkan menemukan abstraksi baru. LoginPagekemungkinan adalah nama yang buruk, yang ingin Anda pastikan adalah bahwa pengguna diautentikasi saat meramban halaman itu, saat sedang diarahkan ke LoginPageatau ditampilkan pesan kesalahan yang tepat jika ia tidak.
Polygnome

Jawaban:

46

Eh tunggu, Anda khawatir pengulangan itu

public LoginPage loginPage;

di dua tempat melanggar KERING? Dengan logika itu

int x;

sekarang hanya bisa ada dalam satu objek di seluruh basis kode. Bleh.

KERING adalah hal yang baik untuk diingat tetapi datanglah. Selain

... extends LoginPage

semakin diduplikasi dalam alternatif Anda sehingga bahkan menjadi anal tentang KERING tidak akan masuk akal.

Kekhawatiran DRY yang valid cenderung berfokus pada perilaku identik yang diperlukan di banyak tempat yang didefinisikan di banyak tempat sehingga kebutuhan untuk mengubah perilaku ini pada akhirnya membutuhkan perubahan di banyak tempat. Buat keputusan di satu tempat dan Anda hanya perlu mengubahnya di satu tempat. Itu tidak berarti hanya satu objek yang dapat menyimpan referensi ke LoginPage Anda.

KERING seharusnya tidak diikuti secara membabi buta. Jika Anda menggandakan karena menyalin dan menempel lebih mudah daripada memikirkan metode atau nama kelas yang baik, maka Anda mungkin salah.

Tetapi jika Anda ingin meletakkan kode yang sama di tempat yang berbeda karena tempat yang berbeda dikenakan tanggung jawab yang berbeda dan cenderung perlu berubah secara mandiri, maka mungkin bijaksana untuk melonggarkan penegakan Anda terhadap KERING dan membiarkan perilaku yang identik ini memiliki identitas yang berbeda . Itu adalah pemikiran yang sama yang melarang angka ajaib.

KERING bukan hanya tentang seperti apa kode itu. Ini tentang tidak menyebarkan rincian ide sekitar dengan pengulangan tanpa alasan memaksa pengelola untuk memperbaiki hal-hal menggunakan pengulangan tanpa berpikir. Ketika Anda mencoba mengatakan pada diri sendiri bahwa pengulangan tanpa pikiran hanyalah konvensi Anda bahwa segala sesuatu mengarah pada cara yang benar-benar buruk.

Apa yang saya pikir Anda benar-benar coba untuk mengeluh disebut kode boilerplate. Ya, menggunakan komposisi daripada pewarisan menuntut kode boilerplate. Tidak ada yang terkena secara gratis, Anda harus menulis kode yang memaparkannya. Dengan boilerplate itu muncul fleksibilitas, kemampuan untuk mempersempit antarmuka yang terbuka, untuk memberikan hal-hal yang berbeda nama yang sesuai untuk tingkat abstraksi mereka, tipuan yang baik, dan Anda menggunakan apa yang Anda terdiri dari luar, bukan di dalam, jadi Anda menghadapi antarmuka normal.

Tapi ya, ini banyak mengetik keyboard ekstra. Selama saya bisa mencegah masalah yo-yo memantul ke atas dan ke bawah tumpukan warisan ketika saya membaca kode itu layak.

Sekarang bukan karena saya menolak untuk pernah menggunakan warisan. Salah satu kegunaan favorit saya adalah untuk memberikan pengecualian nama baru:

public class MyLoginPageWasNull extends NullPointerException{}
candied_orange
sumber
int x;bisa ada tidak lebih dari nol kali dalam basis kode
K. Alan Bates
@ K.AlanBates permisi ?
candied_orange
... saya tahu Anda akan membalas dengan lol kelas Point. Tidak ada yang peduli dengan kelas Point. Jika saya masuk ke basis kode Anda dan melihat int a; int b; int x;saya akan mendorong untuk menghapus Anda.
K. Alan Bates
@ K.AlanBates Wow. Menyedihkan bahwa Anda berpikir bahwa perilaku semacam itu akan membuat Anda menjadi pembuat kode yang baik. Coder terbaik bukanlah yang bisa menemukan banyak hal tentang orang lain untuk digerogoti. Itu yang membuat sisanya lebih baik.
candied_orange
Menggunakan nama-nama yang tidak berarti adalah bukan-pemula dan kemungkinan membuat pengembang kode itu sebagai kewajiban daripada aset.
K. Alan Bates
127

Kesalahpahaman umum dengan prinsip KERING adalah bahwa itu entah bagaimana terkait dengan tidak mengulangi baris kode. Prinsip KERING adalah "Setiap bagian dari pengetahuan harus memiliki representasi tunggal, tidak ambigu, otoritatif dalam suatu sistem" . Ini tentang pengetahuan, bukan kode.

LoginPagetahu tentang cara menggambar halaman untuk login. Jika EditInfoPagetahu bagaimana melakukan ini maka itu akan menjadi pelanggaran. Termasuk LoginPagemelalui komposisi sama sekali tidak melanggar prinsip KERING.

Prinsip KERING mungkin merupakan prinsip yang paling disalahgunakan dalam rekayasa perangkat lunak dan harus selalu dianggap bukan sebagai prinsip untuk tidak menduplikasi kode, tetapi sebagai prinsip untuk tidak menduplikasi pengetahuan domain abstrak. Sebenarnya, dalam banyak kasus jika Anda menerapkan KERING dengan benar maka Anda akan menduplikasi kode , dan ini tidak selalu merupakan hal yang buruk.

wasatz
sumber
27
"Prinsip tanggung jawab tunggal" lebih banyak disalahgunakan. "Dont repeat yourself" mungkin dengan mudah masuk sebagai nomor dua :-)
gnasher729
28
"KERING" adalah akronim yang berguna untuk "Jangan Ulangi Diri Sendiri" yang merupakan frase menangkap, tetapi bukan nama prinsip, nama sebenarnya dari prinsip adalah "Sekali Dan Hanya Sekali", yang pada gilirannya adalah nama yang menarik untuk prinsip yang membutuhkan beberapa paragraf untuk dijelaskan. Yang penting adalah memahami pasangan paragraf, bukan menghafal tiga huruf D, R, dan Y.
Jörg W Mittag
4
@ gnasher729 Anda tahu, saya sebenarnya setuju dengan Anda bahwa SRP mungkin lebih banyak disalahgunakan. Meskipun untuk bersikap adil, dalam banyak kasus saya pikir mereka sering disalahgunakan bersama. Teori saya adalah bahwa pemrogram secara umum tidak boleh dipercaya untuk menggunakan akronim dengan "nama mudah".
wasatz
17
IMHO ini adalah jawaban yang bagus. Namun, jujur ​​saja: menurut pengalaman saya, bentuk pelanggaran KERING yang paling sering disebabkan oleh pemrograman copy-paste dan hanya kode duplikasi. Seseorang pernah mengatakan kepada saya suatu kali "setiap kali Anda akan menyalin-menempelkan beberapa kode, pikirkan dua kali jika bagian yang digandakan tidak dapat diekstraksi menjadi fungsi umum" - yang merupakan saran yang sangat bagus.
Doc Brown
9
Penyalahgunaan copy-paste tentu saja merupakan kekuatan pendorong untuk melanggar KERING tetapi hanya melarang copy-paste adalah pedoman yang buruk untuk mengikuti KERING. Saya tidak akan merasa menyesal sama sekali karena memiliki dua metode dengan kode yang identik ketika metode-metode itu mewakili pengetahuan yang berbeda. Mereka melayani dua tanggung jawab yang berbeda. Mereka kebetulan memiliki kode identik hari ini. Mereka harus bebas untuk berubah secara mandiri. Ya, pengetahuan, bukan kode. Baik. Saya menulis salah satu jawaban lain tetapi saya akan tunduk pada yang ini. +1.
candied_orange
12

Jawaban singkat: ya, benar - sampai tingkat kecil yang dapat diterima.

Pada pandangan pertama, pewarisan terkadang dapat menyelamatkan Anda beberapa baris kode, karena itu memiliki efek mengatakan "kelas saya menggunakan kembali akan berisi semua metode dan atribut publik dalam cara 1: 1". Jadi jika ada daftar 10 metode dalam suatu komponen, kita tidak perlu mengulanginya dalam kode di kelas bawaan. Ketika dalam skenario komposisi 9 dari 10 metode harus diekspos secara publik melalui komponen reusing, maka kita harus menuliskan 9 delegasi panggilan, dan membiarkan yang tersisa keluar, tidak ada jalan lain untuk mengatasi hal ini.

Mengapa ini bisa ditoleransi? Lihatlah metode yang direplikasi dalam skenario komposisi - metode ini secara eksklusif mendelegasikan panggilan ke antarmuka komponen, sehingga tidak mengandung logika nyata.

Inti dari prinsip KERING adalah untuk menghindari memiliki dua tempat dalam kode di mana aturan logis yang sama dikodekan - karena ketika aturan logis ini berubah, dalam kode non-KERING mudah untuk mengadaptasi salah satu tempat itu dan melupakan yang lain, yang menyebabkan kesalahan.

Tapi karena panggilan pendelegasian tidak mengandung logika, ini biasanya tidak mengalami perubahan seperti itu, jadi tidak menyebabkan masalah nyata ketika "lebih suka komposisi daripada warisan". Dan bahkan jika antarmuka komponen berubah, yang mungkin menyebabkan perubahan formal di semua kelas menggunakan komponen, dalam bahasa yang dikompilasi kompiler akan memberi tahu kami ketika kami lupa mengubah salah satu penelepon.

Catatan untuk contoh Anda: Saya tidak tahu bagaimana penampilan HomePageAnda dan Anda EditInfoPage, tetapi jika mereka memiliki fungsi login, dan HomePage(atau EditInfoPage) adalah LoginPage , maka warisan mungkin merupakan alat yang tepat di sini. Contoh yang kurang bisa diperdebatkan, di mana komposisi akan menjadi alat yang lebih baik dengan cara yang lebih jelas, mungkin akan membuat segalanya lebih jelas.

Dengan asumsi tidak ada HomePageatau EditInfoPagetidak LoginPage, dan orang ingin menggunakan kembali yang terakhir, seperti yang Anda tulis, daripada sangat mungkin bahwa seseorang hanya memerlukan beberapa bagian LoginPage, bukan segalanya. Jika itu masalahnya, pendekatan yang lebih baik daripada menggunakan komposisi dengan cara yang ditunjukkan mungkin untuk

  • ekstrak bagian yang dapat digunakan kembali LoginPagemenjadi komponennya sendiri

  • menggunakan kembali komponen itu HomePagedan EditInfoPagecara yang sama digunakan sekarang di dalamLoginPage

Dengan begitu, biasanya akan menjadi jauh lebih jelas mengapa dan kapan komposisi atas warisan adalah pendekatan yang tepat.

Doc Brown
sumber