È una cattiva pratica usare un'interfaccia solo per la categorizzazione?


12

Per esempio:

Dire che ho classi A, B, C. Ho due interfacce, chiamiamole IAnimale IDog. IDogeredita da IAnimal. Ae Bsono IDogs, mentre Cnon lo è, ma è un IAnimal.

La parte importante è che IDognon fornisce funzionalità aggiuntive. Viene utilizzato solo per consentire Ae B, ma non C, essere passato come argomento a determinati metodi.

Questa è una cattiva pratica?



5
@JarrodRoberson mentre tendo ad essere d'accordo, se lavori nel mondo .Net, sei un po 'bloccato con esso (o andrai contro la convenzione stabilita se lo fai diversamente).
Daniel B,

2
@JarrodRoberson IMHO MyProject.Data.IAnimale MyProject.Data.Animalsono meglio di MyProject.Data.Interfaces.Animale MyProject.Data.Implementations.Animal
Mike Koder il

1
Il punto è non ci dovrebbe essere alcun motivo per la cura, anche se si tratta di una Interfaceo Implementation, se in un prefisso ripetitivi o uno spazio dei nomi sia, è una tautologia in entrambi i casi e viola SECCO. Dogè tutto ciò di cui dovresti preoccuparti. PitBull extends Dognon necessita neanche di ridondanza di implementazione, la parola extendsmi dice tutto quello che devo sapere, leggi il link che ho pubblicato nel mio commento originale.

1
@Jarrod: smettila di lamentarti con me. Reclami al team di sviluppo .NET. Se andassi contro lo standard, i miei colleghi sviluppatori mi odierebbero.
Kendall Frey,

Risposte:


13

Interfaccia che non ha alcun membro o ha esattamente gli stessi membri con un'altra interfaccia ereditata dalla stessa interfaccia denominata Marker Interface e la si utilizza come marker.

Non è una cattiva pratica, ma l'interfaccia può essere sostituita da attributi (annotazioni) se la lingua che stai utilizzando la supporta.

Posso affermare con certezza che non è una cattiva pratica perché ho visto un modello "Marker Interface" ad uso intensivo in Orchard Project

Ecco un esempio del progetto 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 {}

Fare riferimento a Marker Interface .


Attributi / annotazioni supportano il controllo in fase di compilazione (usando C # come linguaggio di riferimento)? So che potrei generare un'eccezione se l'oggetto non ha l'attributo, ma il modello di interfaccia rileva errori in fase di compilazione.
Kendall Frey,

Se si utilizza Marker Interface in modo da non avere alcun vantaggio in termini di compilazione. In pratica stai facendo il controllo del tipo tramite l'interfaccia.
Freshblood,

Non ho capito bene. Questo modello di "interfaccia Marker" fornisce controlli in fase di compilazione, in quanto non è possibile passare Ca un metodo aspettandosi un IDog.
Kendall Frey,

Se stai usando questo modello, stai facendo grandi lavori di riflessione. Voglio dire che stai facendo dinamicamente o staticamente dei controlli del tipo. Sono sicuro che hai qualcosa di sbagliato nel tuo caso d'uso, ma non ho visto come lo stai usando.
Freshblood,

Lascia che lo descriva come ho fatto nel commento sulla risposta di Telastyn. ad es. ho una Kennelclasse che memorizza un elenco di IDogs.
Kendall Frey,

4

Sì, è una cattiva pratica in quasi tutte le lingue.

I tipi non sono lì per codificare i dati; sono un aiuto per dimostrare che i tuoi dati vanno dove devono andare e si comportano come devono comportarsi. L'unico motivo per sottotipo è se cambia un comportamento polimorfico (e non sempre allora).

Poiché non è possibile digitare, ciò che un IDog può fare è implicito. Un programmatore pensa una cosa, un altro pensa un'altra e le cose si guastano. O le cose che rappresenta aumentano quando hai bisogno di un controllo più preciso.

[modifica: chiarimento]

Quello che voglio dire è che stai facendo un po 'di logica basata sull'interfaccia. Perché alcuni metodi possono funzionare solo con cani e altri animali? Tale differenza è un contratto implicito poiché non esiste un membro effettivo che fornisca la differenza; nessun dato reale nel sistema. L'unica differenza è l'interfaccia (da qui il commento 'no typing'), e ciò significa che differirà tra i programmatori. [/modificare]

Alcune lingue forniscono meccanismi di tratto in cui questo non è troppo male, ma quelli tendono a concentrarsi sulle capacità, non sulla raffinatezza. Ho poca esperienza con loro, quindi non posso parlare delle migliori pratiche lì. In C ++ / Java / C # però ... non va bene.


Sono confuso dai tuoi due paragrafi centrali. Cosa intendi per "sottotipo"? Sto usando interfacce, che sono contratti, non implementazioni, e non sono sicuro di come si applicano i tuoi commenti. Cosa intendi con "nessuna digitazione"? Cosa intendi con "implicito"? Un IDog può fare tutto ciò che può fare un IAnimal, ma non è garantito che sia in grado di fare di più.
Kendall Frey,

Grazie per il chiarimento. Nel mio caso specifico, non sto esattamente usando le interfacce in modo diverso. Probabilmente può essere meglio descritto dicendo che ho una Kennelclasse che memorizza un elenco di IDogs.
Kendall Frey,

@KendallFrey: o stai uscendo dall'interfaccia (male) o stai creando una distinzione artificiale che profuma di un'astrazione errata.
Telastyn,

2
@Telastyn - Lo scopo di tale interfaccia è quello di fornire un metadata
Freshblood,

1
@Telastyn: come si applicano gli attributi al controllo in fase di compilazione? AFAIK, in C #, gli attributi possono essere utilizzati solo in fase di esecuzione.
Kendall Frey,

2

Conosco bene solo C #, quindi non posso dirlo per la programmazione OO in generale, ma come esempio C #, se devi classificare le classi le opzioni ovvie sono interfacce, proprietà enum o attributi personalizzati. Di solito sceglierei l'interfaccia non importa cosa pensano gli altri.

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.");
    }
}

