Subkelas khusus konstruktor: Apakah ini anti-pola?

37

Saya sedang berdiskusi dengan rekan kerja, dan kami akhirnya memiliki intuisi yang bertentangan tentang tujuan subklasifikasi. Intuisi saya adalah bahwa jika fungsi utama dari sebuah subclass adalah untuk mengekspresikan kisaran terbatas dari nilai yang mungkin dari induknya, maka itu mungkin seharusnya tidak menjadi sebuah subclass. Dia berpendapat untuk intuisi yang berlawanan: bahwa subclass mewakili objek yang lebih "spesifik", dan karena itu hubungan subclass lebih tepat.

Untuk menempatkan intuisi saya lebih konkret, saya berpikir bahwa jika saya memiliki subclass yang memperpanjang kelas induk tetapi satu-satunya kode yang menimpa subclass adalah konstruktor (ya, saya tahu konstruktor umumnya tidak "menimpa", bersabarlah), lalu yang benar-benar dibutuhkan adalah metode pembantu.

Sebagai contoh, pertimbangkan kelas kehidupan nyata ini:

public class DataHelperBuilder
{
    public string DatabaseEngine { get; set; }
    public string ConnectionString { get; set; }

    public DataHelperBuilder(string databaseEngine, string connectionString)
    {
        DatabaseEngine = databaseEngine;
        ConnectionString = connectionString;
    }

    // Other optional "DataHelper" configuration settings omitted

    public DataHelper CreateDataHelper()
    {
        Type dataHelperType = DatabaseEngineTypeHelper.GetType(DatabaseEngine);
        DataHelper dh = (DataHelper)Activator.CreateInstance(dataHelperType);
        dh.SetConnectionString(ConnectionString);

        // Omitted some code that applies decorators to the returned object
        // based on omitted configuration settings

        return dh;
    }
}

Klaimnya adalah bahwa sepenuhnya pantas untuk memiliki subkelas seperti ini:

public class SystemDataHelperBuilder
{
    public SystemDataHelperBuilder()
        : base(Configuration.GetSystemDatabaseEngine(),
               Configuration.GetSystemConnectionString())
    {
    }
 }

Jadi, pertanyaannya:

  1. Di antara orang yang berbicara tentang pola desain, intuisi mana yang benar? Apakah subkelas seperti yang dijelaskan di atas merupakan anti-pola?
  2. Jika itu anti-pola, apa namanya?

Saya minta maaf jika ini ternyata merupakan jawaban yang mudah di-googleable; pencarian saya di google sebagian besar mengembalikan informasi tentang anti-pola konstruktor telescoping dan tidak benar-benar apa yang saya cari.

HardlyKnowEm
sumber
2
Saya menganggap kata "Helper" dalam sebuah nama sebagai antipattern. Ini seperti membaca esai dengan referensi konstan untuk sesuatu dan whatsit. Mengatakan apa itu adalah . Apakah ini pabrik? Konstruktor? Bagi saya itu menampar proses berpikir malas, dan mendorong pelanggaran prinsip tanggung jawab tunggal, karena nama semantik yang tepat akan mengarahkannya. Saya setuju dengan pemilih lain, dan Anda harus menerima jawaban @ lxrec. Membuat subkelas untuk metode pabrik sama sekali tidak ada gunanya, kecuali Anda tidak dapat mempercayai pengguna untuk memberikan argumen yang benar seperti yang ditentukan dalam dokumentasi. Dalam hal ini, dapatkan pengguna baru.
Aaron Hall
Saya sangat setuju dengan jawaban @ Ixrec. Bagaimanapun, jawabannya mengatakan bahwa saya benar selama ini dan rekan kerja saya salah. ;-) Tapi serius, itu sebabnya aku merasa aneh menerimanya; Saya pikir saya bisa dituduh bias. Tapi saya baru mengenal programmer StackExchange; Saya bersedia menerimanya jika saya yakin itu bukan pelanggaran etiket.
HardlyKnowEm
1
Tidak, diharapkan Anda menerima jawaban yang menurut Anda paling berharga. Yang menurut komunitas paling berharga sejauh ini adalah Ixrec sejauh ini lebih dari 3 banding 1. Dengan demikian mereka berharap Anda menerimanya. Dalam komunitas ini, ada sangat sedikit frustrasi yang diungkapkan dalam seorang penanya menerima jawaban yang salah, tetapi ada banyak frustrasi yang diungkapkan dalam penanya yang tidak pernah menerima jawaban. Perhatikan bahwa tidak ada yang mengeluh tentang jawaban yang diterima di sini, sebaliknya mereka mengeluh tentang pemungutan suara, yang tampaknya benar dengan sendirinya: stackoverflow.com/q/2052390/541136
Aaron Hall
Baiklah, saya akan menerimanya. Terima kasih telah meluangkan waktu untuk mendidik saya tentang standar komunitas di sini.
HardlyKnowEm

