Accoppiamento. Migliori pratiche


11

Seguendo questo thread ho iniziato

Il modello Singleton

Mi ha fatto pensare a quanto sono accoppiate le mie classi e al modo migliore per ottenere un accoppiamento libero. Per favore, 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, 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 sottoscrive i loro eventi, quando vengono attivati, la GUI crea un altro oggetto (in questo esempio, un datagridview (una classe che ho creato che avvolge una datagridview standard e aggiunge funzionalità aggiuntive) e la collega alla GUI. È un accoppiamento errato o buono?

Non voglio davvero prendere cattive abitudini e iniziare a scrivere male, quindi apprezzerei le tue risposte.

Risposte:


11

Per rendere il tuo codice liberamente accoppiato qui ci sono alcune cose semplici da ricordare:

Parte 1:

Tecnicamente noto come "Separazione della preoccupazione". Ogni classe ha un ruolo specifico, dovrebbe gestire la logica aziendale o la logica dell'applicazione. Cerca di evitare la classe che combina entrambe le responsabilità. vale a dire una classe che gestisce i dati (a lungo termine) è la logica dell'applicazione mentre una classe che utilizza i dati è la 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: i motivi di progettazione non sono assoluti.
Dovrebbero essere personalizzati in base alla situazione ma hanno un tema sottostante simile a tutte le applicazioni. Quindi non guardare gli esempi qui sotto e dire che devo seguirlo rigidamente; questi sono solo esempi (e sono leggermente inventati).

Iniezione delle dipendenze :

Qui è dove passi un oggetto che usa una classe. L'oggetto che passi in base a un'interfaccia in modo che la tua classe sappia cosa farne ma non ha bisogno di conoscere l'implementazione effettiva.

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 è lo stream purché implementi l'interfaccia di std :: istream.

Modello di localizzazione di servizio :

Il modello del localizzatore di servizio è una leggera variazione sull'iniezione di dipendenza. Invece di dare un oggetto che può usare, gli passi un oggetto che sa come localizzare (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 all'oggetto applicazione un oggetto persistenza. Quando si esegue un'azione di salvataggio / caricamento, utilizza il persistor per creare un oggetto che sappia effettivamente come eseguire l'azione. Nota: ancora una volta il persistor è un'interfaccia e puoi fornire implementazioni diverse a seconda della situazione.

Ciò è utile quando potentiallyè richiesto un oggetto unico ogni volta che si crea un'istanza di un'azione.

Personalmente trovo che ciò sia particolarmente utile nella scrittura di unit test.

Nota dei motivi:

I modelli di progettazione sono un argomento enorme per sé. Questo non è affatto un elenco esclusivo di modelli che è possibile utilizzare per facilitare l'accoppiamento; questo è solo un punto di partenza comune.

Con l'esperienza ti renderai conto che stai già usando questi schemi è solo che non hai usato i loro nomi formali. Standardizzando i loro nomi (e convincendo tutti a impararli) scopriamo che è facile e veloce comunicare idee.


3
@Darren Young: Grazie per averlo accettato. Ma la tua domanda ha solo tre ore. Ritornerei tra circa un giorno per verificare che gli altri non abbiano fornito risposte migliori.
Martin York,

Grazie per l'eccellente risposta. Per quanto riguarda la separazione delle preoccupazioni ... Ho delle classi che richiedono alcuni dati per il suo uso e quindi faccio qualcosa con quei dati. È così che in passato ho progettato lezioni. Sarebbe quindi meglio creare una classe che ottenga i dati e li adatti al modulo corretto e quindi un'altra classe per utilizzare effettivamente i dati?
Darren Young,

@Darren Young: Come per tutta la programmazione, la linea è grigia e sfocata non ben definita. È difficile dare una risposta esatta senza leggere il tuo codice. Ma quando parlo managing the datami riferisco alle variabili (non ai dati effettivi). Quindi cose come i puntatori devono essere gestite in modo da non perdere. Ma i dati possono essere iniettati o il modo in cui i dati vengono recuperati può essere astratto (in modo che la classe possa essere riutilizzata con diversi metodi di recupero dei dati). Mi dispiace, non posso essere più preciso.
Martin York,

1
@Darren Young: come notato da @StuperUser nella sua risposta. Non esagerare (AKA minutae of loose coupling(adoro quella parola minutae)). Il segreto della programmazione è imparare quando usare le tecniche. L'uso eccessivo può portare a un groviglio di codice.
Martin York,

@Martin - grazie per il consiglio. Penso che sia qui che sto lottando al momento ..... Tendo a preoccuparmi costantemente dell'architettura del mio codice e anche a cercare di imparare il linguaggio specifico che sto usando C #. Immagino che arriverà con l'esperienza e il mio desiderio di imparare queste cose. Apprezzo i tuoi commenti.
Darren Young,

6

Sono uno sviluppatore ASP.NET, quindi non so molto sull'accoppiamento di WinForms, ma conosco un po 'dalle app Web di livello N, ipotizzando un'architettura di applicazione a 3 livelli di UI, Domain, Data Access Layer (DAL).

L'accoppiamento allentato riguarda le astrazioni.

Come afferma @MKO se è possibile sostituire un assembly con un altro (ad es. Un nuovo progetto UI che utilizza il progetto Dominio, un nuovo DAL che salva in un foglio di calcolo anziché in un database), allora c'è un accoppiamento libero. Se il tuo dominio e DAL dipendono da progetti più in basso nella catena, l'accoppiamento potrebbe essere più lento.

Un aspetto di qualcosa che è liberamente accoppiato è se puoi 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, interfacce e Iniettori di dipendenza (DI) e Inversion of Control (IoC) allentati sono utili per l'aspetto di isolamento della progettazione per la testabilità.

Ad esempio, un oggetto nel progetto UI chiama un oggetto Repository nel progetto Dominio.
È possibile creare un oggetto falso che implementa la stessa interfaccia del repository utilizzato dal codice in prova, quindi scrivere un comportamento speciale per i test ( stub per impedire il codice di produzione che salva / elimina / viene chiamato e beffa che agiscono come 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à solo contro quel metodo e tutti gli errori di test isoleranno il difetto con 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 è l'accoppiamento di classe, ci saranno maggiori informazioni su questo nella documentazione MSDN.

Non farti impantanare TROPPO nei minuti di accoppiamento lento, se NON c'è alcuna possibilità che le cose vengano riutilizzate (ad esempio un progetto di dominio con più di un'interfaccia utente) e la durata del prodotto è piccola, quindi l'accoppiamento lento diventa inferiore di priorità (verrà comunque preso in considerazione), ma rimarrà comunque la responsabilità degli architetti / responsabili della tecnologia che revisioneranno il codice.


3

L'accoppiamento si riferisce al grado di conoscenza diretta che una classe ha di un'altra . Questo non deve essere interpretato come incapsulamento o non incapsulamento. Non è un riferimento alla conoscenza di una classe degli attributi o dell'implementazione di un'altra classe, ma piuttosto alla conoscenza dell'altra classe stessa. L'accoppiamento forte si verifica quando una classe dipendente contiene un puntatore direttamente a una classe concreta che fornisce il comportamento richiesto. La dipendenza non può essere sostituita o la sua "firma" modificata, senza richiedere una modifica alla classe dipendente. L'accoppiamento allentato si verifica quando la classe dipendente contiene un puntatore solo a un'interfaccia, che può quindi essere implementata da una o più classi concrete.La dipendenza della classe dipendente è da un "contratto" specificato dall'interfaccia; un elenco definito di metodi e / o proprietà che devono fornire le classi di implementazione. Qualsiasi classe che implementa l'interfaccia può quindi soddisfare la dipendenza di una classe dipendente senza dover cambiare la classe. Ciò consente l'estensibilità nella progettazione del software; una nuova classe che implementa un'interfaccia può essere scritta per sostituire una dipendenza corrente in alcune o tutte le situazioni, senza richiedere una modifica alla classe dipendente; le nuove e le vecchie classi possono essere scambiate liberamente. Un accoppiamento forte non lo consente. Collegamento di riferimento.


3

Dai un'occhiata ai 5 principi SOLIDI . Aderendo all'SRP, l'ISP e il DIP ridurranno significativamente l'accoppiamento, essendo il DIP di gran lunga il più potente. È il principio fondamentale alla base del già citato DI .

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

E infine, potresti trovare questa sezione su IoC abbastanza utile, come punto di accesso a tecniche comuni.

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


1

Secondo Wikipedia:

Nell'informatica e nella progettazione di sistemi, un sistema liberamente accoppiato è uno in cui ciascuno dei suoi componenti ha, o fa uso di, poca o nessuna conoscenza delle definizioni di altri componenti separati.

Il problema con l'accoppiamento stretto rende difficile apportare modifiche. (Molti autori 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 a un modulo nel sistema richiede ulteriori modifiche nei moduli a cui è accoppiato. Più spesso, ciò richiede più cambiamenti 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 certa 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 situazione particolare è difficile essere specifici. Tuttavia, potresti iniziare con un'architettura a 3 livelli che separa:

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

Qualcosa da considerare è che per le applicazioni piccole e una tantum con un singolo sviluppatore, i vantaggi di applicare un accoppiamento libero a tutti i livelli potrebbero non valere la pena. D'altro canto, sono indispensabili applicazioni più grandi e complesse con più sviluppatori. Inizialmente, ci sono costi nell'introduzione delle astrazioni e nell'educazione degli sviluppatori che non hanno familiarità con il codice relativo alla sua architettura. A lungo termine, tuttavia, l'accoppiamento libero offre vantaggi che superano di gran lunga i costi.

Se sei seriamente intenzionato a progettare sistemi debolmente accoppiati, leggi i principi e gli schemi di progettazione SOLID.

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

Per quanto riguarda gli schemi, è importante capire che non esiste un'unica implementazione "corretta" di nessuno degli schemi. Né sono modelli di cookie cutter per progettare la 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.

Ti auguro il meglio.


1

Usa l'iniezione delle dipendenze, i modelli di strategia e gli eventi. In generale: leggi gli schemi di progettazione, sono tutti incentrati sull'accoppiamento lento e sulla riduzione delle dipendenze. Direi che gli eventi sono quasi vagamente accoppiati come si farebbe mentre l'iniezione di dipendenza e gli schemi di strategia richiedono alcune interfacce.

Un buon trucco è posizionare le classi in librerie / assiemi diversi e farli dipendere dal minor numero possibile di librerie, il che ti costringerà a refactoring per utilizzare meno dipendenze


4
Il preservativo è un termine IT astratto o semplicemente non sto ottenendo il gioco di parole?
Darren Young,

3
È un altro nome per il contenitore di iniezione di dipendenza.
Mchl

2
Anche un ottimo modo per prevenire la diffusione di virus. stiamo parlando della stessa cosa?
sova,

1
L'accoppiamento pesante viene spesso eseguito sul backend
Homde, il

1
Impostare un titolo difficilmente abusa della formattazione
Homde,

0

Consentitemi di fornire una vista alternativa. Penso solo che in termini ogni classe sia 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 esempio,

init, apri, chiudi

contro

setTheFoo, setBar, initX, getConnection, close

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

Non mi preoccupo troppo di dover modificare e ricompilare i chiamanti. Mantengo MOLTO codice alcuni dei quali nuovi e circa 15 anni. Di solito voglio errori del compilatore quando apporto delle modifiche. A volte rompo di proposito un'API per questo motivo. Mi dà la possibilità di considerare le ramificazioni su ogni chiamante. Non sono un grande fan dell'iniezione di dipendenze perché voglio essere in grado di tracciare visivamente il mio codice senza scatole nere e voglio che il compilatore rilevi il maggior numero possibile di errori.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.