Come rimuovere le dipendenze dalle classi interne nell'interfaccia

3

Ho un componente software che fa parte di un prodotto software più grande. Il componente software vive nel suo spazio dei nomi Component . Anche il componente ha un'interfaccia (una parte di esso è in basso) che altri componenti usano:

class IComponent {
    ...
    virtual void AddClient(unsigned int id, Component::Client client) = 0;
    virtual void RemoveClient(unsigned int id) = 0;
    virtual void PrintClients() = 0;
    ...
};

Ciò che non mi piace dell'interfaccia è la dipendenza di alcuni dei suoi metodi sulle classi interne del componente. AddClient() ad esempio dipende dalla classe Component::Client .

Tale dipendenza è accettabile? Quali sono le migliori pratiche per rimuovere tali dipendenze?

    
posta Konstantin 30.12.2016 - 07:50
fonte

3 risposte

4

Ci sono quattro possibilità che vedo qui:

Possibilità 1 - NBD

Non è un grosso problema fintanto che esporre Component::Client non perde gran parte dei dettagli di implementazione. Se ritieni che questo sia il tipo di interfaccia che vuoi esporre e non ti aspetti cambiamenti radicali che potrebbero invalidare questa interfaccia, potrebbe essere semplicemente No Big Deal ™.

Possibilità 2 - Component::Client s sono principalmente dati

Se l'interfaccia esterna di Component::Client è per lo più di dati, invece di richiederne uno direttamente, basta chiedere le parti di dati necessarie per crearne una, e basta invece renderla internamente. Se è presente solo una o alcune implementazioni interne fisse di Component::Client , potresti trovarti in questa situazione. Quindi se il tuo codice chiamante è simile a:

Component::Client client(data, filters, listeners, etc);
component.AddClient(11, client);
// client is never used again

Quindi per quanto riguarda il codice chiamante, il client è solo un archivio dati passato al componente. Anche se il client può avere un carico di comportamento su di esso, se è interessante solo per il componente, allora esternamente è irrilevante e potresti cambiare il metodo in modo che sia:

component.AddClient(11, data, filters, listeners, etc);
// What client?

Il metodo AddClient crea e memorizza internamente l'istanza Component::Client , ma il codice esterno non lo saprebbe mai. (Se la creazione di un'istanza Component::Client è particolarmente complicata, ci sono modi per piegare modelli come un costruttore per quello che potrebbe essere necessario.)

Possibilità 3 - Component::Client s sono principalmente di comportamento

Se stai passando un'istanza Component::Client a un'istanza IComponent principalmente per fornire un comportamento, esponendo Component::Client potrebbe non essere evitabile. Usarlo come un oggetto strategico è una forma di fornire un comportamento, per esempio. Esistono due modi in cui è possibile che venga fornito un comportamento.

Possibilità 3, parte 1 - Il codice cliente crea un Component::Client da un numero fisso di implementazioni fornite

Se ci sono alcune implementazioni di Component::Client che il tuo codice cliente deve scegliere e poi passare, potresti invece passare quel codice client in un enum (o simile). L'enumerazione indica al metodo AddClient quale tipo di implementazione di Component::Client creare e archiviare internamente. Nota che sebbene la classe non sia esposta, viene invece esposta una enumerazione.

Possibilità 3, parte 2 - Il codice client estende Component::Client per fornire implementazioni personalizzate

Qui non puoi fare molto ma esporre Component::Client . Il codice cliente deve vederlo per estenderlo. Se hai solo bisogno di un po 'di comportamento aggiuntivo per un'implementazione interna altrimenti molto grande, potresti esporre un'interfaccia di strategia molto più semplice affinchè i clienti implementino e nascondano la maggior parte del Component::Client . Potresti anche riuscire a riutilizzare un'interfaccia preesistente (nella libreria standard o altrove nella tua) per soddisfare le tue esigenze.

Possibilità 4 - Possibilità 2 + Possibilità 3

Se hai un Component::Client che fornisce sia il comportamento che i dati, puoi guardarlo in due modi. In primo luogo, potrebbe avere senso allora, e dovresti lasciarlo da solo (aka, possibilità 1). In alternativa, è possibile suddividere il comportamento e i dati, quindi fare Possibility 2 e Possibility 3 con le parti appena suddivise. Tieni presente la singola responsabilità mentre fai questo: una classe dovrebbe avere una sola responsabilità, non di più e non di meno.

Come nota finale, voglio sottolineare che in tutti i casi, l'idea di un client componente esiste ancora per il codice client. Spesso, avere una classe o un tipo da rappresentare è utile, anche se solo un valore di dati o un oggetto con valore comportamentale.

    
risposta data 30.12.2016 - 20:40
fonte
3

AddClient assomiglia più a RegisterClient, dove un'altra classe sta registrando il client con qualche intero, in modo che questo ID intero possa essere usato come riferimento ovunque.

Questa è una buona interfaccia. Il client fa parte di Component e il metodo accetta solo gli oggetti di Component. Questo è un buon incapsulamento.

Se al posto di Component :: Client qualche altra classe sarebbe stata lì come SomeOtherComponent :: Client, allora ci sarebbe stato posto per alzare le sopracciglia. Ma il campione menzionato dirò che va bene.

    
risposta data 30.12.2016 - 09:09
fonte
1

Suppongo che tu non possa fare molto.

Una volta che tu (o qualcun altro) hai scritto un'interfaccia, si applicano le stesse regole applicate alle API: in pratica sei bloccato con esso.

Le interfacce non devono essere progettate per essere modificate perché una volta che ci sono un sacco di altri codici iniziano a farvi affidamento.

Se tuttavia vuoi che la tua prossima nuova interfaccia sia progettata meglio ... ma penso che sia un'altra domanda.

    
risposta data 30.12.2016 - 11:18
fonte

Leggi altre domande sui tag