Uso delle interfacce per codice liberamente accoppiato

10

Sfondo

Ho un progetto che dipende dall'uso di un certo tipo di dispositivo hardware, mentre non importa chi fa quel dispositivo hardware finché fa quello che mi serve. Detto questo, anche due dispositivi che dovrebbero fare la stessa cosa avranno differenze quando non sono realizzati dallo stesso produttore. Quindi sto pensando di usare un'interfaccia per disaccoppiare l'applicazione dal particolare make / model del dispositivo in questione, e invece l'interfaccia copre solo la funzionalità di livello più alto. Ecco a cosa sto pensando la mia architettura sarà:

  1. Definisci un'interfaccia in un progetto C # IDevice .
  2. Avere un concreto in una libreria definita in un altro progetto C #, che verrà utilizzato per rappresentare il dispositivo.
  3. Chiedi al dispositivo concreto di implementare l'interfaccia IDevice .
  4. L'interfaccia IDevice potrebbe avere metodi come GetMeasurement o SetRange .
  5. Fai in modo che l'applicazione conosca il calcestruzzo e passa il calcestruzzo al codice dell'applicazione che utilizza ( non implementa ) il dispositivo IDevice .

Sono abbastanza sicuro che questo sia il modo giusto per farlo, perché poi potrò cambiare il dispositivo utilizzato senza influire sull'applicazione (che a volte sembra succedere). In altre parole, non importa come le implementazioni di GetMeasurement o SetRange funzionino effettivamente attraverso il calcestruzzo (come possono differire tra i produttori del dispositivo).

L'unico dubbio nella mia mente è che sia l'applicazione sia la classe concreta del dispositivo dipendono entrambi dalla libreria che contiene l'interfaccia IDevice . Ma è una brutta cosa?

Inoltre, non vedo come l'applicazione non debba conoscere il dispositivo, a meno che il dispositivo e IDevice si trovino nello stesso spazio dei nomi.

Domanda

Questo sembra l'approccio giusto per implementare un'interfaccia per disaccoppiare la dipendenza tra la mia applicazione e il dispositivo che usa?

    
posta Snoop 09.05.2016 - 18:55
fonte

3 risposte

5

Penso che tu abbia una buona conoscenza di come funziona il software disaccoppiato:)

The only doubt in my mind, is that now both the application, and the device's concrete class both depend on the library that contains the IDevice interface. But, is that a bad thing?

Non è necessario!

I also don't see how the application won't need to know about the device, unless the device and IDevice are in the same namespace.

Puoi gestire tutti questi problemi con Struttura del progetto .

Il modo in cui lo faccio in genere:

  • Metti tutte le mie cose astratte in un progetto Common . Qualcosa come MyBiz.Project.Common . Altri progetti sono liberi di farvi riferimento, ma potrebbero non fare riferimento ad altri progetti.
  • Quando creo un'implementazione concreta di un'astrazione, la metto in un progetto separato. Qualcosa come MyBiz.Project.Devices.TemperatureSensors . Questo progetto farà riferimento al progetto Common .
  • Ho quindi il mio progetto Client che è il punto di accesso alla mia applicazione (qualcosa come MyBiz.Project.Desktop ). All'avvio, l'applicazione passa attraverso un processo Bootstrapping in cui configuro i mapping di astrazione / implementazione di calcestruzzo. Posso istanziare il mio concreto IDevices come WaterTemperatureSensor e IRCameraTemperatureSensor qui, oppure posso configurare servizi come fabbriche o un contenitore IoC per istanziare i tipi concreti giusti per me più avanti.

La cosa fondamentale qui è che solo il tuo progetto Client deve essere a conoscenza sia del progetto Common astratto sia di tutti i progetti di implementazione concreta. Confinando la mappatura concreta e astratta del tuo codice Bootstrap, puoi rendere il resto della tua applicazione piacevolmente inconsapevole dei tipi concreti.

Codice ftw:)

    
risposta data 09.05.2016 - 20:03
fonte
3

Sì, sembra l'approccio giusto. No, non è una cosa negativa per l'applicazione e la libreria di dispositivi dipendere dall'interfaccia, specialmente se si ha il controllo dell'implementazione.

