Come "programmare su un'interfaccia"

3

Ho letto queste domande:

Non capisco come "programmare su un'interfaccia" se si utilizzano metodi in classi concrete che non fanno parte dell'interfaccia.

Mi rendo conto che l'esempio più comune di questo principio di progettazione è List vs ArrayList in Java perché è facile capire e illustrare il punto.

Ecco un esempio un po 'sciocco per illustrare ciò che la mia domanda sta ponendo (il codice è in PHP ma si applica alla maggior parte delle lingue OOP):

interface ResponseInterface {
    public function getStatusCode();
}

class Response implements ResponseInterface {
    private $status;

    public function getStatusCode() {
        return $this->status;
    }
}

class AwesomeResponse implements ResponseInterface {
    private $status;
    private $message = ['200' => 'OK', '500' => 'Internal Server Error'];

    public function getStatusCode() {
        return $this->status;
    }

    public function getStatusMessage() {
        return $this->message[$status];
    }
}

class Server {
    public function sendResponse(ResponseInterface $response) {
        // this seems wrong -----^

        header(vsprintf('HTTP/1.1 %d %s', [
            $response->getStatusCode(),
            $response->getStatusMessage()
        ]), true, $response->getStatusCode());
    }
}

Come puoi vedere, il metodo sendResponse accetta un parametro ResponseInterface ma chiama getStatusMessage() che non fa parte dell'interfaccia ma solo qualcosa implementato in AwesomeResponse , che implementa ResponseInterface .

L'applicazione si arresta in modo anomalo al runtime quando viene passato un oggetto Response mentre prova a chiamare il metodo inesistente getStatusMessage() . Pertanto, l'implementazione corretta sarebbe:

public function sendResponse(AwesomeResponse $response) {
    // ...stuff
}

Ma AwesomeResponse non è un'interfaccia, quindi come posso programmare su un'interfaccia?

    
posta rink.attendant.6 12.12.2015 - 03:34
fonte

3 risposte

5

Hmm - un esempio meno banale potrebbe aiutare. Considera Executor , che è un'interfaccia che definisce un contratto per eseguendo Runnables .

Ora, se scrivi AwesomeService in questo modo:

class AwesomeService {
    // ...
    public void runAllPendingTasks(ScheduledThreadPoolExecutor executor) {
        for(Runnable task : pendingTasks) {
            executor.execute(task);
        }
    }
 }

Quindi il mio codice, cercando di chiamare il tuo, ha bisogno di trovare ScheduledThreadPoolExecutor in giro da qualche parte. D'altra parte, se la firma del metodo ammette che le tue esigenze possono essere soddisfatte da qualsiasi cosa che implementi Executor

class AwesomeService {
    // ...
    public void runAllPendingTasks(Executor executor) {
        for(Runnable task : pendingTasks) {
            executor.execute(task);
        }
    }
 }

Il mio codice può farti passare il mio Executor personalizzato e migliorare ciò che accade effettivamente quando esegui le tue attività. Ad esempio, potrei voler monitorare la frequenza con cui eseguiamo le attività, in modo da poter generare metriche in tempo reale per monitorare lo stato di salute dell'applicazione.

class MeteredExecutor implements Executor {
    private final Executor impl;
    private final io.dropwizard.metrics.Meter meter;

    MeteredExecutor(...) {...}

    public void execute(Runnable command) {
        meter.mark();
        impl.execute(command);
    }
}

OK, quindi perché un'interfaccia non sembra essere di aiuto per AwesomeResponse ? La risposta principale è l'incapsulamento: il MeteredExecutor fornisce il servizio di esecuzione utilizzando il proprio stato interno per soddisfare una richiesta. Quello che non fa è condividere quello stato interno. Osserva che AwesomeService chiama execute e lascia che MeteredExecutor faccia il lavoro, invece di chiamare getMeter().mark() e getImpl.execute(command) .

"Dammi il tuo stato interno" è una motivazione davvero pessima per un'interfaccia.

