Uso de interfaces para código acoplado libremente

10

Antecedentes

Tengo un proyecto que depende del uso de cierto tipo de dispositivo de hardware, mientras que en realidad no importa quién haga ese dispositivo de hardware siempre que haga lo que necesito que haga. Dicho esto, incluso dos dispositivos que se supone que deben hacer lo mismo tendrán diferencias cuando no sean fabricados por el mismo fabricante. Por lo tanto, estoy pensando en usar una interfaz para desacoplar la aplicación de la marca / modelo particular del dispositivo involucrado y, en cambio, hacer que la interfaz cubra la funcionalidad de más alto nivel. Así es como creo que se verá mi arquitectura:

  1. Defina una interfaz en un proyecto de C # IDevice.
  2. Tenga un concreto en una biblioteca definida en otro proyecto de C #, que se usará para representar el dispositivo.
  3. Haga que el dispositivo concreto implemente la IDeviceinterfaz.
  4. La IDeviceinterfaz puede tener métodos como GetMeasuremento SetRange.
  5. Haga que la aplicación tenga conocimiento sobre el concreto y pase el concreto al código de la aplicación que utiliza ( no implementa ) el IDevicedispositivo.

Estoy bastante seguro de que esta es la forma correcta de hacerlo, porque así podré cambiar qué dispositivo se está utilizando sin afectar la aplicación (lo que parece suceder a veces). En otras palabras, no importará cómo las implementaciones GetMeasuremento SetRangerealmente funcionan a través del concreto (ya que pueden diferir entre los fabricantes del dispositivo).

La única duda en mi mente es que ahora tanto la aplicación como la clase concreta del dispositivo dependen de la biblioteca que contiene la IDeviceinterfaz. Pero, ¿es eso algo malo?

Tampoco veo cómo la aplicación no necesitará saber sobre el dispositivo, a menos que el dispositivo IDeviceesté en el mismo espacio de nombres.

Pregunta

¿Parece este el enfoque correcto para implementar una interfaz para desacoplar la dependencia entre mi aplicación y el dispositivo que utiliza?

Fisgonear
fuente
Esta es absolutamente la forma correcta de hacerlo. Básicamente, está programando un controlador de dispositivo, y esta es exactamente la forma en que los controladores de dispositivo se escriben tradicionalmente, con una buena razón. No puede evitar el hecho de que para usar las capacidades de un dispositivo, debe confiar en un código que conozca estas capacidades al menos de manera abstracta.
Kilian Foth
@KilianFoth Sí, tuve un presentimiento, olvidé agregar una parte a mi pregunta. Ver # 5.
Snoop,

Respuestas:

5

Creo que tienes una buena comprensión de cómo funciona el software desacoplado :)

La única duda en mi mente es que ahora tanto la aplicación como la clase concreta del dispositivo dependen de la biblioteca que contiene la interfaz IDevice. Pero, ¿es eso algo malo?

¡No necesita ser!

Tampoco veo cómo la aplicación no necesitará saber sobre el dispositivo, a menos que el dispositivo y el IDevice estén en el mismo espacio de nombres.

Puede abordar todas estas inquietudes con la Estructura del proyecto .

La forma en que normalmente lo hago:

  • Pon todas mis cosas abstractas en un Commonproyecto. Algo así como MyBiz.Project.Common. Otros proyectos son libres de referenciarlo, pero es posible que no haga referencia a otros proyectos.
  • Cuando creo una implementación concreta de una abstracción, la pongo en un proyecto separado. Algo así como MyBiz.Project.Devices.TemperatureSensors. Este proyecto hará referencia al Commonproyecto.
  • Luego tengo mi Clientproyecto, que es el punto de entrada a mi aplicación (algo así como MyBiz.Project.Desktop). Al inicio, la aplicación pasa por un proceso de Bootstrapping donde configuro asignaciones de abstracción / implementación concreta. Puedo crear instancias de mi concreto IDevicescomo WaterTemperatureSensory IRCameraTemperatureSensoraquí, o puedo configurar servicios como Fábricas o un contenedor IoC para instanciar los tipos de concreto adecuados para mí más adelante.

La clave aquí es que solo su Clientproyecto necesita conocer tanto el Commonproyecto abstracto como todos los proyectos de implementación concretos. Al limitar el mapeo abstracto-> concreto a su código Bootstrap, está haciendo posible que el resto de su aplicación desconozca completamente los tipos concretos.

Código vagamente acoplado ftw :)

