Apakah praktik yang buruk menggunakan antarmuka hanya untuk kategorisasi?

12

Sebagai contoh:

Katakanlah saya memiliki kelas A, B, C. Saya memiliki dua antarmuka, sebut saja IAnimaldan IDog. IDogmewarisi dari IAnimal. Adan Byang IDogs, sementara Ctidak, tapi itu adalah IAnimal.

Bagian yang penting adalah IDogtidak menyediakan fungsionalitas tambahan. Ini hanya digunakan untuk memungkinkan Adan B, tetapi tidak C, untuk diteruskan sebagai argumen untuk metode tertentu.

Apakah ini praktik buruk?

Kendall Frey
sumber
8
IAnimaldan IDogyang nama tautologi mengerikan!
5
@JarrodRoberson sementara saya cenderung setuju, jika Anda bekerja di dunia .Net, Anda agak terjebak dengannya (atau akan menentang konvensi yang telah dibuat jika Anda melakukannya secara berbeda).
Daniel B
2
@JarrodRoberson IMHO MyProject.Data.IAnimaldan MyProject.Data.Animallebih baik daripada MyProject.Data.Interfaces.Animaldan MyProject.Data.Implementations.Animal
Mike Koder
1
Intinya adalah seharusnya tidak ada alasan untuk peduli apakah itu adalah Interfaceatau Implementation, apakah dalam awalan berulang atau namespace baik, itu adalah tautologi baik cara dan melanggar KERING. Doghanya itu yang harus Anda pedulikan. PitBull extends Dogtidak perlu redundansi implementasi, kata itu extendsmemberitahu saya semua yang perlu saya ketahui, baca tautan yang saya posting di komentar asli saya.
1
@Jarrod: Berhenti mengeluh kepada saya. Mengeluh kepada tim .NET dev. Jika saya melanggar standar, sesama devs saya akan membenci nyali saya.
Kendall Frey

Jawaban:

13

Antarmuka yang tidak memiliki anggota atau memiliki anggota yang sama persis dengan antarmuka lain yang diwarisi dari antarmuka yang sama yang disebut Marker Interface dan Anda menggunakannya sebagai penanda.

Ini bukan praktik yang buruk tetapi antarmuka dapat diganti dengan atribut (penjelasan) jika bahasa yang Anda gunakan mendukungnya.

Saya yakin dapat mengatakan bahwa ini bukan praktik yang buruk karena saya telah melihat pola "Marker Interface" penggunaan berat di Proyek Orchard

Berikut adalah contoh dari proyek Orchard.

public interface ISingletonDependency : IDependency {}

/// <summary>
/// Base interface for services that may *only* be instantiated in a unit of work.
/// This interface is used to guarantee they are not accidentally referenced by a singleton dependency.
/// </summary>
public interface IUnitOfWorkDependency : IDependency {}

/// <summary>
/// Base interface for services that are instantiated per usage.
/// </summary>
public interface ITransientDependency : IDependency {}

Silakan merujuk ke Penanda Antarmuka .

