Apakah masuk akal untuk mendefinisikan antarmuka jika saya sudah memiliki kelas abstrak?

12

Saya memiliki kelas dengan beberapa fungsi standar / bersama. Saya gunakan abstract classuntuk itu:

public interface ITypeNameMapper
{
    string Map(TypeDefinition typeDefinition);
}

public abstract class TypeNameMapper : ITypeNameMapper
{
    public virtual string Map(TypeDefinition typeDefinition)
    {
        if (typeDefinition is ClassDefinition classDefinition)
        {
            return Map(classDefinition);
        }
        ...

        throw new ArgumentOutOfRangeException(nameof(typeDefinition));
    }

    protected abstract string Map(ClassDefinition classDefinition);
}

Seperti yang Anda lihat, saya juga memiliki antarmuka ITypeNameMapper. Apakah masuk akal untuk mendefinisikan antarmuka ini jika saya sudah memiliki kelas abstrak TypeNameMapperatau abstract classcukup?

TypeDefinition dalam contoh minimal ini juga abstrak.

Konrad
sumber
4
Hanya FYI - dalam OOD, memeriksa tipe dalam kode merupakan indikasi bahwa hierarki kelas Anda tidak benar - ini memberi tahu Anda untuk membuat kelas turunan dari tipe yang Anda periksa. Jadi, dalam hal ini, Anda menginginkan antarmuka ITypeMapper yang menentukan metode Map () dan kemudian mengimplementasikan antarmuka itu di kelas TypeDefinition dan ClassDefinition. Dengan begitu, ClassAssembler Anda hanya beralih melalui daftar ITypeMappers, memanggil Map () pada masing-masing, dan mendapatkan string yang diinginkan dari kedua jenis karena mereka masing-masing memiliki implementasi sendiri.
Rodney P. Barbati
1
Menggunakan kelas dasar sebagai pengganti antarmuka, kecuali Anda benar-benar harus, disebut "membakar basis". Jika itu bisa dilakukan dengan antarmuka, lakukan dengan antarmuka. Kelas abstrak kemudian menjadi tambahan yang bermanfaat. artima.com/intv/dotnet.html Secara khusus, remot mengharuskan Anda untuk berasal dari MarshalByRefObject jadi jika Anda ingin di-remotes, Anda tidak dapat berasal dari hal lain.
Ben
@ RodneyP.Barbati tidak akan berfungsi jika saya ingin memiliki banyak pemetaan untuk jenis yang sama yang dapat saya alihkan tergantung pada situasinya
Konrad
1
@Ben membakar basis yang menarik. Terima kasih untuk permata itu!
Konrad
@ Konrad Itu tidak benar - tidak ada yang mencegah Anda dari mengimplementasikan antarmuka dengan memanggil implementasi lain dari antarmuka, karenanya, setiap jenis mungkin memiliki implementasi pluggable yang Anda paparkan melalui implementasi dalam tipe.
Rodney P. Barbati

Jawaban:

31

Ya, karena C # tidak mengizinkan banyak pewarisan kecuali dengan antarmuka.

Jadi jika saya memiliki kelas yang merupakan TypeNameMapper dan SomethingelseMapper saya bisa melakukannya:

class MultiFunctionalClass : ITypeNameMapper, ISomethingelseMapper 
{
    private TypeNameMapper map1
    private SomethingelseMapper map2

    public string Map(TypeDefinition typeDefinition) { return map1.Map(typeDefintion);}