Jawaban:

54

Jika semua yang ingin Anda lakukan adalah membuat kelas X dengan argumen tertentu, subklasifikasi adalah cara yang aneh untuk mengekspresikan maksud itu, karena Anda tidak menggunakan fitur apa pun yang diberikan oleh kelas dan pewarisan. Ini bukan benar-benar anti-pola, itu hanya aneh dan sedikit sia-sia (kecuali Anda punya alasan lain untuk itu). Cara yang lebih alami untuk mengungkapkan niat ini adalah Metode Pabrik , yang dalam hal ini adalah nama yang bagus untuk "metode penolong" Anda.

Mengenai intuisi umum, baik "lebih spesifik" dan "rentang terbatas" adalah cara berpikir yang berpotensi berbahaya tentang subkelas, karena keduanya menyiratkan bahwa menjadikan Square sebagai subkelas Persegi Panjang adalah ide yang bagus. Tanpa mengandalkan sesuatu yang formal seperti LSP, saya akan mengatakan intuisi yang lebih baik adalah bahwa subkelas menyediakan implementasi antarmuka dasar, atau memperluas antarmuka untuk menambahkan beberapa fungsi baru.

Ixrec
sumber
2
Metode Pabrik tidak membiarkan Anda menerapkan perilaku tertentu (diatur oleh argumen) dalam basis kode Anda. Dan kadang-kadang berguna untuk menjelaskan secara eksplisit bahwa kelas / metode / bidang ini mengambil SystemDataHelperBuilderkhusus.
Telastyn
3
Ah, argumen persegi vs persegi panjang adalah apa yang saya cari, saya pikir. Terima kasih!
HardlyKnowEm
7
Tentu saja, memiliki persegi menjadi subkelas persegi panjang bisa baik-baik saja jika mereka tidak berubah. Anda benar-benar harus melihat ke LSP.
David Conrad
10
Hal tentang "masalah" kuadrat / persegi panjang adalah bahwa bentuk geometris tidak dapat diubah. Anda selalu dapat menggunakan kotak di mana persegi panjang masuk akal, tetapi mengubah ukuran bukanlah sesuatu yang persegi atau persegi panjang lakukan.
Doval
3
@ nha LSP = Prinsip Substitusi Liskov
Ixrec
16

Manakah dari intuisi ini yang benar?

Rekan kerja Anda benar (dengan asumsi sistem tipe standar).

Pikirkan tentang itu, kelas mewakili nilai-nilai hukum yang memungkinkan. Jika kelas Amemiliki bidang satu byte F, Anda mungkin cenderung berpikir yang Amemiliki 256 nilai hukum, tetapi intuisi itu salah. Amembatasi "setiap permutasi nilai yang pernah" ke "harus memiliki bidang F0-255".