Darah segar
sumber
Apakah atribut / anotasi mendukung pemeriksaan waktu kompilasi (menggunakan C # sebagai bahasa referensi)? Saya tahu saya bisa melempar pengecualian jika objek tidak memiliki atribut, tetapi pola antarmuka menangkap kesalahan pada waktu kompilasi.
Kendall Frey
Jika Anda menggunakan Marker Interface, maka Anda belum mendapatkan manfaat kompilasi cek sama sekali. Anda pada dasarnya melakukan pemeriksaan jenis dengan antarmuka.
Freshblood
Saya bingung. Pola 'Penanda antarmuka' ini memang menyediakan pemeriksaan waktu kompilasi, di mana Anda tidak bisa meneruskan Cmetode yang mengharapkan IDog.
Kendall Frey
Jika Anda menggunakan pola ini, maka Anda melakukan pekerjaan reflektif. Maksud saya Anda secara dinamis atau statis melakukan pemeriksaan jenis. Saya yakin Anda memiliki sesuatu yang salah dalam kasus penggunaan Anda, tetapi saya belum melihat bagaimana Anda menggunakannya.
Freshblood
Biarkan saya menggambarkannya seperti yang saya lakukan di komentar pada jawaban Telastyn. misalnya saya memiliki Kennelkelas yang menyimpan daftar IDogs.
Kendall Frey
4

Ya, ini praktik buruk di hampir setiap bahasa.

Jenis tidak ada di sana untuk menyandikan data; mereka adalah bantuan untuk membuktikan bahwa data Anda pergi ke mana ia harus pergi dan berperilaku seperti itu perlu berperilaku. Satu-satunya alasan untuk sub-tipe adalah jika perilaku polimorfik berubah (dan tidak selalu demikian).

Karena tidak ada pengetikan, apa yang bisa dilakukan IDog tersirat. Satu programmer memikirkan satu hal, yang lain berpikir hal lain dan hal-hal rusak. Atau hal-hal yang diwakilinya meningkat karena Anda membutuhkan kontrol berbutir halus.

[edit: klarifikasi]

Maksud saya adalah Anda melakukan beberapa logika berdasarkan antarmuka. Mengapa beberapa metode hanya dapat bekerja dengan anjing dan hewan lainnya? Perbedaan itu adalah kontrak yang tersirat karena tidak ada anggota aktual yang menyediakan perbedaan; tidak ada data aktual dalam sistem. Satu-satunya perbedaan adalah antarmuka (oleh karena itu komentar 'tidak mengetik'), dan apa artinya akan berbeda antara programmer. [/ edit]

Bahasa tertentu menyediakan mekanisme sifat di mana ini tidak terlalu buruk tetapi mereka cenderung fokus pada kemampuan, bukan perbaikan. Saya memiliki sedikit pengalaman dengan mereka sehingga tidak dapat berbicara dengan praktik terbaik di sana. Dalam C ++ / Java / C # though ... not good.

Telastyn
sumber
Saya bingung dengan dua paragraf tengah Anda. Apa yang Anda maksud dengan 'sub-tipe'? Saya menggunakan antarmuka, yang merupakan kontrak, bukan implementasi, dan saya tidak yakin bagaimana komentar Anda berlaku. Apa yang Anda maksud dengan 'tidak mengetik'? Apa yang Anda maksud dengan 'tersirat'? IDog dapat melakukan semua hal yang dapat dilakukan IAnimal, tetapi tidak dijamin dapat melakukan lebih banyak.
Kendall Frey
Terimakasih atas klarifikasinya. Dalam kasus khusus saya, saya tidak persis menggunakan antarmuka secara berbeda. Mungkin bisa dijelaskan dengan mengatakan saya memiliki Kennelkelas yang menyimpan daftar IDogs.
Kendall Frey
@KendallFrey: Entah Anda keluar dari antarmuka (buruk), atau Anda membuat perbedaan buatan yang berbau abstraksi yang salah.
Telastyn
2
@Telastyn - Tujuan dari antarmuka adalah untuk menyediakan metadata
Freshblood
1
@ Telastyn: Jadi bagaimana atribut berlaku untuk pemeriksaan waktu kompilasi? AFAIK, dalam C #, atribut hanya dapat digunakan saat runtime.
Kendall Frey
2

Saya hanya tahu C # dengan baik, jadi saya tidak bisa mengatakan ini untuk pemrograman OO secara umum, tetapi sebagai contoh C #, jika Anda perlu mengkategorikan kelas opsi yang jelas adalah antarmuka, enum properti atau atribut khusus. Biasanya saya akan memilih antarmuka apa pun yang dipikirkan orang lain.

if(animal is IDog)
{
}
if(animal.Type == AnimalType.Dog)
{
}
if(animal.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any())
{
}


var dogs1 = animals.OfType<IDog>();
var dogs2 = animals.Where(a => a.Type == AnimalType.Dog);
var dogs3 = animals.Where(a => a.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any());


var dogsFromDb1 = session.Query<IDog>();
var dogsFromDb2 = session.Query<Animal>().Where(a => a.Type == AnimalType.Dog);
var dogsFromDb3 = session.Query<Animal>().Where(a => a.GetType().GetCustomAttributes(typeof(DogAttribute),false).Any());


public void DoSomething(IDog dog)
{
}
public void DoSomething(Animal animal)
{
    if(animal.Type != AnimalType.Dog)
    {
        throw new ArgumentException("This method should be used for dogs only.");
    }
}
public void DoSomething(Animal animal)
{
    if(!animal.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any())
    {
        throw new ArgumentException("This method should be used for dogs only.");
    }
}
Mike Koder
sumber
Bagian terakhir dari kode sebagian besar untuk apa saya menggunakannya. Saya dapat melihat bahwa versi antarmuka jelas lebih pendek, serta diperiksa waktu kompilasi.
Kendall Frey
Saya punya kasus penggunaan simillar tetapi kebanyakan. Net devs sangat menyarankan untuk tidak menggunakannya, tetapi saya tidak akan menggunakan atribut
GorillaApe
-1

Tidak ada yang salah dengan memiliki antarmuka yang mewarisi antarmuka lain tanpa menambahkan anggota baru, jika semua implementasi dari antarmuka yang terakhir diharapkan dapat melakukan hal - hal yang beberapa implementasi dari yang sebelumnya mungkin tidak berguna . Memang, ini adalah pola yang baik dimana IMHO seharusnya lebih banyak digunakan dalam hal-hal seperti .net Framework, terutama karena seorang pelaksana yang kelasnya secara alami akan memenuhi kendala yang tersirat oleh antarmuka yang lebih diturunkan dapat menunjukkan bahwa hanya dengan menerapkan antarmuka yang lebih diturunkan. , tanpa harus mengubah kode atau deklarasi anggota-implementasi.

Sebagai contoh, misalkan saya memiliki antarmuka:

antarmuka IMaybeMutableFoo {
  Bar Thing {get; set;} // Dan properti lainnya juga
  bool IsMutable;
  IImmutableFoo AsImmutable ();
}
antarmuka IImmutableFoo: IMaybeMutableFoo {};

Suatu implementasi IImmutableFoo.AsImmutable()akan diharapkan untuk mengembalikan dirinya sendiri; implementasi kelas yang bisa berubah IMaybeMutableFoo.AsImmutable()akan diharapkan untuk mengembalikan objek baru yang sangat tidak dapat diubah yang berisi snapshot data. Sebuah rutinitas yang ingin menyimpan snapshot data yang disimpan dalam suatu IMaybeMutableFoodapat menawarkan kelebihan:

publicDoid StoreData (IImmutableFoo ThingToStore)
{
  // Menyimpan referensi akan cukup, karena ThingToStore dikenal tidak dapat diubah
}
publicDoid StoreData (IMaybeMutableFoo ThingToStore)
{
  StoreData (ThingToStore.AsImmutable ()); // Panggil kelebihan yang lebih spesifik
}

Dalam kasus di mana kompiler tidak tahu bahwa benda yang akan disimpan adalah IImmutableFoo, ia akan memanggil AsImmutable(). Fungsi harus kembali dengan cepat jika item sudah tidak dapat diubah, tetapi panggilan fungsi melalui antarmuka masih membutuhkan waktu. Jika kompiler tahu bahwa item tersebut sudah merupakan IImmutableFoo, ia dapat melewati pemanggilan fungsi yang tidak perlu.

supercat
sumber
Downvoter: Ingin berkomentar? Ada saat-saat mewarisi antarmuka tanpa menambahkan sesuatu yang baru itu konyol, tetapi itu tidak berarti tidak ada saat-saat ketika itu adalah teknik yang baik (dan IMHO kurang dimanfaatkan).
supercat