    public string Map(OtherDef otherDef) { return map2.Map(orderDef); }
}
Ewan
sumber
4
@Liath: Tanggung jawab tunggal sebenarnya merupakan faktor pendukung mengapa warisan dibutuhkan. Mempertimbangkan tiga ciri yang mungkin: ICanFly, ICanRun, ICanSwim. Anda perlu membuat 8 kelas makhluk, satu untuk setiap kombinasi sifat (YYY, YYN, YNY, ..., NNY, NNN). SRP menyatakan bahwa Anda menerapkan setiap perilaku (terbang, lari, berenang) satu kali dan kemudian menerapkannya kembali pada 8 makhluk. Komposisi akan lebih baik di sini, tetapi mengabaikan komposisi sebentar, Anda seharusnya sudah melihat perlunya mewarisi / menerapkan beberapa perilaku yang dapat digunakan kembali secara terpisah karena SRP.
Flater
4
@ Konrad itulah yang saya maksud. Jika Anda menulis antarmuka, dan tidak pernah membutuhkannya, maka biayanya adalah 3 LoC. Jika Anda tidak menulis antarmuka dan mendapati Anda memang membutuhkannya, biayanya mungkin refactoring seluruh aplikasi Anda.
Ewan
3
(1) Menerapkan antarmuka adalah warisan? (2) Pertanyaan dan jawaban keduanya harus memenuhi syarat. "Tergantung." (3) Warisan berganda membutuhkan stiker peringatan di atasnya. (4) Saya dapat menunjukkan kepada Anda K tentang interfacemalapraktik formal craptacular .. implementasi berlebihan yang seharusnya ada di basis - yang berevolusi secara independen !, perubahan logika bersyarat yang mendorong sampah ini karena kekurangan metode templat, enkapsulasi rusak, abstraksi yang tidak ada. Favorit saya: 1-metode kelas masing-masing menerapkan 1-metode sendiri interface, masing-masing dengan hanya satu contoh. ... etc, etc, and etc.
radarbob
3
Ada koefisien gesekan dalam kode yang tidak terlihat. Anda merasakannya ketika dipaksa untuk membaca antarmuka yang berlebihan. Kemudian itu menunjukkan dirinya memanas seperti pesawat ulang-alik membanting ke atmosfer sebagai implementasi antarmuka bulan kelas abstrak. Ini adalah Twilight Zone dan ini adalah pesawat ulang-alik Challenger. Menerima nasib saya, saya menonton plasma yang mengamuk dengan takjub, rasa takut terpisah. Gesekan tanpa belas kasihan merobek bagian depan kesempurnaan, menempatkan hulk yang hancur ke dalam produksi dengan bunyi gemuruh yang menggelegar.
radarbob
4
@radarbob Poetic. ^ _ ^ Saya setuju; sementara biaya antarmuka rendah, itu tidak ada. Tidak begitu banyak dalam menulisnya, tetapi dalam situasi debugging itu 1-2 langkah lagi untuk sampai ke perilaku aktual, yang dapat bertambah dengan cepat ketika Anda mendapatkan setengah lusin level abstraksi yang mendalam. Di perpustakaan, biasanya sepadan. Namun dalam aplikasi, refactoring lebih baik daripada generalisasi prematur.
Errorsatz
1

Antarmuka dan kelas abstrak melayani tujuan yang berbeda:

  • Antarmuka mendefinisikan API dan milik klien bukan implementasinya.
  • Jika kelas berbagi implementasi maka Anda dapat mengambil manfaat dari kelas abstrak .

Dalam contoh Anda, interface ITypeNameMappertentukan kebutuhan klien dan abstract class TypeNameMappertidak menambah nilai apa pun.

Geoffrey Hale
sumber
2
Kelas abstrak juga milik klien. Saya tidak tahu apa yang Anda maksud dengan itu. Segala sesuatu yang menjadi publicmilik klien. TypeNameMappermenambahkan banyak nilai karena menghemat pelaksana dari menulis beberapa logika umum.
Konrad
2
Apakah sebuah antarmuka disajikan sebagai interfaceatau tidak hanya relevan dalam C # yang melarang pewarisan berganda penuh.
Deduplicator
0

Seluruh konsep antarmuka dibuat untuk mendukung keluarga kelas yang memiliki API bersama.

Ini secara eksplisit mengatakan bahwa setiap penggunaan antarmuka menyiratkan bahwa ada (atau diharapkan) lebih dari satu implementasi spesifikasinya.

Kerangka kerja DI telah memperkeruh perairan di sini karena banyak dari mereka memerlukan antarmuka meskipun hanya akan ada satu implementasi - bagi saya ini adalah overhead yang tidak masuk akal karena dalam banyak kasus hanya cara yang lebih kompleks dan lebih lambat untuk memanggil yang baru, tetapi adalah apa adanya.

Jawabannya ada pada pertanyaan itu sendiri. Jika Anda memiliki kelas abstrak, Anda sedang mempersiapkan pembuatan lebih dari satu kelas turunan dengan API umum. Dengan demikian, penggunaan antarmuka ditunjukkan dengan jelas.

