Accoppiamento. Migliori pratiche

10

In seguito a questa discussione ho iniziato

Il pattern Singleton

Mi ha fatto pensare a come sono accoppiate le mie classi e come ottenere il miglior accoppiamento. Tieni presente che sono un nuovo programmatore (4 mesi nel mio primo lavoro) e questa è davvero la prima considerazione che ho dato a questo, e sono molto desideroso di capire il concetto.

Quindi, che cosa costituisce esattamente l'accoppiamento libero rispetto all'accoppiamento pesante? Nel mio attuale (e primo progetto), sto lavorando al progetto ac # winforms, in cui la sezione GUI crea oggetti e sottocapitoli ai loro eventi, quando vengono attivati, la GUI crea un altro oggetto (in questo esempio, un datagridview (una classe che ho creato che avvolge un datagridview standard e aggiunge funzionalità aggiuntive) e lo allega alla GUI. Questo cattivo accoppiamento o buono?

Non voglio davvero entrare in cattive abitudini e iniziare a programmare male, quindi apprezzerei le tue risposte.

    
posta Darren Young 18.01.2011 - 11:53
fonte

7 risposte

11

PER Rendere il tuo codice liberamente abbinato ecco alcune semplici cose da ricordare:

Parte 1:

Tecnicamente noto come "Separation of Concern". Ogni classe ha un ruolo specifico, dovrebbe gestire la logica aziendale o la logica dell'applicazione. Cerca di evitare le lezioni che combinano entrambe le responsabilità. Ad esempio, una classe che gestisce (a lungo termine) i dati è una logica applicativa mentre una classe che utilizza i dati è una logica aziendale.

Personalmente mi riferisco a questo (nel mio piccolo mondo) come create it or use it . Una classe dovrebbe creare un oggetto o usare un oggetto che non dovrebbe mai fare entrambi.

Parte 2:

Come implementare la separazione delle preoccupazioni.
Come punto di partenza ci sono due semplici tecniche:

Nota: gli schemi di progettazione non sono assoluti.
Dovrebbero essere personalizzati per la situazione, ma hanno un tema di fondo simile a tutte le applicazioni. Quindi non guardare gli esempi qui sotto e dire che devo seguire questo rigidamente; questi sono solo esempi (e leggermente inventati).

Iniezione di dipendenza :

Qui è dove si passa un oggetto utilizzato da una classe. L'oggetto che passi in base a un'interfaccia in modo che la tua classe sappia cosa fare con esso ma non ha bisogno di conoscere l'effettiva implementazione.

class Tokenizer
{
    public:
        Tokenizer(std::istream& s)
            : stream(s)
        {}
        std::string nextToken() { std::string token; stream >> token;return token;}
    private:
        std::istream& stream;
};

Qui iniettiamo il flusso in un Tokenizer. Il tokenizer non sa di che tipo è il flusso fintanto che implementa l'interfaccia di std :: istream.

Modello localizzatore di servizio :

Il modello di localizzazione del servizio è una leggera variazione dell'iniezione di dipendenza. Piuttosto che dare un oggetto che può usare, tu passi un oggetto che sa come individuare (creare) l'oggetto che vuoi usare.

class Application
{
     public:
         Application(Persister& p)
             : persistor(p)
         {}

         void save()
         {
             std::auto_ptr<SaveDialog> saveDialog = persistor.getSaveDialog();
             saveDialog.DoSaveAction();
         }

         void load()
         {
             std::auto_ptr<LoadDialog> loadDialog = persistor.getLoadDialog();
             loadDialog.DoLoadAction();
         }
    private:
        Persister& persistor;
};

Qui passiamo l'oggetto dell'applicazione a un oggetto resistore. Quando si esegue un'azione di salvataggio / caricamento, utilizza la persistenza per creare un oggetto che sappia effettivamente come eseguire l'azione. Nota: di nuovo il persistenza è un'interfaccia e puoi fornire diverse implementazioni a seconda della situazione.

È utile quando è richiesto un oggetto unico potentially ogni volta che istanziate un'azione.

Personalmente ritengo che questo sia particolarmente utile nella scrittura dei test unitari.

Nota dei pattern:

I modelli di design sono un argomento enorme per se stesso. Questo non è affatto un elenco esclusivo di schemi che puoi usare per aiutare con l'accoppiamento libero; questo è solo un punto di partenza comune.