Jika Anda memperpanjang Adengan Byang memiliki bidang byte lain FF, itu menambahkan batasan . Alih-alih "nilai di mana Fbyte", Anda memiliki "nilai di mana Fbyte && FFjuga byte". Karena Anda mematuhi aturan lama, semua yang bekerja untuk tipe dasar masih berfungsi untuk subtipe Anda. Tetapi karena Anda menambahkan aturan, Anda lebih jauh mengkhususkan diri dari "bisa apa saja".

Atau memikirkan cara lain, menganggap bahwa Amemiliki banyak subtipe: B, C, dan D. Variabel tipe Adapat berupa subtipe ini, tetapi variabel tipe Blebih spesifik. Ini (dalam kebanyakan sistem tipe) tidak boleh a Catau a D.

Apakah ini anti-pola?

Tingkatkan? Memiliki objek khusus berguna dan tidak jelas merusak kode. Mencapai hasil itu dengan menggunakan subtyping mungkin sedikit dipertanyakan, tetapi tergantung pada alat apa yang Anda miliki. Bahkan dengan alat yang lebih baik, implementasi ini sederhana, mudah dan kuat.

Saya tidak akan menganggap ini sebagai anti-pola karena tidak jelas salah dan buruk dalam situasi apa pun.

Telastyn
sumber
5
"Lebih banyak aturan berarti lebih sedikit nilai yang mungkin, berarti lebih spesifik." Saya benar-benar tidak mengikuti ini. Tipe byte and bytepasti memiliki nilai lebih dari sekadar tipe byte; 256 kali lebih banyak. Selain itu, saya tidak berpikir Anda bisa mengacaukan aturan dengan sejumlah elemen (atau kardinalitas jika Anda ingin lebih tepatnya). Itu rusak ketika Anda mempertimbangkan set tak terbatas. Ada bilangan genap sama banyaknya dengan bilangan bulat, tetapi mengetahui bahwa suatu bilangan bahkan masih merupakan batasan / memberi saya lebih banyak informasi daripada hanya mengetahui bilangan bulat! Hal yang sama bisa dikatakan untuk bilangan asli.
Doval
6
@Telastyn Kita keluar dari topik, tetapi dapat dibuktikan bahwa kardinalitas (longgar, "ukuran") dari himpunan bilangan bulat genap sama dengan himpunan semua bilangan bulat, atau bahkan semua bilangan rasional. Ini hal yang sangat keren. Lihat math.grinnell.edu/~miletijo/museum/infinite.html
HardlyKnowEm
2
Teori himpunan sangat tidak intuitif. Anda dapat meletakkan bilangan bulat dan rata dalam korespondensi satu-ke-satu (mis. Menggunakan fungsi n -> 2n). Ini tidak selalu mungkin; Anda tidak dapat memetakan bilangan bulat ke real, jadi set real "lebih besar" daripada bilangan bulat. Tetapi Anda dapat memetakan interval (nyata) [0, 1] ke himpunan semua real, sehingga menjadikannya "ukuran" yang sama. Himpunan bagian didefinisikan sebagai "setiap elemen Ajuga merupakan elemen B" justru karena begitu set Anda menjadi tak terbatas, dapat menjadi kasus bahwa mereka kardinalitas yang sama tetapi memiliki elemen yang berbeda.
Doval
1
Lebih terkait dengan topik yang dihadapi, contoh yang Anda berikan adalah kendala bahwa, dalam hal pemrograman berorientasi objek, sebenarnya meluas -fungsionalitas- dalam hal bidang tambahan. Saya berbicara tentang subclass yang tidak memperluas fungsionalitas - seperti masalah lingkaran-elips.
HardlyKnowEm
1
@Doval: Ada lebih dari satu arti yang mungkin dari "lebih sedikit" - yaitu lebih dari satu relasi pemesanan pada set. Relasi "is a subset of" memberikan urutan (sebagian) yang didefinisikan dengan baik pada set. Selain itu, "lebih banyak aturan" dapat diartikan sebagai "semua elemen set memuaskan lebih banyak (yaitu superset) proposisi (dari set tertentu)". Dengan definisi ini, "lebih banyak aturan" memang berarti "nilai lebih sedikit". Ini, secara kasar, pendekatan yang diambil oleh sejumlah model semantik program komputer, misalnya domain Scott.
psmears
12

