Interfacce: profitto dell'uso

1

Prima di tutto, il mio linguaggio ubiquitario è PHP, e sto pensando di imparare Java.

Quindi lascia che divida la mia domanda su due parti strettamente correlate.

Ecco la prima parte.

Dire che ho una classe modello di dominio. Ha alcuni getter, setter, alcuni metodi di interrogazione, ecc. E un giorno voglio avere la possibilità di confrontarli. Quindi sembra:

class MyEntity extends AbstractEntity
{
    public function getId()
    {
        // get id property
    }

    public function setId($id)
    {
        // set id property
    }

    // plenty of other methods that set or retrieve data

    public function compareTo(MyEntity $anotherEntity)
    {
        // some compare logic
    }
}

Se fosse stato Java, avrei dovuto implementare un'interfaccia Comparable . Ma perché? Polimorfismo? Readbility? O qualcos'altro? E se fosse PHP, dovrei creare l'interfaccia Comparable per me stesso?

Quindi ecco la seconda parte.

Il mio collega mi ha detto che è una regola empirica in Java creare un'interfaccia per ogni aspetto comportamentale della classe. Ad esempio, se volessi presentare questo oggetto come una stringa, dovrei dichiarare questo comportamento con qualcosa come implements Stringable , dove in caso di PHP Stringable sarebbe simile a:

interface Stringable
{
    public function __toString();
}

Questa è davvero una regola empirica? Quali benefici si ottengono con questo approccio? E ne vale la pena in PHP? E in Java?

    
posta Zapadlo 03.04.2012 - 22:39
fonte

6 risposte

1

Non so nulla di PHP, e non intendo davvero rispondere alla tua domanda, ma la risposta alla tua prima parte è "Polymorphism" e la seconda è "No". Le altre risposte qui fanno un buon lavoro di spiegazione di tutto.

Quello che volevo aggiungere è che puoi utilizzare le classi anziché le interfacce.

All'estremo, puoi scrivere una classe astratta con tutti i metodi astratti che funzionano esattamente come un'interfaccia. Il lato negativo immediato è che qualsiasi classe che estende ("implementa") questa classe non può estendere un'altra classe, che potrebbe essere un problema, ora o più tardi. D'altra parte, puoi rendere concreti uno o più dei tuoi metodi e salvare il lavoro in tutte le classi di estensione (precedentemente implementate). Tutte le classi discendenti (sottoclassi, sottoclassi, ecc.) Possono essere referenziate come appartenenti a questa classe, fornendo tutto il polimorfismo di un'interfaccia.

Il mio solito processo è iniziare con un'interfaccia, vedere che ho codice comune in tutte le classi che lo implementano, quindi trasformare l'interfaccia in una classe base e inserire il codice comune lì. Più tardi, mi trovo a scrivere una nuova classe che non può estendere la classe base, ma ha bisogno di assomigliarlo. (Ho bisogno del polimorfismo.) Quindi scrivo una nuova interfaccia come quella vecchia, ho quella vecchia - ora la classe base - la implemento e cambio tutti i punti in cui qualcosa è dichiarato essere del tipo di classe base dichiararlo come nuovo tipo di interfaccia. La nuova classe implementa anche l'interfaccia.

E vado alla prossima interfaccia che diventa una classe base che genera un'interfaccia.

    
risposta data 04.04.2012 - 19:16
fonte
6

Le interfacce aggiungono un livello di astrazione al codice. (come una cucitura). Significa che puoi quindi modificare le implementazioni concrete nelle classi dipendenti senza interrompere il codice esistente. È ottimo per disaccoppiare il codice e rende fattibili test di unità.

My colleague told me that it is a rule of thumb in Java to create an interface for every behavioral aspect of the class.

Ha torto. Esistono molti casi d'uso in cui non è necessario implementare interfacce. Spesso è meglio seguire l'approccio KISS . Le interfacce aggiungono complessità e per piccole classi / applicazioni semplici potrebbe non valerne la pena.

Dovresti usare le interfacce per ottenere qualcosa (ad esempio implementando un pattern o per un test unitario) implementando ciecamente qualcosa "perché il suo bene" porterà solo al codice gonfiabile

    
risposta data 04.04.2012 - 00:13
fonte
4

Avere MyEntity implementare Comparable<MyEntity> consente di utilizzare Collections.sort(...) e qualsiasi altra operazione che agisce su Comparable . Da il documento :

Lists (and arrays) of objects that implement this interface can be sorted automatically by Collections.sort (and Arrays.sort). Objects that implement this interface can be used as keys in a sorted map or as elements in a sorted set, without the need to specify a comparator.

L'utilizzo del parametro MyEntity su Comparable semplifica il tuo metodo compareTo in quanto non è necessario eseguire alcun tipo di controllo o fusione nel codice.