Con l'esperienza ti renderai conto che stai già usando questi modelli è solo che non hai usato i loro nomi formali. Standardizzando i loro nomi (e facendo in modo che tutti li imparino) scopriamo che è facile e più veloce comunicare idee.

    
risposta data 18.01.2011 - 14:50
fonte
6

Sono uno sviluppatore di ASP.NET quindi non so molto sull'accoppiamento di WinForms, ma conosco un po 'le applicazioni web N-Tier, assumendo un'architettura di applicazione a 3 livelli di interfaccia utente, dominio, livello di accesso ai dati (DAL).

L'accoppiamento lento riguarda le astrazioni.

Come @MKO afferma se è possibile sostituire un assieme con un altro (ad esempio un nuovo progetto di interfaccia utente che utilizza il progetto di dominio, un nuovo DAL che salva su un foglio di calcolo anziché su un database), allora vi è un accoppiamento lento. Se il tuo Dominio e DAL dipendono da progetti più in basso lungo la catena, allora l'accoppiamento potrebbe essere più lento.

Un aspetto di qualcosa che è strettamente collegato è se è possibile sostituire un oggetto con un altro che implementa la stessa interfaccia. Non dipende dall'oggetto reale, ma dalla descrizione astratta di ciò che fa (la sua interfaccia).
Accoppiamento lento, interfacce e Iniettori di dipendenza (DI) e Inversion of Control (IoC) sono utili per l'aspetto di isolamento della progettazione per la testabilità.

es. Un oggetto nel progetto di interfaccia utente chiama un oggetto repository nel progetto Domain.
Puoi creare un oggetto falso che implementa la stessa interfaccia del repository utilizzato dal codice sotto test, quindi scrivere un comportamento speciale per i test ( stub per impedire il codice di produzione che salva / cancella / viene chiamato e mock che fungono da stub e tengono traccia dello stato dell'oggetto falso a scopo di test).
Ciò significa che l'unico codice di produzione chiamato è ora solo nell'oggetto UI, il test sarà contro quel solo metodo e qualsiasi errore di test isolerà il difetto in quel metodo.

Inoltre, nel menu Analizza in VS (a seconda della versione che hai) ci sono strumenti per calcolare le metriche del codice per il tuo progetto, uno dei quali è Class Coupling, ci saranno maggiori informazioni su questo nella documentazione MSDN.

Non lasciare che TOO sia impantanato nel minutae di accoppiamento lento, se non ci sono possibilità che le cose vengano riutilizzate (ad esempio un progetto di dominio con più di una UI) e la vita di il prodotto è piccolo, quindi l'accoppiamento libero diventa meno importante (verrà comunque preso in considerazione), ma rimarrà comunque la responsabilità di architetti / leader tecnici che esamineranno il tuo codice.

    
risposta data 18.01.2011 - 12:33
fonte
3

Coupling refers to the degree of direct knowledge that one class has of another. This is not meant to be interpreted as encapsulation vs. non-encapsulation. It is not a reference to one class's knowledge of another class's attributes or implementation, but rather knowledge of that other class itself. Strong coupling occurs when a dependent class contains a pointer directly to a concrete class which provides the required behavior. The dependency cannot be substituted, or its "signature" changed, without requiring a change to the dependent class. Loose coupling occurs when the dependent class contains a pointer only to an interface, which can then be implemented by one or many concrete classes. The dependent class's dependency is to a "contract" specified by the interface; a defined list of methods and/or properties that implementing classes must provide. Any class that implements the interface can thus satisfy the dependency of a dependent class without having to change the class. This allows for extensibility in software design; a new class implementing an interface can be written to replace a current dependency in some or all situations, without requiring a change to the dependent class; the new and old classes can be interchanged freely. Strong coupling does not allow this. Ref Link.

    
risposta data 18.01.2011 - 13:20
fonte
3

Dai un'occhiata ai 5 principi SOLID . Aderendo all'SRP, l'ISP e il DIP diminuiranno sensibilmente l'accoppiamento, essendo il DIP di gran lunga il più potente. È il principio fondamentale al di sotto del già citato DI .

Inoltre, GRASP vale la pena dare un'occhiata. Si tratta di uno strano mix tra concetti astratti (che sarà difficile da implementare all'inizio) e modelli concreti (che potrebbero effettivamente essere di aiuto), ma la bellezza è probabilmente l'ultima delle tue preoccupazioni in questo momento.

E per ultimo, potresti trovare questa sezione su IoC molto utile, come punto di partenza per le tecniche comuni.

In effetti, ho trovato una domanda su stackoverflow , dove mostro l'applicazione di SOLID su un problema concreto. Potrebbe essere una lettura interessante.

    
risposta data 18.01.2011 - 13:10
fonte
1

