Haruskah saya menggunakan metode abstrak atau virtual?


11

Jika kita berasumsi bahwa tidak diinginkan untuk kelas dasar untuk menjadi kelas antarmuka murni, dan menggunakan 2 contoh dari bawah, yang merupakan pendekatan yang lebih baik, menggunakan definisi kelas metode abstrak atau virtual?

  • Keuntungan dari versi "abstrak" adalah bahwa ia mungkin terlihat lebih bersih dan memaksa kelas turunan untuk memberikan implementasi yang penuh harapan.

  • Keuntungan dari versi "virtual" adalah dapat dengan mudah ditarik oleh modul lain dan digunakan untuk pengujian tanpa menambahkan banyak kerangka dasar seperti yang dibutuhkan versi abstrak.

Versi Abstrak:

public abstract class AbstractVersion
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

Versi Virtual:

public class VirtualVersion
{
    public virtual ReturnType Method1()
    {
        return ReturnType.NotImplemented;
    }

    public virtual ReturnType Method2()
    {
        return ReturnType.NotImplemented;
    }
             .
             .
    public virtual ReturnType MethodN()
    {
        return ReturnType.NotImplemented;
    }

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

Mengapa kita menganggap antarmuka tidak diinginkan?
Anthony Pegram

Tanpa masalah sebagian, sulit untuk mengatakan yang satu lebih baik dari yang lain.
Codism

@Anthony: Antarmuka tidak diinginkan karena ada fungsi berguna yang juga akan masuk ke kelas ini.
Dunk

4
return ReturnType.NotImplemented? Serius? Jika Anda tidak dapat menolak jenis yang tidak diterapkan pada waktu kompilasi (Anda dapat; menggunakan metode abstrak) setidaknya melemparkan pengecualian.
Jan Hudec

3
@Dunk: Di sini mereka adalah diperlukan. Kembali nilai-nilai akan pergi dicentang.
Jan Hudec

Jawaban:


15

Pilihan saya, jika saya mengkonsumsi barang-barang Anda, akan menjadi metode abstrak. Itu sejalan dengan "gagal lebih awal." Mungkin menyakitkan pada waktu deklarasi untuk menambahkan semua metode (meskipun alat refactoring yang layak akan melakukan ini dengan cepat), tetapi setidaknya saya tahu apa masalahnya segera dan memperbaikinya. Saya lebih suka daripada melakukan debug 6 bulan dan perubahan 12 orang nanti untuk melihat mengapa kita tiba-tiba mendapatkan pengecualian yang tidak diterapkan.


Poin bagus tentang tidak sengaja mendapatkan kesalahan NotImplemented Yang merupakan nilai tambah di sisi abstrak, karena Anda akan mendapatkan waktu kompilasi bukannya kesalahan run-time.
Dunk

3
+1 - Selain gagal lebih awal, pewaris dapat segera melihat metode apa yang perlu mereka terapkan melalui "Implement Abstract Class" daripada melakukan apa yang mereka pikir sudah cukup dan kemudian gagal saat runtime.
Telastyn

Saya menerima jawaban ini karena gagal pada waktu kompilasi adalah nilai tambah yang gagal saya daftarkan dan karena referensi untuk menggunakan IDE untuk secara otomatis mengimplementasikan metode dengan cepat.
Dunk

25

Versi virtualnya rawan bug dan secara semantik salah.

Abstrak mengatakan "metode ini tidak diterapkan di sini. Anda harus menerapkannya untuk membuat kelas ini berfungsi"

Virtual mengatakan "Saya memiliki implementasi default tetapi Anda dapat mengubah saya jika Anda perlu"

Jika tujuan akhir Anda adalah testabilitas maka antarmuka biasanya merupakan pilihan terbaik. (kelas ini melakukan x daripada kelas ini adalah kapak). Anda mungkin perlu memecah kelas menjadi beberapa komponen yang lebih kecil agar ini berfungsi dengan baik.


3

Ini tergantung pada penggunaan kelas Anda.

Jika metode memiliki beberapa implementasi "kosong" yang masuk akal, Anda memiliki banyak metode dan Anda sering menimpa hanya beberapa dari mereka, kemudian menggunakan virtualmetode masuk akal. Misalnya ExpressionVisitordiimplementasikan dengan cara ini.

Kalau tidak, saya pikir Anda harus menggunakan abstractmetode.

Idealnya, Anda seharusnya tidak memiliki metode yang tidak diterapkan, tetapi dalam beberapa kasus, itu adalah pendekatan terbaik. Tetapi jika Anda memutuskan untuk melakukan itu, metode tersebut harus membuang NotImplementedException, tidak mengembalikan nilai khusus.


Saya akan mencatat bahwa "NotImplementedException" sering menunjukkan kesalahan kelalaian, sedangkan "NotSupportedException" menunjukkan pilihan terbuka. Selain itu, saya setuju.
Anthony Pegram

Penting untuk dicatat bahwa banyak metode didefinisikan dalam istilah "Lakukan apa pun yang diperlukan untuk memenuhi kewajiban apa pun yang terkait dengan X". Meminta metode seperti itu pada objek yang tidak memiliki item yang terkait dengan X mungkin tidak ada gunanya, tetapi masih akan didefinisikan dengan baik. Lebih lanjut, jika suatu objek mungkin atau mungkin tidak memiliki kewajiban apa pun yang terkait dengan X, umumnya lebih bersih dan lebih efisien jika tanpa syarat mengatakan "Puaskan semua kewajiban Anda terkait dengan X", daripada bertanya terlebih dahulu apakah memiliki kewajiban dan secara kondisional memintanya untuk memuaskan mereka. .
supercat

1

Saya menyarankan agar Anda mempertimbangkan kembali memiliki antarmuka terpisah yang didefinisikan yang mengimplementasikan kelas dasar Anda, dan kemudian Anda mengikuti pendekatan abstrak.

Kode gambar seperti ini:

public interface IVersion
{
    ReturnType Method1();        
    ReturnType Method2();
             .
             .
    ReturnType MethodN();
}

public abstract class AbstractVersion : IVersion
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

Melakukan ini menyelesaikan masalah ini:

  1. Dengan memiliki semua kode yang menggunakan objek yang berasal dari AbstractVersion sekarang dapat diimplementasikan untuk menerima antarmuka IVersion, Ini berarti bahwa mereka dapat lebih mudah diuji unit.

  2. Rilis 2 produk Anda kemudian dapat mengimplementasikan antarmuka IVersion2 untuk memberikan fungsionalitas tambahan tanpa melanggar kode pelanggan yang ada.

misalnya.

public interface IVersion
{
    ReturnType Method1();        
    ReturnType Method2();
             .
             .
    ReturnType MethodN();
}

public interface IVersion2
{
    ReturnType Method2_1();
}

public abstract class AbstractVersion : IVersion, IVersion2
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();
    public abstract ReturnType Method2_1();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

Juga patut dibaca tentang inversi dependensi, untuk mencegah kelas ini mengandung dependensi kode keras yang mencegah pengujian unit yang efektif.


Saya memilih untuk menyediakan kemampuan untuk menangani versi yang berbeda. Namun, saya telah mencoba dan mencoba lagi untuk menggunakan kelas antarmuka secara efektif, sebagai bagian dari desain normal dan selalu akhirnya menyadari bahwa kelas antarmuka tidak memberikan nilai / sedikit dan malah mengaburkan kode daripada membuat hidup lebih mudah. Sangat jarang bahwa saya akan memiliki beberapa kelas mewarisi dari yang lain, seperti kelas antarmuka, dan tidak ada cukup banyak kesamaan yang tidak dapat dibagikan. Kelas abstrak cenderung bekerja lebih baik karena saya mendapatkan aspek yang dapat dibagikan yang tidak disediakan oleh antarmuka.
Dunk

Dan menggunakan pewarisan dengan nama-nama kelas yang baik memberikan cara yang jauh lebih intuitif untuk dengan mudah memahami kelas (dan dengan demikian sistem) daripada sekelompok nama kelas antarmuka fungsional yang sulit untuk disatukan secara mental secara keseluruhan. Selain itu, menggunakan kelas antarmuka cenderung membuat banyak kelas tambahan, membuat sistem lebih sulit untuk dipahami dengan cara lain.
Dunk

-2

Ketergantungan injeksi bergantung pada antarmuka. Inilah contoh singkat. Siswa Kelas memiliki fungsi yang disebut CreateStudent yang memerlukan parameter yang mengimplementasikan antarmuka "IReporting" (dengan metode ReportAction). Setelah membuat siswa, ia memanggil ReportAction pada parameter kelas beton. Jika sistem diatur untuk mengirim email setelah membuat siswa, kami mengirim kelas konkret yang mengirim email dalam implementasi ReportAction-nya, atau kami bisa mengirim kelas konkret lain yang mengirimkan output ke printer dalam implementasi ReportAction-nya. Bagus untuk digunakan kembali kode.


1
ini tampaknya tidak menawarkan sesuatu yang substansial atas poin yang dibuat dan dijelaskan dalam 5 jawaban sebelumnya
agas
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.