Kapan menggunakan obat generik dalam desain antarmuka

11

Saya memiliki beberapa antarmuka yang saya maksudkan untuk diimplementasikan oleh pihak ketiga di masa depan, dan saya sendiri menyediakan basis implementasi. Saya hanya akan menggunakan pasangan untuk menunjukkan contoh.

Saat ini, mereka didefinisikan sebagai

Barang:

public interface Item {

    String getId();

    String getName();
}

ItemStack:

public interface ItemStackFactory {

    ItemStack createItemStack(Item item, int quantity);
}

ItemStackContainer:

public interface ItemStackContainer {

    default void add(ItemStack stack) {
        add(stack, 1);
    }

    void add(ItemStack stack, int quantity);
}

Sekarang, Itemdan ItemStackFactorysaya benar-benar dapat melihat beberapa pihak ketiga yang perlu memperpanjangnya di masa depan. ItemStackContainerjuga dapat diperluas di masa mendatang, tetapi tidak dengan cara yang dapat saya ramalkan, di luar implementasi default yang disediakan.

Sekarang, saya mencoba membuat perpustakaan ini sekuat mungkin; ini masih dalam tahap awal (pra-pra-alfa) sehingga ini mungkin merupakan tindakan rekayasa berlebihan (YAGNI). Apakah ini tempat yang tepat untuk menggunakan obat generik?

public interface ItemStack<T extends Item> {

    T getItem();

    int getQuantity();
}

Dan

public interface ItemStackFactory<T extends ItemStack<I extends Item>> {

    T createItemStack(I item, int quantity);
}

Saya khawatir hal ini akan membuat implementasi dan penggunaan menjadi lebih sulit untuk dibaca dan dipahami; Saya pikir itu adalah rekomendasi untuk menghindari obat generik bersarang sedapat mungkin.

Zymus
sumber
1
Agak terasa seperti Anda mengekspos detail implementasi baik cara. Mengapa penelepon perlu bekerja dan mengetahui / peduli tentang tumpukan daripada jumlah sederhana?
cHao
Itu adalah sesuatu yang tidak saya pikirkan. Itu memang memberikan beberapa wawasan yang bagus tentang masalah khusus ini. Saya akan memastikan untuk membuat perubahan pada kode saya.
Zymus

Jawaban:

9

Anda menggunakan obat generik di antarmuka Anda ketika implementasi Anda kemungkinan juga bersifat generik.

Misalnya, setiap struktur data yang dapat menerima objek arbitrer adalah kandidat yang baik untuk antarmuka generik. Contoh: List<T>dan Dictionary<K,V>.

Setiap situasi di mana Anda ingin meningkatkan keamanan tipe dengan cara yang umum adalah kandidat yang baik untuk obat generik. A List<String>adalah daftar yang hanya beroperasi pada string.

Setiap situasi di mana Anda ingin menerapkan SRP dengan cara yang digeneralisasi dan menghindari metode spesifik tipe adalah kandidat yang baik untuk obat generik. Misalnya, dokumen PersonDocument, PlaceDocument, dan ThingDocument dapat diganti dengan a Document<T>.

Pola Pabrik Abstrak adalah kasus penggunaan yang baik untuk generik, sedangkan Metode Pabrik biasa hanya akan membuat objek yang mewarisi dari antarmuka konkret yang umum.

Robert Harvey
sumber
Saya tidak benar-benar setuju dengan gagasan bahwa antarmuka generik harus dipasangkan dengan implementasi generik. Mendeklarasikan parameter tipe generik dalam antarmuka dan kemudian memperbaiki atau membuat instance dalam berbagai implementasi adalah teknik yang ampuh. Ini sangat umum di Scala. Menghubungkan hal-hal abstrak; kelas mengkhususkan mereka.
Benjamin Hodgson
@BenjaminHodgson: itu artinya Anda membuat implementasi pada antarmuka umum untuk tipe tertentu. Pendekatan keseluruhan masih bersifat generik, meskipun agak kurang begitu.
Robert Harvey
Saya setuju. Mungkin saya salah mengerti jawaban Anda - Anda sepertinya menasihati desain semacam itu.
Benjamin Hodgson
@BenjaminHodgson: Bukankah Metode Pabrik ditulis dengan antarmuka konkret , bukan yang generik? (meskipun Pabrik Abstrak akan menjadi generik)
Robert Harvey
Saya pikir kami setuju :) Saya mungkin akan menulis contoh OP sebagai sesuatu class StackFactory { Stack<T> Create<T>(T item, int count); }. Saya dapat menulis contoh tentang apa yang saya maksud dengan "memperbaiki parameter tipe dalam subkelas" dalam jawaban jika Anda ingin klarifikasi lebih lanjut, meskipun jawabannya hanya akan berhubungan dengan pertanyaan awal.
Benjamin Hodgson
8

Rencana Anda tentang cara memperkenalkan generalitas ke antarmuka Anda tampaknya benar bagi saya. Namun, pertanyaan Anda apakah itu ide yang bagus atau tidak membutuhkan jawaban yang agak lebih rumit.

Selama bertahun-tahun saya telah mewawancarai sejumlah kandidat untuk posisi Rekayasa Perangkat Lunak atas nama perusahaan tempat saya bekerja, dan saya menyadari bahwa mayoritas pencari kerja di luar sana merasa nyaman menggunakan kelas generik, (misalnya, kelas koleksi standar,) tetapi tidak dengan menulis kelas generik. Sebagian besar tidak pernah menulis satu kelas generik dalam karier mereka, dan tampak seolah-olah mereka akan benar-benar hilang jika diminta untuk menulis satu. Jadi, jika Anda memaksakan penggunaan obat generik pada siapa pun yang ingin mengimplementasikan antarmuka Anda atau memperluas implementasi basis Anda, Anda mungkin menunda orang.

