Usando interfaces para código fracamente acoplado


10

fundo

Eu tenho um projeto que depende do uso de um determinado tipo de dispositivo de hardware, embora realmente não importe quem faz esse dispositivo de hardware desde que faça o que eu preciso. Com isso dito, mesmo dois dispositivos que devem fazer a mesma coisa terão diferenças quando não forem fabricados pelo mesmo fabricante. Então, estou pensando em usar uma interface para desacoplar o aplicativo da marca / modelo específico do dispositivo envolvido e, em vez disso, fazer com que a interface cubra apenas a funcionalidade de nível mais alto. Aqui está o que eu acho que minha arquitetura será:

  1. Defina uma interface em um projeto C # IDevice.
  2. Tenha um concreto em uma biblioteca definida em outro projeto C #, que será usado para representar o dispositivo.
  3. Faça com que o dispositivo concreto implemente a IDeviceinterface.
  4. A IDeviceinterface pode ter métodos como GetMeasurementou SetRange.
  5. Faça com que o aplicativo tenha conhecimento sobre o concreto e passe o concreto para o código do aplicativo que utiliza ( não implementa ) o IDevicedispositivo.

Tenho certeza de que esse é o caminho certo a seguir, porque poderei alterar qual dispositivo está sendo usado sem afetar o aplicativo (o que parece acontecer algumas vezes). Em outras palavras, não importa como as implementações GetMeasurementou SetRangerealmente funcionem no concreto (pois podem diferir entre os fabricantes do dispositivo).

A única dúvida em minha mente é que agora tanto o aplicativo quanto a classe concreta do dispositivo dependem da biblioteca que contém a IDeviceinterface. Mas isso é uma coisa ruim?

Também não vejo como o aplicativo não precisará saber sobre o dispositivo, a menos que ele IDeviceesteja no mesmo espaço para nome.

Questão

Parece a abordagem correta para implementar uma interface para dissociar a dependência entre meu aplicativo e o dispositivo que ele usa?


Esta é absolutamente a maneira correta de fazer isso. Você está essencialmente programando um driver de dispositivo, e é exatamente assim que os drivers de dispositivo são escritos tradicionalmente, por um bom motivo. Não é possível contornar o fato de que, para usar os recursos de um dispositivo, você precisa confiar no código que os conhece pelo menos de maneira abstrata.
Kilian Foth

@KilianFoth Sim, eu tive um pressentimento, esqueci de adicionar uma parte à minha pergunta. Veja # 5.
Snoop

Respostas:


5

Eu acho que você tem um bom entendimento de como o software dissociado funciona :)

A única dúvida em minha mente é que agora tanto o aplicativo quanto a classe concreta do dispositivo dependem da biblioteca que contém a interface IDevice. Mas isso é uma coisa ruim?

Não precisa ser!

Também não vejo como o aplicativo não precisará saber sobre o dispositivo, a menos que o dispositivo e o dispositivo de ID estejam no mesmo espaço para nome.

Você pode lidar com todas essas preocupações com a Estrutura do projeto .

Do jeito que eu normalmente faço:

  • Coloque todas as minhas coisas abstratas em um Commonprojeto. Algo como MyBiz.Project.Common. Outros projetos são livres para fazer referência a ele, mas pode não fazer referência a outros projetos.
  • Quando crio uma implementação concreta de uma abstração, coloco-a em um projeto separado. Algo como MyBiz.Project.Devices.TemperatureSensors. Este projeto fará referência ao Commonprojeto.
  • Então, eu tenho meu Clientprojeto, que é o ponto de entrada para o meu aplicativo (algo como MyBiz.Project.Desktop). Na inicialização, o aplicativo passa por um processo de Bootstrapping no qual eu configuro mapeamentos de abstração / implementação concreta. Posso instanciar meu concreto IDevicescomo aqui WaterTemperatureSensore IRCameraTemperatureSensoraqui, ou posso configurar serviços como Fábricas ou um contêiner de IoC para instanciar os tipos de concreto certos para mim mais tarde.

O principal aqui é que apenas o seu Clientprojeto precisa estar ciente do Commonprojeto abstrato e de todos os projetos de implementação concretos. Ao restringir o mapeamento abstract-> Concrete ao seu código de Bootstrap, você possibilita que o restante do seu aplicativo não tenha conhecimento dos tipos de concreto.

Código vagamente acoplado ftw :)


