Ketika Anda menggunakan warisan untuk menggunakan kembali kode, apakah Anda merasa terlalu rumit untuk menelan manfaat dari penggunaan kembali?

8

Saya telah mengkode selama sekitar 8 tahun, namun saya masih menemukan warisan terlalu fleksibel dan kadang-kadang membuat Anda benar-benar bingung dengan kode yang Anda tulis. Salah satu contoh paling sederhana adalah:

abstract class AClass {
    protected void method1() {
        if(check()) {
            do1();
        } else {
            do2();
        }
    }
    protected abstract void do1();
    protected abstract void do2();
}

Tujuan kelas adalah agar orang dapat mengimplementasikan do1 () dan do2 () sehingga beberapa logika lebih lanjut dapat dilakukan, namun terkadang orang memutuskan untuk membebani method1 (), dan kemudian semuanya menjadi rumit dengan segera.

Saya hanya menemukan dalam pola strategi, kode digunakan kembali dengan baik melalui warisan, dalam banyak kasus perancang kelas dasar tahu subkelasnya dengan sangat baik, dan bahwa warisan sepenuhnya opsional.

Saya memiliki kelas yang diwarisi oleh 4 kelas - sebuah IoHandler, dan subclass untuk sisi server, sisi klien, server tepi, server asal, dan mulai membuat saya gila. Saya selalu dalam kode refactoring, saya selalu keluar dengan ide-ide yang saya pikir akan berhasil dan kemudian terbukti tidak. Dikatakan bahwa otak manusia hanya dapat menyimpan 7 buah informasi satu kali, apakah saya membawa terlalu banyak?