Tidak, ini bukan anti-pola. Saya dapat memikirkan sejumlah kasus penggunaan praktis untuk ini:

  1. Jika Anda ingin menggunakan kompilasi memeriksa waktu untuk memastikan koleksi objek hanya sesuai dengan satu subkelas tertentu. Sebagai contoh, jika Anda memiliki MySQLDaodan SqliteDaodi sistem Anda, tetapi karena alasan tertentu Anda ingin memastikan kumpulan hanya berisi data dari satu sumber, jika Anda menggunakan subkelas seperti yang Anda jelaskan, Anda dapat meminta kompilator memverifikasi kebenaran khusus ini. Di sisi lain, jika Anda menyimpan sumber data sebagai bidang, ini akan menjadi pemeriksaan run-time.
  2. Aplikasi saya saat ini memiliki hubungan satu ke satu antara AlgorithmConfiguration+ ProductClassdan AlgorithmInstance. Dengan kata lain, jika Anda mengonfigurasi FooAlgorithmuntuk ProductClassdi file properti, Anda hanya bisa mendapatkan satu FooAlgorithmuntuk kelas produk itu. Satu-satunya cara untuk mendapatkan dua FooAlgorithms untuk diberikan ProductClassadalah dengan membuat subclass FooBarAlgorithm. Ini tidak apa-apa, karena itu membuat file properti mudah digunakan (tidak perlu sintaks untuk banyak konfigurasi berbeda dalam satu bagian dalam file properti), dan sangat jarang akan ada lebih dari satu contoh untuk kelas produk yang diberikan.
  3. Jawaban @ Telastyn memberikan contoh yang baik.

Intinya: Tidak ada salahnya melakukan hal ini. Sebuah Anti-pola didefinisikan sebagai:

Anti-pola (atau antipattern) adalah respons umum terhadap masalah berulang yang biasanya tidak efektif dan berisiko sangat kontraproduktif.

Tidak ada risiko menjadi sangat kontraproduktif di sini, jadi ini bukan anti-pola.

durron597
sumber
2
Ya, saya pikir jika saya harus mengulang pertanyaan itu lagi, saya akan mengatakan "kode bau." Itu tidak cukup buruk untuk menjamin peringatan generasi muda pengembang berikutnya untuk menghindarinya, tetapi itu adalah sesuatu yang jika Anda melihatnya, itu mungkin menunjukkan bahwa ada masalah dalam desain menyeluruh, tetapi pada akhirnya mungkin juga benar.
HardlyKnowEm
Poin 1 tepat sasaran. Saya tidak begitu mengerti poin 2, tapi saya yakin itu sama baiknya ... :)
Phil
9

Jika sesuatu adalah pola atau antipattern sangat tergantung pada bahasa dan lingkungan tempat Anda menulis. Misalnya, forloop adalah pola dalam assembly, C, dan bahasa yang serupa dan anti-pola dalam lisp.

Izinkan saya menceritakan kisah beberapa kode yang saya tulis dulu ...

Itu dalam bahasa yang disebut LPC dan menerapkan kerangka kerja untuk casting mantra dalam game. Anda memiliki superclass mantra, yang memiliki subkelas mantra tempur yang menangani beberapa pemeriksaan, dan kemudian subkelas untuk mantra kerusakan target langsung tunggal, yang kemudian disubklasifikasikan ke mantra individu - misil sihir, baut petir dan sejenisnya.