Secondo Wikipedia:

In computing and systems design a loosely coupled system is one where each of its components has, or makes use of, little or no knowledge of the definitions of other separate components.

Il problema con l'accoppiamento stretto rende difficile apportare modifiche. (Molti scrittori sembrano suggerire che ciò causa principalmente problemi durante la manutenzione, ma nella mia esperienza è rilevante anche durante lo sviluppo iniziale.) Ciò che tende ad accadere in sistemi strettamente accoppiati è che una modifica di un modulo nel sistema richiede ulteriori cambiamenti nei moduli a cui è accoppiato. Più spesso, questo richiede più modifiche in altri moduli e così via.

Al contrario, in un sistema liberamente accoppiato, i cambiamenti sono relativamente isolati. Sono quindi meno costosi e possono essere realizzati con maggiore sicurezza.

Nel tuo esempio particolare, la gestione degli eventi fornisce una separazione tra la GUI e i dati sottostanti. Tuttavia, sembra che ci siano altre aree di separazione che potresti esplorare. Senza ulteriori dettagli sulla tua particolare situazione, è difficile essere specifici. Tuttavia, potresti iniziare con un'architettura a 3 livelli che si separa:

  • Logica di archiviazione e recupero dei dati
  • Logica aziendale
  • Logica richiesta per eseguire l'interfaccia utente

Qualcosa da considerare è che per piccole applicazioni con un singolo sviluppatore, i vantaggi di imporre l'accoppiamento libero a tutti i livelli potrebbero non valerne la pena. D'altra parte, applicazioni più grandi e complesse con più sviluppatori sono indispensabili. Inizialmente, vi è un costo sostenuto per introdurre le astrazioni e educare gli sviluppatori che non conoscono il codice relativo alla sua architettura. Nel lungo periodo, tuttavia, l'accoppiamento libero offre vantaggi che superano di gran lunga i costi.

Se sei seriamente intenzionato a progettare sistemi accoppiati liberamente, leggi i principi SOLID e i modelli di progettazione.

La cosa importante da comprendere, tuttavia, è che questi schemi e principi sono proprio questo: modelli e principi. Sono regole non . Ciò significa che devono essere applicati in modo pragmatico e intelligente

Per quanto riguarda i pattern, è importante capire che non esiste una singola implementazione "corretta" di nessuno dei pattern. Né sono modelli di cookie-cutter per la progettazione della propria implementazione. Sono lì per dirti quale forma potrebbe avere una buona soluzione e per fornirti un linguaggio comune per comunicare le decisioni di progettazione con altri sviluppatori.

Tutto il meglio.

    
risposta data 18.01.2011 - 13:01
fonte
1

Utilizza l'iniezione di dipendenza, i modelli di strategia e gli eventi. In generale: leggete gli schemi di progettazione, sono tutti legati all'accoppiamento lento e alla riduzione delle dipendenze. Direi che gli eventi si avvicinano in modo approssimativo a quanto si otterrebbe mentre l'iniezione delle dipendenze ei modelli di strategia richiedono alcune interfacce.

Un buon trucco è quello di posizionare le classi in librerie / assiemi differenti e farle dipendere dal minor numero possibile di altre librerie, che ti costringono a rifattorizzare per usare meno dipendenze

    
risposta data 18.01.2011 - 11:58
fonte
0

Consentitemi di fornire una vista alternativa. Penso solo a questo in termini di classe essendo una buona API. L'ordine in cui vengono chiamati i metodi è ovvio. Quello che fanno è ovvio. Hai ridotto il numero di metodi al minimo necessario. Per esempi,

init, open, close

vs

setTheFoo, setBar, initX, getConnection, close

Il primo è ovvio e sembra una bella API. Il secondo potrebbe causare errori se richiamati nell'ordine sbagliato.

Non mi preoccupo troppo di dover modificare e ricompilare i chiamanti. Mantengo un sacco di codice in parte nuovo e circa 15 anni. Di solito voglio errori del compilatore quando apporto le modifiche. A volte interrompo intenzionalmente un'API per questo motivo. Mi dà la possibilità di considerare le ramificazioni su ciascun chiamante. Non sono un grande fan dell'iniezione di dipendenza perché voglio essere in grado di tracciare visivamente il mio codice senza caselle nere e voglio che il compilatore catturi il maggior numero possibile di errori.

    
risposta data 18.01.2011 - 19:42
fonte

Leggi altre domande sui tag