Namun, yang dapat Anda coba adalah menyediakan versi antarmuka dan generik non-generik antarmuka Anda dan implementasi basis Anda, sehingga orang-orang yang tidak menggunakan obat generik dapat hidup bahagia di dunia mereka yang sempit dan berat, sementara orang-orang yang do generics dapat menuai manfaat dari pemahaman mereka yang lebih luas tentang bahasa.

Mike Nakis
sumber
3

Sekarang, Itemdan ItemStackFactorysaya benar-benar dapat melihat beberapa pihak ketiga yang perlu memperpanjangnya di masa depan.

Ada keputusan yang dibuat untuk Anda. Jika Anda tidak memberikan obat generik, mereka perlu meningkatkan penerapannya Itemsetiap kali mereka menggunakan:

class MyItem implements Item {
}
MyItem item = new MyItem();
ItemStack is = createItemStack(item, 10);
// They would have to cast here.
MyItem theItem = (MyItem)is.getItem();

Setidaknya Anda harus membuat ItemStackobat generik sesuai saran Anda. Manfaat terdalam dari obat generik adalah bahwa Anda hampir tidak pernah perlu melakukan apa pun, dan menawarkan kode yang memaksa pengguna untuk melemparkan adalah IMHO pelanggaran pidana.

OldCurmudgeon
sumber
2

Wow ... Saya memiliki pandangan yang sangat berbeda dalam hal ini.

Saya benar-benar dapat melihat kebutuhan pihak ketiga

Ini sebuah kesalahan. Anda tidak boleh menulis kode "untuk masa depan".

Juga, antarmuka ditulis atas-bawah. Anda tidak melihat kelas dan berkata "Hei, kelas ini benar-benar bisa mengimplementasikan antarmuka dan kemudian saya bisa menggunakan antarmuka itu di tempat lain juga". Itu adalah pendekatan yang salah, karena hanya kelas yang menggunakan antarmuka yang benar-benar tahu antarmuka apa yang seharusnya. Alih-alih, Anda harus berpikir, "Di tempat ini kelas saya membutuhkan dependensi. Biarkan saya mendefinisikan antarmuka untuk dependensi itu. Ketika saya selesai menulis kelas ini, saya akan menulis implementasi dependensi itu." Apakah seseorang akan pernah menggunakan kembali antarmuka Anda dan menggantikan implementasi default Anda dengan antarmuka Anda sama sekali tidak relevan. Mereka mungkin tidak pernah melakukannya. Ini tidak berarti antarmuka itu tidak berguna. Itu membuat kode Anda mengikuti prinsip Inversion of Control, yang membuat kode Anda sangat bisa diperluas di banyak tempat "

anton1980
sumber
0

Saya pikir menggunakan obat generik dalam situasi Anda terlalu rumit.

Jika Anda memilih versi antarmuka generik, desain menunjukkan hal itu

  1. ItemStackContainer <A> berisi satu tipe stack, A. (mis. ItemStackContainer tidak bisa mengandung banyak tipe stack.)
  2. ItemStack didedikasikan untuk jenis item tertentu. Jadi tidak ada tipe abstraksi dari semua jenis tumpukan.
  3. Anda perlu mendefinisikan ItemStackFactory spesifik untuk setiap jenis item. Itu dianggap terlalu bagus sebagai Pola Pabrik.
  4. Tulis kode yang lebih sulit seperti ItemStack <? memperpanjang Item>, mengurangi rawatan.

Asumsi dan pembatasan implisit ini adalah hal yang sangat penting untuk diputuskan dengan hati-hati. Jadi terutama di tahap awal, desain prematur ini tidak boleh diperkenalkan. Sederhana, mudah dimengerti, desain yang umum diinginkan.

Akio Takahashi
sumber
0

Saya akan mengatakan itu terutama tergantung pada pertanyaan ini: Jika seseorang perlu memperluas fungsionalitas Itemdan ItemStackFactory,

  • akankah mereka hanya menimpa metode dan contoh dari subclases mereka akan digunakan seperti kelas dasar, hanya berperilaku berbeda?
  • atau akankah mereka menambah anggota dan menggunakan subclass secara berbeda?

Dalam kasus pertama, tidak ada kebutuhan untuk obat generik, yang kedua, mungkin ada.

Michael Borgwardt
sumber
0

Mungkin, Anda dapat memiliki hierarki antarmuka, di mana Foo adalah satu antarmuka, Bar adalah antarmuka lainnya. Kapan pun suatu tipe harus keduanya, itu adalah FooAndBar.

Untuk menghindari desgin, buat saja tipe FooAndBar saat diperlukan, dan simpan daftar antarmuka yang tidak akan pernah memperpanjang apa pun.

Ini jauh lebih nyaman bagi sebagian besar programmer (paling suka memeriksa tipenya atau tidak pernah berurusan dengan pengetikan statis apa pun). Sangat sedikit orang yang menulis kelas obat generik sendiri.

Juga sebagai catatan: antarmuka adalah tentang tindakan apa yang mungkin dapat dijalankan. Mereka seharusnya benar-benar menjadi kata kerja, bukan kata benda.

Akash Patel
sumber
Ini mungkin menjadi alternatif untuk menggunakan obat generik, tetapi pertanyaan awal menanyakan kapan harus menggunakan obat generik atas apa yang Anda sarankan. Bisakah Anda meningkatkan jawaban Anda untuk menyarankan bahwa obat generik tidak diperlukan, tetapi menggunakan beberapa antarmuka adalah jawaban untuk semua?
Jay Elston