MetaFight
fuente
2
DI sigue naturalmente desde aquí. Eso es en gran medida una preocupación de la estructura del proyecto / código también. Tener un contenedor de IoC puede ayudar con la DI pero no es un requisito previo. ¡También puede lograr DI inyectando dependencias manualmente!
MetaFight
1
@StevieV Sí, si el objeto Foo dentro de su aplicación requiere un IDevice, la inversión de dependencia puede ser tan simple como inyectarlo a través de un constructor ( new Foo(new DeviceA());), en lugar de tener un campo privado dentro de Foo instanciar el Dispositivo A ( private IDevice idevice = new DeviceA();) - todavía logra DI, como Foo no tiene conocimiento de DeviceA en el primer caso
otro fue
2
@anotherdave la suya y la entrada de MetaFight fue muy útil.
Snoop,
1
@StevieV Cuando tengas tiempo, aquí hay una buena introducción a la Inversión de dependencia como un concepto (del "Tío Bob" Martin, el tipo que lo acuñó), distinto de un marco de Inversión de control / Inyección de dependencia, como Spring (u otro) ejemplo popular en el mundo C♯ :))
otra
1
@anotherdave Definitivamente estoy planeando echarle un vistazo, gracias de nuevo.
Snoop,
3

Sí, ese parece ser el enfoque correcto. No, no es malo que la aplicación y la biblioteca de dispositivos dependan de la interfaz, especialmente si usted tiene el control de la implementación.

Si le preocupa que los dispositivos no siempre implementen la interfaz, por cualquier razón, puede utilizar el patrón del adaptador y adaptar la implementación concreta de los dispositivos a la interfaz.

EDITAR

Al abordar su quinta preocupación, piense en una estructura como esta (supongo que tiene el control de definir sus dispositivos):

Tienes una biblioteca central. En ella hay una interfaz llamada IDevice.

En su biblioteca de dispositivos, tiene una referencia a su biblioteca principal, y ha definido una serie de dispositivos que implementan IDevice. También tiene una fábrica que sabe cómo crear diferentes tipos de IDevices.

En su aplicación, usted incluye referencias a la biblioteca central y la biblioteca de dispositivos. Su aplicación ahora usa la fábrica para crear instancias de objetos que se ajustan a la interfaz IDevice.

Esta es una de las muchas formas posibles de abordar sus inquietudes.

EJEMPLOS

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.
            }
        }
    }
}
Price Jones
fuente
Entonces, con esta implementación, ¿a dónde va el concreto?
Snoop,
Solo puedo hablar por lo que elegiría hacer. Si tiene el control de los dispositivos, aún colocaría los dispositivos concretos en su propia biblioteca. Si no tiene el control de los dispositivos, crearía otra biblioteca para los adaptadores.
Precio Jones
Supongo que lo único es, ¿cómo obtendrá la aplicación acceso al concreto específico que necesito?
Snoop,
Una solución sería utilizar el patrón de fábrica abstracto para crear IDevices.
Precio Jones
1

La única duda en mi mente es que ahora tanto la aplicación como la clase concreta del dispositivo dependen de la biblioteca que contiene la interfaz IDevice. Pero, ¿es eso algo malo?

Tienes que pensar al revés. Si no va por este camino, la aplicación necesitaría conocer todos los diferentes dispositivos / implementaciones. Lo que debe tener en cuenta es que cambiar esa interfaz podría romper su aplicación si ya está en estado salvaje, por lo tanto, debe diseñar esa interfaz con cuidado.

¿Parece este el enfoque correcto para implementar una interfaz para desacoplar la dependencia entre mi aplicación y el dispositivo que utiliza?

Simplemente si

cómo el concreto llega realmente a la aplicación

La aplicación puede cargar el conjunto de hormigón (dll) en tiempo de ejecución. Ver: /programming/465488/can-i-load-a-net-assembly-at-runtime-and-instantiate-a-type-knowing-only-the-na

Si necesita descargar / cargar dinámicamente dicho "controlador", debe cargar el ensamblado en un dominio de aplicación separado .

Heslacher
fuente
Gracias, realmente desearía saber más acerca de las interfaces cuando comencé a codificar.
Snoop,
Lo sentimos, olvidé agregar una parte (cómo el concreto realmente llega a la aplicación). ¿Puedes abordar eso? Ver # 5.
Snoop
¿Realmente haces tus aplicaciones de esta manera, cargando las DLL en tiempo de ejecución?
Snoop,
Si, por ejemplo, no conozco la clase concreta, sí. Suponga que un proveedor de dispositivos decide usar su IDeviceinterfaz para que su dispositivo pueda usarse con su aplicación, entonces no habría otra forma de IMO.
Heslacher