Se si teme che i dispositivi possano non implementare sempre l'interfaccia, per qualsiasi motivo, è possibile utilizzare il modello dell'adattatore e adattare l'implementazione concreta dei dispositivi all'interfaccia.

Modifica

Nell'affrontare la tua quinta preoccupazione, pensa a una struttura come questa (presumo che tu abbia il controllo sulla definizione dei tuoi dispositivi):

Hai una libreria principale. In esso c'è un'interfaccia chiamata IDevice.

Nella libreria dei dispositivi, hai un riferimento alla tua libreria principale e hai definito una serie di dispositivi che implementano tutti i dispositivi ID. Hai anche una fabbrica che sa come creare diversi tipi di IDevice.

Nella tua applicazione includi riferimenti alla libreria principale e alla libreria dei dispositivi. L'applicazione ora utilizza la fabbrica per creare istanze di oggetti conformi all'interfaccia IDevice.

Questo è uno dei tanti modi possibili per risolvere i tuoi dubbi.

ESEMPI:

namespace Core
{
    public interface IDevice { }
}


namespace Devices
{
    using Core;

    class DeviceOne : IDevice { }

    class DeviceTwo : IDevice { }

    public class Factory
    {
        public IDevice CreateDeviceOne()
        {
            return new DeviceOne();
        }

        public IDevice CreateDeviceTwo()
        {
            return new DeviceTwo();
        }
    }
}

// do not implement IDevice
namespace ThirdrdPartyDevices
{

    public class ThirdPartyDeviceOne  { }

    public class ThirdPartyDeviceTwo  { }

}

namespace DeviceAdapters
{
    using Core;
    using ThirdPartyDevices;

    class ThirdPartyDeviceAdapterOne : IDevice
    {
        private ThirdPartyDeviceOne _deviceOne;

        // use the third party device to adapt to the interface
    }

    class ThirdPartyDeviceAdapterTwo : IDevice
    {
        private ThirdPartyDeviceTwo _deviceTwo;

        // use the third party device to adapt to the interface
    }

    public class AdapterFactory
    {
        public IDevice CreateThirdPartyDeviceAdapterOne()
        {
            return new ThirdPartyDeviceAdapterOne();
        }

        public IDevice CreateThirdPartyDeviceAdapterTwo()
        {
            return new ThirdPartyDeviceAdapterTwo();
        }
    }
}

namespace Application
{
    using Core;
    using Devices;
    using DeviceAdapters;

    class App
    {
        void RunInHouse()
        {
            var factory = new Factory();
            var devices = new List<IDevice>() { factory.CreateDeviceOne(), factory.CreateDeviceTwo() };
            foreach (var device in devices)
            {
                // call IDevice  methods.
            }
        }

        void RunThirdParty()
        {
            var factory = new AdapterFactory();
            var devices = new List<IDevice>() { factory.CreateThirdPartyDeviceAdapterOne(), factory.CreateThirdPartyDeviceAdapterTwo() };
            foreach (var device in devices)
            {
                // call IDevice  methods.
            }
        }
    }
}
    
risposta data 09.05.2016 - 19:09
fonte
1

The only doubt in my mind, is that now both the application, and the device's concrete class both depend on the library that contains the IDevice interface. But, is that a bad thing?

Devi pensare il contrario. Se non si va in questo modo, l'applicazione dovrebbe conoscere tutti i diversi dispositivi / implementazioni. Quello che dovresti tenere a mente è che cambiare quell'interfaccia potrebbe rompere la tua applicazione se è già in circolazione, quindi dovresti progettare con cura questa interfaccia.

Does this seem like the right approach for implementing an interface to decouple the dependency between my application and the device that it uses?

Semplicemente sì

how the concrete actually gets to the app

L'applicazione può caricare l'assembly concreto (dll) in fase di esecuzione. Vedi: link

Se è necessario scaricare / caricare dinamicamente un "driver" di questo tipo, è necessario caricare l'assembly in un AppDomain .

    
risposta data 09.05.2016 - 19:06
fonte