Mengurangi boilerplate di kelas yang mengimplementasikan antarmuka melalui komposisi

10

Saya punya kelas: Aitu adalah gabungan dari sejumlah kelas yang lebih kecil B,, Cdan D.

B, C, Dan Dmengimplementasikan interface IB, ICdan IDmasing-masing.

Karena Amendukung semua fungsionalitas B, Cdan D, Aimplementasi IB, ICdan IDjuga, tetapi sayangnya hal ini menyebabkan banyak perutean ulang dalam implementasiA

Seperti itu:

interface IB
{
    int Foo {get;}
}

public class B : IB
{
    public int Foo {get {return 5;}}
}

interface IC
{
    void Bar();
}

public class C : IC
{
    public void Bar()
    {
    }
}

interface ID
{
    string Bash{get; set;}
}

public class D: ID
{
    public string Bash {get;set;}
}

class A : IB, IC, ID
{
    private B b = new B();
    private C c = new C();
    private D d = new D();

    public int Foo {get {return b.Foo}}

    public A(string bash)
    {
        Bash = bash;
    }

    public void Bar()
    {
        c.Bar();
    }

    public string Bash
    {
         get {return d.Bash;}
         set {d.Bash = value;}
    }
}

Apakah ada cara untuk menyingkirkan pengalihan boilerplate ini A? B, Cdan Dsemua mengimplementasikan fungsionalitas yang berbeda tetapi umum, dan saya suka Amengimplementasikannya IB, ICdan IDkarena itu berarti saya dapat lulus dengan Asendirinya sebagai ketergantungan yang cocok dengan antarmuka tersebut, dan tidak harus mengekspos metode pembantu dalam.

Nick Udell
sumber
Bisakah Anda mengatakan sesuatu tentang mengapa Anda memerlukan kelas A untuk mengimplementasikan IB, IC, ID? Mengapa tidak mengekspos BCD sebagai publik saja?
Esben Skov Pedersen
Saya pikir alasan utama saya lebih suka Amengimplementasikan IB, ICdan IDapakah rasanya lebih masuk akal untuk menulis Person.Walk()sebagai kebalikan dari Person.WalkHelper.Walk(), misalnya.
Nick Udell

Jawaban:

7

Apa yang Anda cari biasanya disebut mixin . Sayangnya, C # tidak mendukungnya.

Ada beberapa solusi: satu , dua , tiga , dan banyak lagi.

Saya sebenarnya sangat suka yang terakhir. Gagasan untuk menggunakan kelas parsial yang dihasilkan secara otomatis untuk menghasilkan boilerplate mungkin adalah yang paling dekat yang bisa Anda dapatkan dengan solusi yang benar-benar baik:

[pMixins] adalah plug-in Visual Studio yang memindai solusi untuk kelas parsial yang dihiasi dengan atribut pMixin. Dengan menandai parsial kelas Anda, [pMixins] dapat membuat file di belakang kode dan menambahkan anggota tambahan ke kelas Anda

Euforia
sumber
2

Meskipun tidak mengurangi boilerplate dalam kode itu sendiri, Visual Studio 2015 sekarang hadir dengan opsi refactoring untuk secara otomatis menghasilkan boilerplate untuk Anda.

Untuk menggunakan ini, pertama buat antarmuka IExampledan implementasi Anda Example. Kemudian buat kelas baru Anda Compositedan buat warisan IExample, tetapi jangan mengimplementasikan antarmuka.

Tambahkan properti atau bidang tipe Exampleke Compositekelas Anda , buka menu tindakan cepat pada token IExampledi Compositefile kelas Anda dan pilih "Terapkan antarmuka melalui 'Contoh'" di mana 'Contoh' dalam hal ini adalah nama bidang atau properti.

Anda harus mencatat bahwa sementara Visual Studio akan menghasilkan dan mengarahkan kembali semua metode dan properti yang didefinisikan dalam antarmuka ke kelas pembantu ini untuk Anda, itu tidak akan mengarahkan peristiwa pada saat jawaban ini diposting.

Nick Udell
sumber
1

Kelas Anda melakukan terlalu banyak, itu sebabnya Anda harus mengimplementasikan begitu banyak boilerplate. Anda mengatakan dalam komentar:

rasanya lebih masuk akal untuk menulis Person.Walk () dibandingkan dengan Person.WalkHelper.Walk ()

Saya tidak setuju. Tindakan berjalan kemungkinan melibatkan banyak aturan dan tidak termasuk dalam Personkelas - itu termasuk dalam WalkingServicekelas dengan walk(IWalkable)metode di mana Orang mengimplementasikan IWalkable.

Selalu ingat prinsip-prinsip SOLID ketika Anda menulis kode. Dua yang paling berlaku di sini adalah Pemisahan Kekhawatiran (ekstrak kode berjalan ke kelas yang terpisah) dan Segregasi Antarmuka (membagi antarmuka menjadi tugas / fungsi / kemampuan tertentu). Kedengarannya seperti Anda mungkin memiliki beberapa I dengan beberapa antarmuka Anda tetapi kemudian membatalkan semua pekerjaan baik Anda dengan membuat satu kelas mengimplementasikannya.

Stuart Leyland-Cole
sumber
Dalam contoh saya fungsi tersebut adalah diimplementasikan dalam kelas terpisah, dan saya memiliki kelas terdiri dari beberapa ini sebagai cara pengelompokan perilaku yang diperlukan. Tidak ada implementasi aktual yang ada di kelas saya yang lebih besar, semuanya ditangani oleh komponen yang lebih kecil. Mungkin Anda benar, dan mempublikasikan kelas-kelas penolong itu agar mereka dapat berinteraksi langsung adalah cara yang harus ditempuh.
Nick Udell
4
Adapun ide walk(IWalkable)metode, saya pribadi tidak suka karena layanan jalan itu menjadi tanggung jawab penelepon, dan bukan Orang, dan konsistensi tidak dijamin. Setiap penelepon harus mengetahui layanan mana yang digunakan untuk menjamin konsistensi, sedangkan menyimpannya di kelas Person berarti harus diubah secara manual agar perilaku berjalan berbeda, sambil tetap memungkinkan inversi ketergantungan.
Nick Udell
0

Apa yang Anda cari adalah pewarisan berganda. Namun, baik C # maupun Java tidak memilikinya.

Anda dapat memperpanjang B dari A. Ini akan menghilangkan kebutuhan untuk bidang pribadi untuk B serta lem pengarah ulang. Tetapi Anda hanya dapat melakukan ini untuk salah satu dari B, C atau D.

Sekarang jika Anda dapat membuat C mewarisi dari B, dan D dari C maka Anda hanya perlu memiliki A memperpanjang D ...;) - hanya untuk memperjelas, saya tidak benar-benar mendorongnya dalam contoh ini karena tidak ada indikasi untuk itu.

Di C ++ Anda bisa memiliki A inherit dari B, C, dan D.

Tetapi multiple inheritance membuat program lebih sulit untuk dipikirkan, dan memiliki masalah sendiri; Lebih jauh, seringkali ada cara yang bersih untuk menghindarinya. Namun, terkadang tanpa itu, kita perlu membuat tradeoffs desain antara mengulangi boilerplate dan bagaimana model objek diekspos.

Rasanya agak seperti Anda mencoba mencampur komposisi dan warisan. Jika ini adalah C ++, Anda bisa menggunakan solusi pewarisan murni. Tetapi karena itu bukan saya akan merekomendasikan merangkul komposisi.

Erik Eidt
sumber
2
Anda dapat memiliki banyak pewarisan menggunakan antarmuka di java dan c #, seperti yang telah dilakukan op dalam kode contohnya.
Robert Harvey
1
Beberapa orang mungkin mempertimbangkan untuk mengimplementasikan antarmuka sama dengan multiple inheritance (seperti pada katakan dalam C ++). Orang lain akan tidak setuju, karena antarmuka tidak dapat menyajikan metode turunan instance, yang adalah apa yang akan Anda miliki jika Anda memiliki beberapa pewarisan. Dalam C #, Anda tidak dapat memperluas beberapa kelas dasar, oleh karena itu, Anda tidak dapat mewarisi implementasi metode contoh dari beberapa kelas, itulah sebabnya Anda membutuhkan semua boilerplate ...
Erik Eidt