L'ultima sezione del codice è principalmente per cui lo uso. Vedo che la versione dell'interfaccia è ovviamente più corta, oltre ad essere controllata in fase di compilazione.
Kendall Frey,

Ho un caso d'uso simile ma la maggior parte degli sviluppatori .net sconsiglia vivamente di farlo, ma non userò gli attributi
GorillaApe

-1

Non c'è nulla di sbagliato nell'avere un'interfaccia che eredita un'altra interfaccia senza aggiungere nuovi membri, se ci si aspetta che tutte le implementazioni di quest'ultima interfaccia possano fare utilmente cose che alcune implementazioni della prima potrebbero non fare. In effetti, è un buon modello che IMHO avrebbe dovuto essere usato di più in cose come il .net Framework, specialmente perché un implementatore la cui classe avrebbe naturalmente soddisfatto i vincoli impliciti dall'interfaccia più derivata può indicare che semplicemente implementando l'interfaccia più derivata , senza dover modificare alcun codice o dichiarazione di implementazione dei membri.

Ad esempio, supponiamo che io abbia le interfacce:

interfaccia IMaybeMutableFoo {
  Bar Thing {get; set;} // E anche altre proprietà
  bool IsMutable;
  IImmutableFoo AsImmutable ();
}
interfaccia IImmutableFoo: IMaybeMutableFoo {};

Si IImmutableFoo.AsImmutable()prevede che un'implementazione di tornerà da sola; IMaybeMutableFoo.AsImmutable()ci si aspetterebbe che un'implementazione di classe mutabile restituisca un nuovo oggetto profondamente immutabile contenente un'istantanea dei dati. Una routine che vuole archiviare un'istantanea dei dati contenuti in un IMaybeMutableFoopotrebbe offrire sovraccarichi:

public void StoreData (IImmutableFoo ThingToStore)
{
  // La memorizzazione del riferimento sarà sufficiente, poiché ThingToStore è noto per essere immutabile
}
public void StoreData (IMaybeMutableFoo ThingToStore)
{
  StoreData (ThingToStore.AsImmutable ()); // Chiama il sovraccarico più specifico
}

Nel caso in cui il compilatore non sappia che la cosa da memorizzare è una IImmutableFoo, chiamerà AsImmutable(). La funzione dovrebbe tornare rapidamente se l'elemento è già immutabile, ma una chiamata di funzione attraverso un'interfaccia richiede ancora tempo. Se il compilatore sa che l'elemento è già un IImmutableFoo, può saltare la chiamata di funzione inutile.


Downvoter: Vuoi commentare? Ci sono volte che ereditare un'interfaccia senza aggiungere nulla di nuovo è sciocco, ma ciò non significa che non ci siano momenti in cui è una buona tecnica (e IMHO sottoutilizzata).
Supercat,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.