Sifat bagaimana LPC bekerja adalah bahwa Anda memiliki objek master yang merupakan Singleton. sebelum Anda pergi 'eww Singleton' - dulu dan itu bukan "eww". Seseorang tidak membuat salinan mantra, tetapi lebih memilih metode statis (efektif) dalam mantra. Kode untuk rudal ajaib tampak seperti:

inherit "spells/direct";

void init() {
  ::init();
  damage = 10;
  cost = 3;
  delay = 1.0;
  caster_message = "You cast a magic missile at ${target}";
  target_message = "You are hit with a magic missile cast by ${caster}";
}

Dan kamu lihat itu? Kelasnya hanya konstruktor. Ini menetapkan beberapa nilai yang ada di kelas abstrak induknya (tidak, itu tidak boleh menggunakan setter - itu tidak berubah) dan hanya itu. Itu bekerja dengan baik . Itu mudah dikodekan, mudah diperluas dan mudah dimengerti.

Pola semacam ini diterjemahkan ke situasi lain di mana Anda memiliki subkelas yang memperluas kelas abstrak dan menetapkan beberapa nilai sendiri, tetapi semua fungsionalitas berada dalam kelas abstrak yang diwarisi. Melirik StringBuilder di Jawa untuk contoh ini - tidak cukup hanya konstruktor, tapi saya ditekan untuk menemukan banyak logika dalam metode itu sendiri (semuanya ada di AbstractStringBuilder).

Ini bukan hal yang buruk, dan tentu saja bukan anti-pola (dan dalam beberapa bahasa itu mungkin pola itu sendiri).

Komunitas
sumber
2

Penggunaan subclass yang paling umum yang dapat saya pikirkan adalah hierarki pengecualian, meskipun itu adalah kasus yang merosot di mana kita biasanya tidak mendefinisikan apa pun di dalam kelas, selama bahasanya memungkinkan kita mewarisi konstruktor. Kami menggunakan warisan untuk menyatakan bahwa ReadErrorini adalah kasus khusus IOError, dan seterusnya, tetapi ReadErrortidak perlu mengganti metode apa pun IOError.

Kami melakukan ini karena pengecualian ditangkap dengan memeriksa tipenya. Jadi, kita perlu menyandikan spesialisasi dalam jenis sehingga seseorang dapat menangkap hanya ReadErrortanpa menangkap semua IOError, jika mereka mau.

Biasanya, memeriksa jenis barang adalah bentuk yang sangat buruk dan kami berusaha menghindarinya. Jika kita berhasil menghindarinya, maka tidak ada kebutuhan penting untuk mengekspresikan spesialisasi dalam sistem tipe.

Ini mungkin masih berguna, misalnya jika nama tipe muncul dalam bentuk string dari objek: ini adalah bahasa dan mungkin kerangka spesifik. Pada prinsipnya Anda dapat mengganti nama tipe dalam sistem seperti itu dengan panggilan ke metode kelas, dalam hal ini kami akan menimpa lebih dari sekadar konstruktor SystemDataHelperBuilder, kami juga akan menimpanya loggingname(). Jadi, jika ada pencatatan seperti itu di sistem Anda, Anda dapat membayangkan bahwa meskipun kelas Anda secara harfiah hanya mengabaikan konstruktor, "secara moral" juga menimpa nama kelas, dan untuk tujuan praktis, bukan subkelas konstruktor saja. Ia memiliki perilaku berbeda yang diinginkan, itu

Jadi, saya akan mengatakan bahwa kodenya dapat menjadi ide yang bagus jika ada beberapa kode di tempat lain yang entah bagaimana memeriksa contoh SystemDataHelperBuilder, baik secara eksplisit dengan cabang (seperti pengecualian menangkap cabang berdasarkan jenis) atau mungkin karena ada beberapa kode generik yang akan berakhir menggunakan nama SystemDataHelperBuilderdengan cara yang bermanfaat (bahkan jika itu hanya logging).