taktik
sumber
1
Anda tahu Anda bisa mencegah kelebihan metode tertentu? Tandai sebagai final[jika Anda menggunakan java]. Ini secara efektif membuatnya non-virtual, dan tidak dapat kelebihan beban. Sempurna untuk digunakan dalam keadaan ini [pola templat]
Farrell
@Farrell Bahkan saya menandainya 'dilindungi secara intensif sehingga bisa kelebihan beban'. Saya pikir ketika saya melakukan ini saya menggunakan beberapa ide dari MFC, yang dirancang dengan buruk.
tactoth
2
Metode overloading tidak ada hubungannya dengan pewarisan. Sangat aneh bahwa beberapa komentar dan bahkan jawaban yang diterima mengulanginya kata demi kata. Tentunya Anda berbicara tentang mengganti ?
Aaronaught
@Aaronaught Nah koreksi Anda benar-benar berguna :(
tactoth

Jawaban:

3

Setelah Anda terus mengulangi pola-pola seperti itu, mereka tidak terlihat begitu rumit lagi.

Juga, Anda harus tahu tentang kelas dasar sebelum mewarisinya. Dan pewarisan tidak akan menjadi opsional selama kode umum digunakan kembali. Ambil pola lain seperti pabrik abstrak / komposit - warisan itu sebenarnya memiliki tujuan.

Aturan jempol: Jangan gunakan warisan hanya untuk menyatukan banyak kelas. Gunakan di mana pun itu masuk akal. Dalam contoh yang dibuat, pengguna kelas anak yang kelebihan metode1 harus memiliki beberapa alasan serius untuk melakukannya. (OTOH, saya juga ingin cara untuk mencegah kelebihan fungsi tertentu saja - untuk menghindari ini). Dan sebelum menggunakan kelas anak itu, Anda harus tahu tentang kekhasan ini juga. Tetapi pola strategi murni tidak akan melakukannya - jika ya, lakukan dengan sangat baik.

Jika Anda memberikan contoh kode yang sebenarnya, mungkin orang-orang di sini dapat memeriksanya untuk Anda.

Subu Sankara Subramanian
sumber
Terima kasih atas jawabannya, saya masih berpikir warisan terutama digunakan untuk berbagi logika, saya selalu menerapkan prinsip bahwa ketika kode dapat dibagikan, mereka harus dibagikan, dan ketika Anda memutuskan untuk tidak membagikannya, Anda merasa menjadi lebih jelas. .
tactoth
Contoh kode terlalu besar, saya memiliki 30+ kelas yang perlu disortir.
tactoth
18

Ya, ini bisa sangat rumit ... dan Anda berada di perusahaan yang baik.

Joshua Bloch di Java Efektif dengan baik menangkap beberapa masalah warisan dan merekomendasikan untuk memilih komposisi daripada warisan.

Warisan adalah cara ampuh untuk mencapai penggunaan kembali kode, tetapi itu tidak selalu merupakan alat terbaik untuk pekerjaan itu. Digunakan secara tidak tepat, itu mengarah pada perangkat lunak yang rapuh. Aman untuk menggunakan warisan dalam sebuah paket, di mana subclass dan implementasi superclass berada di bawah kendali programmer yang sama. Juga aman untuk menggunakan warisan ketika memperluas kelas yang dirancang khusus dan didokumentasikan untuk ekstensi (Butir 15). Mewarisi dari kelas beton biasa melintasi batas paket, bagaimanapun, berbahaya. Sebagai pengingat, buku ini menggunakan kata "warisan" yang berarti warisan implementasi (ketika satu kelas memperluas yang lain). Masalah yang dibahas dalam item ini tidak berlaku untuk warisan antarmuka (ketika kelas mengimplementasikan antarmuka atau di mana satu antarmuka meluas yang lain).

Tidak seperti doa metode, pewarisan memecah enkapsulasi. Dengan kata lain, subkelas tergantung pada detail implementasi superclass untuk fungsi yang tepat. Implementasi superclass dapat berubah dari rilis ke rilis, dan jika benar, subclass dapat rusak, meskipun kodenya belum disentuh. Sebagai konsekuensinya, subkelas harus berevolusi bersama dengan superkelasnya, kecuali jika penulis superkelas itu telah merancang dan mendokumentasikannya secara khusus untuk tujuan perpanjangan.

Dakotah Utara
sumber
+1. Dan perhatikan bahwa idenya telah ada jauh lebih lama dan jauh lebih luas daripada hanya Blok dan Jawa Efektif ; lihat di sini .
Josh Kelley
1

Saya pikir pertanyaan Anda terlalu kabur dan umum. Apa yang Anda gambarkan terdengar seperti masalah yang rumit, dengan atau tanpa warisan. Jadi IMHO itu sepenuhnya normal bahwa Anda berjuang dengan itu.

Tentu saja dalam beberapa kasus warisan bukan alat terbaik untuk pekerjaan (referensi wajib untuk "mendukung komposisi daripada warisan" ;-). Tetapi ketika digunakan dengan hati-hati, saya merasa itu berguna. Dari uraian Anda, sulit untuk memutuskan apakah warisan adalah alat yang tepat untuk kasus Anda.

Péter Török
sumber
Ini adalah pertanyaan yang tidak jelas dan tujuannya adalah untuk mengumpulkan beberapa ide untuk membuatnya mudah untuk membuat keputusan desain. Saya telah beralih antara komposisi dan warisan bolak-balik.
tactoth
1

jika metode Anda terlalu rumit, itu membuat subkelas menangis

membuat metode Anda melakukan satu hal spesifik

Steven A. Lowe
sumber
1

Warisan komposisi mempersempit vektor perubahan.

Bayangkan solusi Anda diimplementasikan dan Anda menggunakan AClass di 300 tempat berbeda dari basis kode, dan sekarang ada persyaratan untuk mengubah 45 panggilan method1 () sehingga metode do3 () dipanggil alih-alih do2 ().

Anda dapat mengimplementasikan do3 () pada IAnInterface (mudah-mudahan semua implementasi antarmuka dapat menangani metode do3 () dengan mudah) dan membuat BClass yang memanggil do1 () atau do3 () di method1 () memanggil dan mengubah referensi 45 ke baik AClass dan ketergantungan yang disuntikkan.

sekarang gambar mengimplementasikan perubahan yang sama untuk do4 (), do5 (), ...

Atau Anda bisa melakukan yang berikut ...

interface IFactoryCriteria {
    protected boolean check();
}

public final class DoerFactory() {
    protected final IAnInterfaceThatDoes createDoer( final IFactoryCriteria criteria ) {
        return criteria.check() ? new Doer1() : new Doer2();
    }
}

interface IAnInterfaceThatDoes {
    abstract void do();
}

public final class AClass implements IFactoryCriteria {
    private DoerFactory factory;
    public AClass(DoerFactory factory)
    {
        this.factory = factory;
    }

    protected void method1() {
        this.factory.createDoer( this ).do();
    }

    protected boolean check() {
        return true;//or false
    }
}

Sekarang Anda hanya perlu menerapkan pabrik kedua yang mengembalikan Doer1 atau Doer2 dan mengubah injeksi pabrik di 45 tempat. AClass tidak berubah, IAnInterface tidak berubah, dan Anda dapat dengan mudah menangani lebih banyak perubahan seperti do4 (), do5 ().

Heath Lilley
sumber
1
+1 Saya suka penerapan masalah ini dari yang diusulkan oleh Ed. Saya pikir ini membuat masalah lebih SRP serta membatasi dependensi yang dimiliki AClass. Saya kira pertanyaan utama saya adalah pro dan kontra dari hanya meloloskan IAnInterfaceThatDoes ke AClass alih-alih kelas pabrik. Misalnya apa yang terjadi jika logika untuk menentukan IAnInterfaceThatDoes yang tepat berada di luar ruang lingkup pabrik?
dreza
Saya membuat pabrik ketergantungan hanya untuk meminimalkan paparan IAnInterfaceThatDoes. Jika kode panggilan memerlukan akses ke antarmuka itu, maka Anda tentu bisa memindahkannya. Saya tidak tahu situasi di mana saya ingin memindahkan pembuatan antarmuka dari pabrik. Saya akan mengatakan bahwa jika itu harus berubah maka mengimplementasikan antarmuka DoerFactoryI dengan metode createDoer yang terbuka akan menjadi cara lain. Anda kemudian dapat menerapkan pabrik menggunakan kerangka kerja DI atau sesuatu.
Heath Lilley
0

Kenapa tidak melakukannya:

interface IAnInterface {
    abstract void do1();
    abstract void do2();
}

public final class AClass {
    private IAnInterface Dependent;
    public AClass(IAnInterface dependent)
    {
        this.Dependent = dependent;
    }

    protected void method1() {
        if(check()) {
            this.Dependent.do1();
        } else {
            this.Dependent.do2();
        }
    }
}

(Saya pikir final sama dengan "disegel" dalam C #, seseorang tolong perbaiki saya jika sintaks itu salah).

Dependency injection / IoC berarti Anda hanya dapat mewarisi metode yang benar-benar Anda inginkan agar orang dapat mengubahnya, yang menurut saya menyelesaikan masalah Anda. Plus, itu tidak terdengar seperti kelas utama benar-benar melakukan apa pun do1 dan do2, lebih dari itu mendelegasikan panggilan ke subkelasnya berdasarkan pada suatu kondisi: Anda melanggar pemisahan kekhawatiran!

Ed James
sumber