2
DI segue naturalmente daqui. Isso também é uma preocupação da estrutura do projeto / código. Ter um contêiner de IoC pode ajudar com o DI, mas não é um pré-requisito. Você pode obter o DI injetando dependências manualmente também!
MetaFight

1
@StevieV Sim, se o objeto Foo em seu aplicativo exigir um dispositivo ID, a inversão de dependência pode ser tão simples quanto injetá-lo por meio de um construtor ( new Foo(new DeviceA());), em vez de ter um campo privado no Foo instanciar o próprio DeviceA ( private IDevice idevice = new DeviceA();) - você ainda obtém o DI, como Foo desconhece o DeviceA no primeiro caso
anotherdave

2
@anotherdave sua entrada e MetaFight foi muito útil.
Snoop

1
@StevieV Quando você obtém tempo, aqui está uma boa introdução à Inversão de Dependência como um conceito (do "Tio Bob" Martin, o cara que o cunhou), distinto de uma estrutura de Inversão de Controle / Injeção de Dependência, como a Spring (ou outra exemplo popular no mundo C♯ :))
anotherdave

1
@anotherdave Definitivamente planejando verificar isso, obrigado novamente.
Snoop

3

Sim, essa parece ser a abordagem correta. Não, não é uma coisa ruim para o aplicativo e a biblioteca de dispositivos depender da interface, especialmente se você estiver no controle da implementação.

Se você estiver preocupado que os dispositivos nem sempre implementem a interface, por qualquer motivo, poderá utilizar o padrão do adaptador e adaptar a implementação concreta dos dispositivos à interface.

EDITAR

Ao abordar sua quinta preocupação, pense em uma estrutura como esta (suponho que você esteja no controle da definição de seus dispositivos):

Você tem uma biblioteca principal. Nele está uma interface chamada IDevice.

Na sua biblioteca de dispositivos, você tem uma referência à sua biblioteca principal e definiu uma série de dispositivos que implementam todos os dispositivos de ID. Você também tem uma fábrica que sabe como criar diferentes tipos de dispositivos de identificação.

No seu aplicativo, você inclui referências à biblioteca principal e à biblioteca de dispositivos. Seu aplicativo agora usa a fábrica para criar instâncias de objetos em conformidade com a interface do dispositivo de ID.

Essa é uma das muitas maneiras possíveis de solucionar suas preocupações.

EXEMPLOS:

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.
            }
        }
    }
}

Então, com esta implementação, para onde vai o concreto?
Snoop

Só posso falar pelo que escolheria fazer. Se você estiver no controle dos dispositivos, ainda colocaria os dispositivos concretos em sua própria biblioteca. Se você não estiver no controle dos dispositivos, criaria outra biblioteca para os adaptadores.
Price Jones

Acho que a única coisa é: como o aplicativo terá acesso ao concreto específico que eu preciso?
Snoop

Uma solução seria usar o padrão abstrato de fábrica para criar dispositivos de identificação.
Price Jones

1

A única dúvida em minha mente é que agora tanto o aplicativo quanto a classe concreta do dispositivo dependem da biblioteca que contém a interface IDevice. Mas isso é uma coisa ruim?

Você precisa pensar o contrário. Se você não seguir esse caminho, o aplicativo precisará conhecer todos os diferentes dispositivos / implementações. O que você deve ter em mente é que alterar essa interface pode interromper seu aplicativo se ele já estiver disponível, portanto, você deve projetar essa interface com cuidado.

Parece a abordagem correta para implementar uma interface para dissociar a dependência entre meu aplicativo e o dispositivo que ele usa?

Simplesmente sim

como o concreto chega ao aplicativo

O aplicativo pode carregar a montagem de concreto (dll) em tempo de execução. Consulte: /programming/465488/can-i-load-a-net-assembly-at-runtime-and-instantiate-a-type-knowing-only-the-na

Se você precisar descarregar / carregar dinamicamente esse "driver", precisará carregar o assembly em um AppDomain separado .


Obrigado, eu realmente gostaria de saber mais sobre interfaces quando comecei a codificar.
Snoop

Desculpe, esqueci de adicionar uma parte (como o concreto realmente chega ao aplicativo). Você pode resolver isso? Veja # 5.
Snoop

Você realmente executa seus aplicativos dessa maneira, carregando as DLLs em tempo de execução?
Snoop

Se, por exemplo, a classe concreta não é conhecida por mim, então sim. Suponha que um fornecedor de dispositivos decida usar sua IDeviceinterface para que seu dispositivo possa ser usado com seu aplicativo; portanto, não haveria outra maneira de IMO.
Heslacher
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.