Namun, menggunakan jenis untuk kasus khusus memang menimbulkan kebingungan, karena jika ImmutableRectangle(1,1)dan ImmutableSquare(1)berperilaku berbeda dengan cara apa pun maka pada akhirnya seseorang akan bertanya-tanya mengapa dan mungkin berharap tidak. Tidak seperti hierarki pengecualian, Anda memeriksa apakah persegi panjang adalah persegi dengan memeriksa apakah height == width, tidak dengan instanceof. Dalam contoh Anda, ada perbedaan antara a SystemDataHelperBuilderdan a yang DataHelperBuilderdibuat dengan argumen yang sama persis, dan yang berpotensi menyebabkan masalah. Oleh karena itu, fungsi untuk mengembalikan objek yang dibuat dengan argumen "benar" menurut saya biasanya merupakan hal yang benar untuk dilakukan: subkelas menghasilkan dua representasi berbeda dari hal "yang sama", dan itu hanya membantu jika Anda melakukan sesuatu yang Anda biasanya akan berusaha untuk tidak melakukannya.

Perhatikan bahwa saya tidak berbicara dalam hal pola desain dan anti-pola, karena saya tidak percaya bahwa itu mungkin untuk memberikan taksonomi semua ide baik dan buruk yang pernah dipikirkan oleh seorang programmer. Jadi, tidak setiap ide adalah pola atau anti-pola, hanya ketika seseorang mengidentifikasi bahwa itu terjadi berulang kali dalam konteks yang berbeda dan menamainya ;-) Saya tidak mengetahui adanya nama khusus untuk jenis subklasifikasi ini.

Steve Jessop
sumber
Saya bisa melihat kegunaannya ImmutableSquareMatrix:ImmutableMatrix, asalkan ada cara yang efisien untuk meminta suatu ImmutableMatrixuntuk membuat isinya ImmutableSquareMatrixjika mungkin. Sekalipun ImmutableSquareMatrixpada dasarnya seorang ImmutableMatrixyang konstruktornya mensyaratkan tinggi dan lebar yang cocok, dan yang AsSquareMatrixmetodenya hanya akan mengembalikan dirinya sendiri (daripada misalnya membangun ImmutableSquareMatrixyang membungkus susunan backing yang sama), desain semacam itu akan memungkinkan validasi parameter kompilasi-waktu untuk metode yang memerlukan kuadrat matriks
supercat
@supercat: poin bagus, dan dalam contoh pertanyaan jika ada fungsi yang harus dilewati SystemDataHelperBuilder, dan bukan sembarang yang DataHelperBuilderakan dilakukan, maka masuk akal untuk menentukan tipe untuk itu (dan antarmuka, dengan asumsi Anda menangani DI dengan cara itu) . Dalam contoh Anda, ada beberapa operasi yang bisa dimiliki oleh matriks kuadrat yang bukan kuadrat, seperti determinan, tetapi poin Anda bagus bahwa meskipun sistem tidak membutuhkan yang Anda masih ingin memeriksa berdasarkan jenis .
Steve Jessop
... jadi saya kira itu tidak sepenuhnya benar apa yang saya katakan, bahwa "Anda memeriksa apakah persegi panjang adalah persegi dengan memeriksa apakah height == width, tidak dengan instanceof". Anda mungkin lebih suka sesuatu yang bisa Anda nyatakan secara statis.
Steve Jessop
Saya berharap ada mekanisme yang akan memungkinkan sesuatu seperti sintaks konstruktor untuk memanggil metode pabrik statis. Jenis ungkapan foo=new ImmutableMatrix(someReadableMatrix)lebih jelas daripada someReadableMatrix.AsImmutable()atau bahkan ImmutableMatrix.Create(someReadableMatrix), tetapi bentuk-bentuk terakhir menawarkan kemungkinan semantik yang bermanfaat yang tidak dimiliki oleh yang pertama; jika konstruktor dapat mengatakan bahwa matriks adalah kuadrat, dapat memiliki jenisnya mencerminkan yang bisa lebih bersih daripada membutuhkan kode hilir yang membutuhkan matriks kuadrat untuk membuat instance objek baru.
supercat
@supercat: Satu hal kecil tentang Python yang saya sukai adalah tidak adanya newoperator. Kelas hanyalah callable, yang ketika dipanggil akan mengembalikan (secara default) instance dari dirinya sendiri. Jadi, jika Anda ingin mempertahankan konsistensi antarmuka sangat mungkin untuk menulis fungsi pabrik yang namanya dimulai dengan huruf kapital, karena itu terlihat seperti panggilan konstruktor. Bukannya saya melakukan ini secara rutin dengan fungsi pabrik, tetapi ada di sana saat Anda membutuhkannya.
Steve Jessop
2

