Gunakan kelas abstrak dalam C # sebagai definisi

21

Sebagai seorang pengembang C ++ saya cukup terbiasa dengan file header C ++, dan merasa bermanfaat memiliki semacam "dokumentasi" paksa di dalam kode. Saya biasanya memiliki waktu yang buruk ketika saya harus membaca beberapa kode C # karena itu: Saya tidak memiliki semacam peta mental dari kelas yang saya kerjakan.

Mari kita asumsikan bahwa sebagai insinyur perangkat lunak saya merancang kerangka kerja program. Apakah terlalu gila untuk mendefinisikan setiap kelas sebagai kelas abstrak yang tidak diterapkan, mirip dengan apa yang akan kita lakukan dengan header C ++, dan biarkan pengembang mengimplementasikannya?

Saya menduga mungkin ada beberapa alasan mengapa seseorang bisa menemukan ini menjadi solusi yang mengerikan tetapi saya tidak yakin mengapa. Apa yang harus dipertimbangkan seseorang untuk solusi seperti ini?

Dani Barca Casafont
sumber
13
C # adalah bahasa pemrograman yang sama sekali berbeda dari C ++. Beberapa sintaks terlihat mirip, tetapi Anda perlu memahami C # untuk melakukan pekerjaan yang baik di dalamnya. Dan Anda harus melakukan sesuatu tentang kebiasaan buruk yang Anda miliki dengan mengandalkan file header. Saya telah bekerja dengan C ++ selama beberapa dekade, saya tidak pernah membaca file header, hanya menulisnya.
Bent
17
Salah satu hal terbaik dari C # dan Java adalah kebebasan dari file header! Cukup gunakan IDE yang bagus.
Erik Eidt
9
Deklarasi dalam file header C ++ tidak berakhir menjadi bagian dari biner yang dihasilkan. Mereka ada untuk kompiler dan tautan. Kelas-kelas abstrak C # ini akan menjadi bagian dari kode yang dihasilkan, tanpa manfaat apa pun.
Mark Benningfield
16
Konstruk yang setara dalam C # adalah antarmuka, bukan kelas abstrak.
Robert Harvey
6
@DaniBarcaCasafont "Saya melihat header sebagai cara yang cepat dan andal untuk melihat metode dan atribut kelas apa, tipe parameter apa yang diharapkannya dan apa yang dikembalikan". Saat menggunakan Visual Studio alternatif saya untuk itu adalah pintasan Ctrl-MO.
Kapan pun

Jawaban:

44

Alasan ini dilakukan di C ++ berkaitan dengan membuat kompiler lebih cepat dan lebih mudah diimplementasikan. Ini bukan desain untuk membuat pemrograman lebih mudah.

Tujuan dari file header adalah untuk memungkinkan kompiler melakukan pass cepat super cepat untuk mengetahui semua nama fungsi yang diharapkan dan mengalokasikan lokasi memori untuk mereka sehingga mereka dapat direferensikan ketika dipanggil dalam file cp, bahkan jika kelas yang mendefinisikannya memiliki belum diuraikan.

Mencoba mereplikasi konsekuensi dari batasan perangkat keras lama di lingkungan pengembangan modern tidak disarankan!

Menentukan antarmuka atau kelas abstrak untuk setiap kelas akan menurunkan produktivitas Anda; apa lagi yang bisa Anda lakukan dengan waktu itu ? Juga, pengembang lain tidak akan mengikuti konvensi ini.

Bahkan, pengembang lain mungkin menghapus kelas abstrak Anda. Jika saya menemukan antarmuka dalam kode yang memenuhi kedua kriteria ini, saya menghapusnya dan membuatnya keluar dari kode:1. Does not conform to the interface segregation principle 2. Only has one class that inherits from it

Yang lain adalah, ada alat yang disertakan dengan Visual Studio yang melakukan apa yang ingin Anda capai secara otomatis:

  1. Itu Class View
  2. Itu Object Browser
  3. Di Solution ExplorerAnda dapat mengklik pada segitiga untuk mengeluarkan kelas untuk melihat fungsi, parameter, dan jenis kembali.
  4. Ada tiga menu drop-down kecil di bawah tab file, yang paling benar mencantumkan semua anggota kelas saat ini.

Cobalah salah satu di atas sebelum mencurahkan waktu untuk mereplikasi file header C ++ di C #.


Selain itu, ada alasan teknis untuk tidak melakukan ini ... itu akan membuat biner akhir Anda lebih besar dari yang seharusnya. Saya akan mengulangi komentar ini oleh Mark Benningfield:

Deklarasi dalam file header C ++ tidak berakhir menjadi bagian dari biner yang dihasilkan. Mereka ada untuk kompiler dan tautan. Kelas-kelas abstrak C # ini akan menjadi bagian dari kode yang dihasilkan, tanpa manfaat apa pun.


Juga, disebutkan oleh Robert Harvey, secara teknis, padanan terdekat dari header di C # adalah antarmuka, bukan kelas abstrak.

TheCatWhisperer
sumber
2
Anda juga akan berakhir dengan banyak panggilan pengiriman virtual yang tidak perlu (tidak baik jika kode Anda peka terhadap kinerja).
Lucas Trzesniewski
5
inilah Prinsip Segregasi Antarmuka yang dimaksud.
NH.
2
Satu juga mungkin hanya runtuh seluruh kelas (klik kanan -> Menguraikan -> Ciutkan ke Definisi) untuk mendapatkan pandangan sekilas tentang itu.
jpmc26
"apa lagi yang bisa kamu lakukan dengan waktu itu" -> Uhm, copy dan paste dan postwork yang sangat kecil? Butuh waktu sekitar 3 detik, jika sama sekali. Tentu saja, saya bisa menggunakan waktu itu untuk mengambil tip filter sebagai persiapan untuk menggulung rokok. Bukan poin yang relevan. [Penafian: Saya suka keduanya, C # idiomatik, dan juga C ++ idiomatik, dan beberapa bahasa lainnya]
phresnel
1
@phrnelnel: Saya ingin melihat Anda mencoba dan mengulangi definisi kelas dan mewarisi kelas asli dari yang abstrak dalam 3s, dikonfirmasi tanpa kesalahan, untuk setiap kelas yang cukup besar untuk tidak memiliki pandangan mata yang mudah atas itu (karena itu adalah kasus penggunaan yang diusulkan OP: seharusnya tidak rumit kelas rumit). Hampir secara inheren, sifat rumit dari kelas asli berarti bahwa Anda tidak bisa hanya menulis definisi kelas abstrak dengan hati (karena itu berarti Anda sudah mengetahuinya dengan hati) Dan kemudian pertimbangkan waktu yang diperlukan untuk melakukan ini untuk seluruh basis kode.
Flater
14

Pertama, pahami bahwa kelas yang benar-benar abstrak hanyalah antarmuka yang tidak dapat melakukan banyak pewarisan.

Kelas tulis, ekstrak antarmuka, adalah aktivitas yang mematikan otak. Sedemikian rupa sehingga kita memiliki refactoring untuk itu. Sangat disayangkan. Mengikuti pola "setiap kelas mendapat antarmuka" ini tidak hanya menghasilkan kekacauan tetapi juga melewatkan intinya.

Antarmuka tidak boleh dianggap hanya sebagai pernyataan resmi apa pun yang dapat dilakukan oleh kelas. Antarmuka harus dianggap sebagai kontrak yang dipaksakan oleh kode klien menggunakan merinci kebutuhan itu.

Saya sama sekali tidak kesulitan menulis antarmuka yang saat ini hanya memiliki satu kelas yang mengimplementasikannya. Saya sebenarnya tidak peduli jika tidak ada kelas yang mengimplementasikannya. Karena saya sedang memikirkan apa yang saya perlukan menggunakan kode. Antarmuka mengungkapkan apa yang diminta menggunakan kode. Apa pun yang terjadi kemudian dapat melakukan apa yang disukainya asalkan memenuhi harapan-harapan ini.

Sekarang saya tidak melakukan ini setiap kali satu objek menggunakan yang lain. Saya melakukan ini ketika melewati batas. Saya melakukannya ketika saya tidak ingin satu objek tahu persis objek lain yang diajak bicara. Yang merupakan satu-satunya cara polimorfisme akan bekerja. Saya melakukannya ketika saya mengharapkan objek yang kode klien saya bicarakan cenderung berubah. Saya tentu tidak melakukan ini ketika apa yang saya gunakan adalah kelas String. Kelas String bagus dan stabil dan saya merasa tidak perlu berjaga-jaga agar tidak berubah pada saya.

Ketika Anda memutuskan untuk berinteraksi langsung dengan implementasi konkret daripada melalui abstraksi, Anda memperkirakan bahwa implementasinya cukup stabil untuk memercayai untuk tidak berubah.

Bahwa di situlah cara saya meredam Prinsip Ketergantungan Pembalikan . Anda seharusnya tidak secara fanatik menerapkan ini pada segalanya. Ketika Anda menambahkan abstraksi Anda benar-benar mengatakan Anda tidak percaya pilihan menerapkan kelas menjadi stabil selama umur proyek.

Ini semua mengasumsikan Anda mencoba mengikuti Prinsip Terbuka Tertutup . Prinsip ini penting hanya ketika biaya yang terkait dengan membuat perubahan langsung ke kode yang ditetapkan adalah signifikan. Salah satu alasan utama orang tidak setuju tentang betapa pentingnya objek decoupling adalah karena tidak semua orang mengalami biaya yang sama ketika melakukan perubahan langsung. Jika pengujian ulang, kompilasi ulang, dan redistribusi seluruh basis kode Anda sepele bagi Anda, maka menyelesaikan kebutuhan untuk perubahan dengan modifikasi langsung kemungkinan merupakan penyederhanaan yang sangat menarik dari masalah ini.

Tidak ada jawaban yang mati otak untuk pertanyaan ini. Antarmuka atau kelas abstrak bukanlah sesuatu yang harus Anda tambahkan ke setiap kelas dan Anda tidak bisa hanya menghitung jumlah kelas implementasi dan memutuskan itu tidak diperlukan. Ini tentang berurusan dengan perubahan. Yang berarti Anda mengantisipasi masa depan. Jangan kaget jika Anda salah. Buat sesederhana mungkin tanpa menyudutkan diri.

Jadi tolong, jangan menulis abstraksi hanya untuk membantu kami membaca kode. Kami punya alat untuk itu. Gunakan abstraksi untuk memisahkan apa yang perlu dipisahkan.

candied_orange
sumber
5

Ya itu akan mengerikan karena (1) Ini memperkenalkan kode yang tidak perlu (2) Ini akan membingungkan pembaca.

Jika Anda ingin memprogram dalam C # Anda harus membiasakan diri membaca C #. Kode yang ditulis oleh orang lain tidak akan mengikuti pola ini.

JacquesB
sumber
1

Tes unit

Saya sangat menyarankan agar Anda menulis tes Unit daripada mengacaukan kode dengan antarmuka atau kelas abstrak (kecuali jika diperlukan karena alasan lain).

Tes unit yang ditulis dengan baik tidak hanya menjelaskan antarmuka kelas Anda (seperti file header, kelas abstrak atau antarmuka yang akan dilakukan) tetapi juga menjelaskan, dengan contoh, fungsi yang diinginkan.

Contoh: Di mana Anda mungkin telah menulis file header myclass.h seperti ini:

class MyClass
{
public:
  void foo();
};

Sebaliknya, dalam c # tulis tes seperti ini:

[TestClass]
public class MyClassTests
{
    [TestMethod]
    public void MyClass_should_have_method_Foo()
    {
        //Arrange
        var myClass = new MyClass();
        //Act
        myClass.Foo();
        //Verify
        Assert.Inconclusive("TODO: Write a more detailed test");
    }
}

Tes yang sangat sederhana ini menyampaikan informasi yang sama dengan file header. (Kita harus memiliki kelas bernama "MyClass" dengan fungsi tanpa parameter "Foo") Sementara file header lebih kompak, tes berisi informasi yang jauh lebih banyak.

Peringatan: suatu proses memiliki insinyur senior yang memasok (gagal) tes untuk pengembang lain untuk memecahkan bentrokan dengan metodologi seperti TDD, tetapi dalam kasus Anda itu akan menjadi peningkatan besar.

Guran
sumber
Bagaimana Anda melakukan Unit Testing (tes terisolasi) = Mocks diperlukan, tanpa antarmuka? Kerangka apa yang mendukung mengejek dari implementasi aktual, sebagian besar saya telah melihat menggunakan antarmuka untuk menukar implementasi dengan implementasi tiruan.
Bulan
Saya tidak berpikir mengejek diperlukan untuk keperluan OP: s. Ingat, ini bukan unit test sebagai alat untuk TDD, tetapi unit test sebagai pengganti file header. (Mereka tentu saja berkembang menjadi unit test "biasa" dengan mengolok-olok dll)
Guran
Ok, jika saya hanya mempertimbangkan sisi OP saja, saya lebih mengerti. Baca jawaban Anda sebagai jawaban penerapan yang lebih umum. Terima kasih untuk klarifikasi!
Bulan
@Bulan, kebutuhan untuk cemoohan yang ekstensif sering kali mengindikasikan desain yang buruk
TheCatWhisperer
@TheCatWhisperer Ya, saya sangat sadar akan hal itu, jadi tidak ada argumen di sana, tidak dapat melihat bahwa saya membuat pernyataan itu di mana pun juga: DI sedang berbicara tentang pengujian secara umum, bahwa semua kerangka kerja Mock yang saya gunakan menggunakan Antarmuka untuk bertukar keluar implementasi yang sebenarnya, dan jika ada beberapa teknik lain tentang bagaimana Anda akan mengejek jika Anda tidak memiliki antarmuka.
Bulan