Rodney P. Barbati
sumber
2
"Jika Anda memiliki kelas abstrak, ... penggunaan antarmuka ditunjukkan dengan jelas." - Kesimpulan saya adalah kebalikannya: Jika Anda memiliki kelas abstrak, maka gunakan seolah-olah itu adalah antarmuka (jika DI tidak bermain dengannya, pertimbangkan implementasi yang berbeda). Hanya ketika itu benar-benar tidak berfungsi, buat antarmuka - Anda dapat melakukannya kapan saja nanti.
maaartinus
1
Ditto, @maaartinus. Dan bahkan "... seolah-olah itu sebuah antarmuka." Anggota publik kelas adalah antarmuka. Juga untuk "Anda dapat melakukannya kapan saja nanti"; ini masuk ke jantung dasar-dasar desain 101.
radarbob
@maaartinus: Jika seseorang membuat antarmuka dari awal, tetapi menggunakan referensi tipe abstrak di semua tempat, keberadaan antarmuka tidak akan membantu apa pun. Namun, jika kode klien menggunakan tipe antarmuka alih-alih tipe kelas abstrak kapan pun memungkinkan, itu akan sangat memudahkan hal-hal jika diperlukan untuk menghasilkan implementasi yang secara internal sangat berbeda dari kelas abstrak. Mengubah kode klien pada saat itu untuk menggunakan antarmuka akan jauh lebih menyakitkan daripada merancang kode klien untuk melakukannya sejak awal.
supercat
1
@supercat Saya setuju dengan anggapan Anda sedang menulis perpustakaan. Jika ini adalah aplikasi, maka saya tidak dapat melihat masalah mengganti semua kejadian sekaligus. Eclipse saya dapat melakukannya (Java, bukan C #), tetapi jika tidak bisa, itu hanya seperti find ... -exec perl -pi -e ...dan mungkin memperbaiki kesalahan kompilasi sepele. Maksud
maaartinus
Kalimat pertama akan benar, jika Anda ditandai interfacesebagai kode (Anda berbicara tentang C # - interfaces, bukan antarmuka abstrak, seperti seperangkat kelas / fungsi / dll), dan mengakhirinya "... tetapi masih melarang beberapa penuh warisan." Tidak perlu untuk interfacejika Anda memiliki MI.
Deduplicator
0

Jika kita ingin menjawab pertanyaan secara eksplisit, penulis mengatakan, "Apakah masuk akal untuk mendefinisikan antarmuka ini jika saya sudah memiliki kelas abstrak TypeNameMapper atau kelas abstrak sudah cukup?"

Jawabannya adalah ya dan tidak - Ya, Anda harus membuat antarmuka meskipun Anda sudah memiliki kelas dasar abstrak (karena Anda tidak boleh merujuk ke kelas dasar abstrak dalam kode klien apa pun), dan tidak, karena Anda seharusnya tidak membuat kelas dasar abstrak tanpa adanya antarmuka.

Jelas bahwa Anda belum cukup memikirkan API yang ingin Anda bangun. Munculkan API terlebih dahulu, lalu berikan implementasi parsial jika diinginkan. Fakta bahwa kelas dasar abstrak Anda mengetik kode memeriksa sudah cukup untuk memberi tahu Anda itu bukan abstraksi yang tepat.

Seperti pada kebanyakan hal dalam OOD, ketika Anda berada di alur dan memiliki model objek Anda dilakukan dengan baik, kode Anda akan memberi tahu Anda tentang apa yang terjadi selanjutnya. Mulailah dengan sebuah antarmuka, terapkan antarmuka itu di kelas yang Anda butuhkan. Jika Anda menemukan diri Anda menulis kode yang serupa, ekstrak menjadi kelas dasar abstrak - itu antarmuka yang penting, kelas dasar abstrak hanyalah pembantu dan mungkin ada lebih dari satu.

Rodney P. Barbati
sumber
-1

Ini cukup sulit dijawab tanpa mengetahui sisa aplikasi.

Dengan asumsi Anda menggunakan beberapa jenis model DI dan telah merancang kode Anda agar dapat diperluas sebisa mungkin (sehingga Anda dapat menambahkan TypeNameMapperdengan mudah) saya akan mengatakan ya.

Alasan untuk:

  • Anda secara efektif mendapatkannya secara gratis, karena kelas dasar mengimplementasikan interfaceAnda tidak perlu khawatir tentang hal itu dalam implementasi anak
  • Sebagian besar kerangka kerja IoC dan perpustakaan Mocking mengharapkan interfaces. Sementara banyak dari mereka yang bekerja dengan kelas abstrak itu tidak selalu diberikan dan saya sangat percaya dalam mengikuti jalan yang sama dengan 99% pengguna perpustakaan lainnya jika memungkinkan.

Alasan menentang:

  • Sebenarnya (seperti yang telah Anda tunjukkan), Anda TIDAK BENAR-BENAR perlu
  • Jika class/ interfaceperubahan ada sedikit overhead tambahan

Semua hal dipertimbangkan saya akan mengatakan bahwa Anda harus membuat interface. Alasannya cukup dapat diabaikan tetapi, sementara mungkin untuk menggunakan abstrak dalam mengejek dan IoC sering jauh lebih mudah dengan antarmuka. Namun pada akhirnya, saya tidak akan mengkritik kode rekan jika mereka sebaliknya.

Liath
sumber