Definisi Object Oriented yang paling ketat dari hubungan subclass dikenal sebagai «is-a». Menggunakan contoh tradisional persegi dan persegi panjang, persegi «adalah-» persegi panjang.

Dengan ini, Anda harus menggunakan subclassing untuk situasi apa pun di mana Anda merasa «is-a» adalah hubungan yang bermakna untuk kode Anda.

Dalam kasus spesifik Anda dari subkelas dengan apa-apa selain konstruktor, Anda harus menganalisisnya dari perspektif «is-a». Jelaslah bahwa SystemDataHelperBuilder «is-a» DataHelperBuilder, jadi pass pertama menyarankan itu adalah penggunaan yang valid.

Pertanyaan yang harus Anda jawab adalah apakah ada kode lain yang memanfaatkan hubungan «is-a» ini. Apakah ada kode lain yang mencoba membedakan SystemDataHelperBuilders dari populasi umum DataHelperBuilders? Jika demikian, hubungannya cukup masuk akal, dan Anda harus menjaga subkelasnya.

Namun, jika tidak ada kode lain yang melakukan ini, maka apa yang Anda miliki adalah hubungan yang bisa berupa hubungan «adalah-a», atau bisa diterapkan dengan pabrik. Dalam hal ini, Anda bisa memilih jalan yang lain, tetapi saya akan merekomendasikan hubungan Factory daripada hubungan «is-a». Ketika menganalisis kode Berorientasi Objek yang ditulis oleh individu lain, hierarki kelas adalah sumber utama informasi. Ini memberikan hubungan «is-a» yang mereka perlukan untuk memahami kode. Dengan demikian, menempatkan perilaku ini ke dalam subkelas mempromosikan perilaku dari "sesuatu yang akan ditemukan seseorang jika mereka menggali metode superclass" menjadi "sesuatu yang semua orang akan melihat sebelum mereka bahkan mulai menyelam ke dalam kode." Mengutip Guido van Rossum, "kode dibaca lebih sering daripada yang tertulis," jadi mengurangi keterbacaan kode Anda untuk pengembang yang akan datang adalah harga yang harus dibayar. Saya akan memilih untuk tidak membayarnya, jika saya bisa menggunakan pabrik sebagai gantinya.

Cort Ammon
sumber
1

Subtipe tidak pernah "salah" selama Anda selalu bisa mengganti instance dari supertype-nya dengan instance dari subtipe dan semuanya masih berfungsi dengan benar. Ini memeriksa selama subclass tidak pernah mencoba untuk melemahkan jaminan yang dibuat supertype. Itu bisa membuat jaminan yang lebih kuat (lebih spesifik), jadi dalam hal itu intuisi rekan kerja Anda benar.

Mengingat bahwa, subkelas yang Anda buat mungkin tidak salah (karena saya tidak tahu persis apa yang mereka lakukan, saya tidak bisa mengatakannya dengan pasti.) Anda harus waspada terhadap masalah kelas dasar yang rapuh, dan Anda juga harus pertimbangkan apakah suatu interfaceakan menguntungkan Anda, tetapi itu berlaku untuk semua penggunaan warisan.