AwesomeResponse sta esponendo i suoi dati. Non è necessariamente una brutta cosa; nel tuo esempio come scritto, AwesomeResponse è essenzialmente una rappresentazione di un messaggio che sta per attraversare un confine di applicazione. In altre parole, è un tipo di valore che costituisce parte dell'api del tuo servizio; ovviamente espone i suoi dati, ecco a cosa serve!

Richiamare lo stesso punto in un modo diverso: notare che nel caso di AwesomeService e delle interfacce, lo stato viene passato verso l'interno. Il mio stato è un esecutore e lo passo a AwesomeService. Lo stato di AwesomeService è un compito, un passaggio che passa all'Esecutore, che a sua volta lo trasferisce verso un altro Executor e le tartarughe fino in fondo. Le interfacce funzionano perfettamente per questo.

D'altra parte, in AwesomeResponse, lo stato sta venendo fuori. Scrivere interfacce per recuperare lo stato è solo un lavoro extra.

MA

L'incapsulamento è una buona cosa, e AwesomeResponse non è esattamente al limite dell'applicazione. Potremmo riscrivere le cose in modo tale che lo stato sia passato verso l'interno?

interface Writer {
    void header(int code, String message);
}

interface Response {
    void writeTo(Writer writer);
}

class AwesomeResponse implements Response {
    void writeTo(Writer writer) {
        writer.header(this.code, this.message);
    }
}

class NotSoAwesomeResponse implements Response {
    void writeTo(Writer writer) {
        writer.header(this.code, String.valueOf(this.code));
    }
}

class ServerWriter implements Writer {
    void header(int code, String message) {
        header(vsprintf('HTTP/1.1 %d %s', [
            code,
            message
        ]), true, code);
    }
}

class Server {
    public function sendResponse(Response response) {
        ServerWriter writer = new ServerWriter();
        response.writeTo(writer);
    }
}
    
risposta data 12.12.2015 - 07:22
fonte
8

Questo è il punto. Se dipendi da dettagli concreti della classe, non stai codificando per un'interfaccia per definizione.

Questo significa che non puoi eliminare quei dettagli di implementazione e sostituirli con altri. Poiché il cambiamento è inevitabile negli affari, questo ti causerà problemi lungo la strada. Dovrai aggiungere questa funzionalità all'interfaccia (o creare un'altra interfaccia per quell'altra funzionalità) e lavorare con quel contratto, non i dettagli che stai usando ora.

    
risposta data 12.12.2015 - 04:40
fonte
2

Prima di tutto: questo non ha nulla a che fare con le interfacce che sono definite dalla parola chiave con lo stesso nome.

Il consiglio non significa altro che non fare affidamento su un'implementazione concreta, piuttosto su un comportamento astratto ben definito .

Il mio knowlede di PHP è vicino a 0 - quindi mi scuso rispondendo in modo più generale.

Supponiamo che tu abbia un'applicazione di e-commerce e oggetti di dominio come order . Ogni order è stato creato in un dato momento. Spesso ci sono operazioni filtering per ottenere un sottoinsieme di ordini entro un determinato periodo di tempo. Potresti scrivere condizioni come order.getCreated() > concreteDate . Oltre ad altri odori di codice, questo è negativo per un altro motivo: Leghi il tuo codice al fatto di implementazione, che l'operazione > funzioni con il tuo codice. Se l'implementazione di date viene mai modificata, ad esempio se utilizzi un altro oggetto Date con altri operatori (ad esempio .after() ), il tuo codice non funziona.

Se hai progettato order in modo corretto, definiresti l'operazione come order.createdAfter(date) , definirai interface aka. un modo ben definito per ottenere le informazioni necessarie da un ordine, che è esso stesso indipendente dall'implementazione concreta. L'input è un Date e il risultato è un boolean .

È sempre buona pratica scegliere il tipo di dati più astratto per i valori di ritorno per evitare ipotesi (= dipendenze) sulle implementazioni concrete. Ad esempio, sarebbe una buona pratica in Java restituire Iterable<T> invece di concreto List per evitare asserzioni sul comportamento della collezione risultante.

tl; dr

Leggi interface come comportamento ben definito indipendente dalle implementazioni concrete.

    
risposta data 12.12.2015 - 18:27
fonte