public int compareTo(MyEntity other) {
    if (other == null) return -1;
    return getSomeProperty().compareTo(other.getSomeProperty());
}

Se hai bisogno di ordinare su someOtherProperty , puoi farlo facilmente:

List<MyEntity> entityList = someService.getEntities();
Collections.sort(entityList, new Comparator<MyEntity>()
{
    public int compare(MyEntity e1, MyEntity e2)
    {
        if(e1 == null) return e2 == null ? 0 : 1;
        if(e2 == null) return -1;
        return e1.getSomeOtherProperty().compareTo(e2.getSomeOtherProperty());
    } 
});
    
risposta data 03.04.2012 - 23:22
fonte
2

Dalla mia esperienza, questi sono i primi punti che vorrei andare per le interfacce durante la mia codifica.

- Le interfacce possono rendere flessibile il codice agendo come un contratto     tra le implementazioni.

Stringable string =new StringFromMe();

Poi, più tardi, se decidi che vuoi usare un diverso tipo di stringa (forse c'è un'altra libreria, con un tipo migliore di stringa), cambi il tuo codice in:

Stringable string =new OtherKindOfString();

il motivo è, ad esempio, se si desidera creare un elenco di stringhe ed eseguire alcune operazioni su ciascuna di esse, ma si desidera che l'elenco contenga diversi tipi di stringhe. Su ogni stringa puoi fare:

**string .toString()**

Oppure puoi passare entrambi i tipi di stringhe a un metodo che prende il tipo di interfaccia comune.

dosomething(Stringable string)

Questo concetto è anche chiamato come polimorfismo.

- Facilità di test / mocking dell'unità

Ad esempio, la stringa richiede alcuni dati dal database al computer di toString (assumilo solo per motivi di spiegazione) che rende il codice non verificabile dall'unità. Ora puoi fornire un'implementazione semplice della stringa

**Stringable string =new TheDummyStringWithoutDBConnection();**

Quindi puoi procedere con il test dell'unità su altre parti del codice in modo che i test unitari non siano dipendenti dal database o da altri sistemi esterni di per sé.

    
risposta data 03.04.2012 - 22:51
fonte
1

Secondo me, i punti principali delle interfacce sono l'astrazione e la sicurezza del tipo. L'implementazione di un'interfaccia ti obbliga ad esporre alcune funzionalità ad altri, mentre altri componenti non hanno bisogno di sapere del tuo tipo specifico e della sua implementazione.

Ad esempio, un algoritmo di ordinamento generico può aspettarsi un elenco di oggetti che implementano l'interfaccia Comparable . In questo modo, l'algoritmo non ha bisogno di conoscere i dettagli dell'implementazione di entità, voci di elenco o qualsiasi cosa tu voglia ordinare. Ma per definizione, gli elementi che implementano l'interfaccia Comparable devono fornire una determinata funzionalità su cui l'algoritmo di ordinamento può fare affidamento.

Le interfacce sono un buon modo per ridurre l'accoppiamento nella tua architettura, ma non dovresti esagerare con l'uso perché questo può ridurre la manutenibilità e leggibilità del tuo codice a causa del livello di astrazione. Se la funzionalità delle classi che implementano un'interfaccia è molto simile, pensa a una classe base comune.

In uno dei nostri progetti, abbiamo un runtime che può ospitare diversi servizi che fanno ciò che vogliono fare. Poiché il nostro runtime deve gestire tali servizi, richiede che i servizi implementino l'interfaccia IRuntimeService con metodi come Start o Stop . In questo modo, il runtime può ospitare e gestire ogni possibile servizio, a patto che implementa questa interfaccia. Questo esempio potrebbe darti un'idea del perché le interfacce possono essere molto utili.

Mi dispiace di non poterti dire molto su PHP, perché non ho fatto molto OOP lì.

    
risposta data 03.04.2012 - 23:07
fonte
1

In PHP, non hai controlli di tipo rigido come in Java. Quindi in molti casi in cui dovevi usare le interfacce in Java puoi usare qualcosa come is_callable in PHP. PHP non ha l'imposizione del tipo in fase di compilazione, il che significa che non si guadagna tanto definendo molte interfacce, quindi utilizzerei un'interfaccia nei casi in cui c'è una funzionalità non banale dietro di essa. Ad esempio, se si volesse esprimere una funzionalità di file - avendo aperto, chiuso, letto, scritto, ecc. - si potrebbe desiderare un'interfaccia. Se tuttavia si desidera una sola funzione con un'interfaccia potrebbe essere eccessivo.

    
risposta data 04.04.2012 - 00:45
fonte

Leggi altre domande sui tag