Le interfacce vuote (ma non le interfacce marcatori) sono una cattiva pratica di programmazione?

5

Sto progettando un'architettura in cui ho:

public interface IObjectReader{
    public Object read();
}

public class ConcreteObjectReader implements IObjectReader{
     @Override
     public Object read(){
         //do stuff
         return new Object();
     }
}

Ora, se voglio consentire la parametrizzazione di un lettore, potrei creare un'altra interfaccia in questo modo:

public interface IParameterizedObjectReader extends IObjectReader {
    public void setParams(Map<String,String> params);
    public Map<String,String> getParams();
}

Per me è ragionevole. Tuttavia, nel mio progetto ho anche object writer, processori di oggetti e così via, e devono anche essere, eventualmente, parametrizzati. In questo scenario, avere due o più interfacce che definiscono lo stesso contratto (getParams e setParams) è una pessima idea per me. Quindi definirei un IParameter di interfaccia come questo:

public interface IParameter{
    public void setParams(Map<String,String> params);
    public Map<String, String> getParams();
}

e farei l'interfaccia IParametereziedObjectReader (simile per IParameterizedObjectWriter, ecc.) anche estendere IParameter, ma lascerei vuoto.

Questa è una cattiva idea? O forse dovrei lasciare solo l'interfaccia IParameter ed eliminare le sue sottointerfacce? Per chiarezza, devo dire che non uso queste interfacce parametrizzate come marcatori ovunque nel mio codice, quindi sarebbero vuote solo per ragioni architettoniche.

Come progetteresti questa soluzione in modo diverso?

    
posta sibly22 15.07.2017 - 10:37
fonte

4 risposte

12

Are empty interfaces (but not marker interfaces) a bad programming practice?

In generale, sì. Per definizione, le interfacce vuote non forniscono nulla. Possono essere interfacce marker (generalmente malvagie) o alias per un altro tipo (occasionalmente utili a causa del codice legacy quando si rinomina qualcosa). Ma in un progetto greenfield come questo sono solo troppo ingegneristici e YAGNI.

How would you design this solution differently?

Senza sapere di più sulle tue esigenze, non posso dire. Ma cercare di rendere arbitrario leggere / scrivere / elaborare, con il numero / forma di parametri arbitrari è una commissione da pazzi. I sistemi che possono "fare qualsiasi cosa" non forniscono alcuna astrazione utile oltre la semplice scrittura del codice e tendono ad essere incubi da implementare e mantenere. Se vuoi un linguaggio di scripting, usa usa un linguaggio di scripting.

    
risposta data 15.07.2017 - 17:50
fonte
2

Questo sembra un lavoro per il principio di segregazione dell'interfaccia !

Dici che vuoi creare questo:

public interface IParameterizedObjectReader extends IObjectReader {
    public void setParams(Map<String,String> params);
    public Map<String,String> getParams();
}

O questo:

public interface IParameterizedObjectReader extends IObjectReader, IParameter { }

Chiedo, ti serve questo:

IParameter p;

e questo:

IObjectReader oReader;

e questo:

IParameterizedObjectReader por;

Perché se non hai clienti che ne hanno bisogno, stai rendendo le cose un po 'difficili per un motivo difficile da vedere. Il fatto che un'interfaccia possa essere vuota perché ottiene ciò che promette dalle interfacce che estende in realtà non significa nulla per me. Mi piace semplicemente la mancanza di duplicazione. Ma non giustifica o indebolisce l'esistenza dell'interfaccia.

Ciò che giustifica l'esistenza di un'interfaccia è che alcuni clienti vogliono usarlo. I client hanno le interfacce. Hanno bisogno delle cose che l'interfaccia promette. Niente importa se è stato definito con un corpo vuoto. Inferno, l'interfaccia potrebbe semplicemente esistere per fornire un nome indiretto.

E sì, puoi creare complessità in questo modo. Non crearlo a causa di "ragioni architettoniche". Crealo perché qualcosa ne ha bisogno. Adesso. Oggi.

I clienti non dovrebbero sapere su metodi a cui non interessa. Segui questa regola e le tue interfacce saranno interfacce di ruolo. Ciascuno esisterà per una buona ragione. Avrà una sola responsabilità. E non risulta in un'esplosione combinatoria di un'interfaccia per ogni possibile insieme di metodi.

Oh, e ricorda, il prefisso IMyInterface è una cosa di C #. Java fa la cosa del suffisso MyImplementationImpl . Entrambe sono cose terribili ma quelle sono le cose.

    
risposta data 15.07.2017 - 16:57
fonte
1

Costruire un albero di interfacce, avendo un'interfaccia ereditata da un'altra, presto vincerà lo scopo di avere interfacce in primo luogo. Potresti anche costruire un albero delle classi e non usare affatto interfacce.

È sempre meglio avere più interfacce piccole, ognuna con una singola funzione, piuttosto che avere un'interfaccia composta che fa tutto ma serve un singolo scenario. Con quest'ultimo leghi davvero l'interfaccia con una classe che hai già nella parte posteriore della tua testa, rendendola inutilizzabile per qualsiasi altra cosa, quindi inutile da avere.

Quindi nel tuo esempio specifico vuoi un IReader e un'interfaccia IParametrizzabile indipendenti tra loro, ognuno dei quali definisce una singola funzione piuttosto che il set completo di alcune classi arbitrarie.

    
risposta data 16.07.2017 - 01:57
fonte
0

Il fatto che lettore, scrittore e processori abbiano metodi con firma comune sono solo circostanze accidentali.

L'introduzione dell'interfaccia IParameter vincolerà le tue astrazioni di lettore, autore e processore sotto la stessa firma del contratto.
Dopo la prima richiesta di modifica in cui è necessario aggiungere un altro argomento alla classe reader o modificare il tipo di parametro, si eliminerà IParameter .

Dato che l'applicazione necessita di lettori sia parametrici che parametrizzati, considera l'introduzione del lettore con due metodi.

public interface IObjectReader
{
    public Object read();
    public Object read(Map<String,String> params);
}
    
risposta data 16.07.2017 - 08:40
fonte

Leggi altre domande sui tag