Doval
sumber
1

Saya pikir kunci untuk menjawab pertanyaan ini adalah dengan melihat yang satu ini, skenario penggunaan tertentu.

Warisan digunakan untuk menegakkan opsi konfigurasi basis data, seperti string koneksi.

Ini adalah pola anti karena kelas melanggar Prinsip Tanggung Jawab Tunggal --- Mengkonfigurasi dirinya sendiri dengan sumber spesifik konfigurasi itu dan tidak "Melakukan satu hal, dan melakukannya dengan baik." Konfigurasi kelas tidak boleh dimasukkan ke dalam kelas itu sendiri. Untuk mengatur string koneksi database, sering kali saya melihat ini terjadi pada level aplikasi selama beberapa peristiwa terjadi ketika aplikasi dijalankan.

Greg Burghardt
sumber
1

Saya menggunakan konstruktor hanya subkelas cukup teratur untuk mengekspresikan konsep yang berbeda.

Sebagai contoh, pertimbangkan kelas berikut:

public class Outputter
{
    private readonly IOutputMethod outputMethod;
    private readonly IDataMassager dataMassager;

    public Outputter(IOutputMethod outputMethod, IDataMassager dataMassager)
    {
        this.outputMethod = outputMethod;
        this.dataMassager = dataMassager;
    }

    public MassageDataAndOutput(string data)
    {
        this.outputMethod.Output(this.dataMassager.Massage(data));
    }
}

Kami kemudian dapat membuat subkelas:

public class CapitalizeAndPrintOutputter : Outputter
{
    public CapitalizeAndPrintOutputter()
        : base(new PrintOutputMethod(), new Capitalizer())
    {
    }
}

Anda kemudian dapat menggunakan CapitalizeAndPrintOutputter di mana saja dalam kode Anda dan Anda tahu persis apa jenis outputter itu. Selain itu, pola ini sebenarnya membuatnya sangat mudah untuk menggunakan wadah DI untuk membuat kelas ini. Jika wadah DI tahu tentang PrintOutputMethod dan Capitalizer, itu bahkan dapat secara otomatis membuat CapitalizeAndPrintOutputter untuk Anda.

Menggunakan pola ini benar-benar sangat fleksibel dan bermanfaat. Ya, Anda dapat menggunakan metode pabrik untuk melakukan hal yang sama, tetapi kemudian (setidaknya dalam C #) Anda kehilangan beberapa kekuatan dari sistem tipe dan tentu saja menggunakan wadah DI menjadi lebih rumit.

Stephen
sumber
1

Pertama-tama saya ingin perhatikan bahwa ketika kode ditulis, sub-kelas sebenarnya tidak lebih spesifik daripada induknya, karena dua bidang yang diinisialisasi keduanya dapat diselesaikan dengan kode klien.

Sebuah instance dari subclass hanya dapat dibedakan menggunakan downcast.

Sekarang, jika Anda memiliki desain di mana DatabaseEnginedan ConnectionStringproperti diimplementasikan kembali oleh subclass, yang diakses Configurationuntuk melakukannya, maka Anda memiliki kendala yang lebih masuk akal memiliki subclass.

Karena itu, "anti-pola" terlalu kuat untuk seleraku; tidak ada yang salah di sini.

Keith
sumber
0

Dalam contoh yang Anda berikan, pasangan Anda benar. Dua pembangun pembantu data adalah strategi. Satu lebih spesifik daripada yang lain, tetapi keduanya dapat dipertukarkan begitu diinisialisasi.

Pada dasarnya jika klien dari datahelperbuilder adalah untuk menerima systemdatahelperbuilder, tidak ada yang akan rusak. Pilihan lain adalah memiliki systemdatahelperbuilder menjadi contoh statis dari kelas datahelperbuilder.

Michael Brown
sumber