Использование интерфейсов для слабосвязанного кода

10

Фон

У меня есть проект, который зависит от использования определенного типа аппаратного устройства, хотя на самом деле не имеет значения, кто делает это аппаратное устройство, если оно делает то, что мне нужно. При этом даже два устройства, которые должны делать одно и то же, будут иметь различия, если они не сделаны одним и тем же производителем. Поэтому я думаю использовать интерфейс, чтобы отделить приложение от конкретной марки / модели используемого устройства, и вместо этого иметь интерфейс, охватывающий только функциональность самого высокого уровня. Вот как я думаю, что моя архитектура будет выглядеть так:

  1. Определите интерфейс в одном проекте C # IDevice.
  2. Имейте конкретику в библиотеке, определенной в другом проекте C #, который будет использоваться для представления устройства.
  3. Попросите конкретное устройство реализовать IDeviceинтерфейс.
  4. IDeviceИнтерфейс может иметь такие методы , как GetMeasurementили SetRange.
  5. Заставьте приложение иметь знания о бетоне и передайте бетон к коду приложения, которое использует (а не реализует ) IDeviceустройство.

Я уверен, что это правильный путь, потому что тогда я смогу изменить, какое устройство используется, не влияя на приложение (что иногда случается). Другими словами, не имеет значения, как реализации GetMeasurementили SetRangeфактически работают через бетон (как могут отличаться у производителей устройства).

Единственное сомнение, на мой взгляд, заключается в том, что теперь и приложение, и конкретный класс устройства зависят от библиотеки, содержащей IDeviceинтерфейс. Но разве это плохо?

Я также не вижу, как приложению не нужно будет знать об устройстве, если оно не IDeviceнаходится в одном пространстве имен.

Вопрос

Кажется ли это правильным подходом для реализации интерфейса, позволяющего отделить зависимость между моим приложением и устройством, которое оно использует?

шпионить
источник
Это абсолютно правильный способ сделать это. По сути, вы программируете драйвер устройства, и именно так традиционно пишутся драйверы устройств, и на то есть веская причина. Вы не можете обойти тот факт, что для использования возможностей устройства вы должны полагаться на код, который знает эти возможности, по крайней мере, абстрактно.
Килиан Фот
@KilianFoth Да, у меня было чувство, забыл добавить одну часть к моему вопросу. Смотрите № 5.
Снуп

Ответы:

5

Я думаю, у вас есть довольно хорошее понимание того, как работает отделенное программное обеспечение :)

Единственное сомнение, на мой взгляд, заключается в том, что теперь и приложение, и конкретный класс устройства зависят от библиотеки, содержащей интерфейс IDevice. Но разве это плохо?

Это не должно быть!

Я также не вижу, как приложение не должно знать об устройстве, если только устройство и IDevice не находятся в одном пространстве имен.

Вы можете справиться со всеми этими проблемами с помощью структуры проекта .

Как я обычно делаю это:

  • Поместите все мои абстрактные вещи в Commonпроект. Нечто подобное MyBiz.Project.Common. Другие проекты могут ссылаться на него, но он не может ссылаться на другие проекты.
  • Когда я создаю конкретную реализацию абстракции, я помещаю ее в отдельный проект. Нечто подобное MyBiz.Project.Devices.TemperatureSensors. Этот проект будет ссылаться на Commonпроект.
  • Затем у меня есть Clientпроект, который является точкой входа в мое приложение (что-то вроде MyBiz.Project.Desktop). При запуске приложение проходит процесс начальной загрузки, где я настраиваю сопоставления абстракции и конкретной реализации. Я могу создать экземпляр моего бетона , IDevicesкак WaterTemperatureSensorи IRCameraTemperatureSensorздесь, или я могу настроить услуги , как Фабрики или контейнер IoC позже инстанцировать нужные типов конкретных для меня.

Ключевым моментом здесь является то, что только ваш Clientпроект должен знать как абстрактный Commonпроект, так и все конкретные проекты реализации. Ограничивая абстрактное-> конкретное отображение вашим кодом Bootstrap, вы позволяете остальной части вашего приложения блаженно не знать о конкретных типах.

Слабо связанный код ftw :)

MetaFight
источник
2
Я действительно следую отсюда. Это в значительной степени касается и структуры проекта / кода. Наличие контейнера IoC может помочь с DI, но это не является обязательным условием. Вы можете достичь DI, вводя зависимости вручную!
MetaFight
1
@StevieV Да, если объект Foo в вашем приложении требует IDevice, инверсия зависимостей может быть такой же простой, как внедрение его через конструктор ( new Foo(new DeviceA());), вместо того, чтобы иметь личное поле в Foo для создания экземпляра DeviceA ( private IDevice idevice = new DeviceA();) - вы все равно получаете DI, так как Foo не знает о DeviceA в первом случае
anotherdave
2
@anotherdave ваш и MetaFight вход был очень полезным.
Снуп
1
@StevieV Когда у вас будет время, вот хорошее введение в Deverdency Inversion как концепцию (от «Дяди Боба» Мартина, парня, который это придумал), отличную от Inversion of Control / Depency Injection, например Spring (или некоторых других Известный пример в C♯ мире :))
anotherdave
1
@anotherdave Определенно планирую проверить это, еще раз спасибо.
Снуп
3

Да, это похоже на правильный подход. Нет, для приложения и библиотеки устройств неплохо зависеть от интерфейса, особенно если вы контролируете реализацию.

Если вы обеспокоены тем, что устройства могут не всегда реализовывать интерфейс, по какой-либо причине вы можете использовать шаблон адаптера и адаптировать конкретную реализацию устройств к интерфейсу.

РЕДАКТИРОВАТЬ

При решении вашей пятой задачи, подумайте о такой структуре (я предполагаю, что вы управляете определением своих устройств):

У вас есть основная библиотека. В нем есть интерфейс под названием IDevice.

В вашей библиотеке устройств у вас есть ссылка на вашу базовую библиотеку, и вы определили ряд устройств, которые все реализуют IDevice. У вас также есть фабрика, которая знает, как создавать различные виды IDevices.

В вашем приложении вы включаете ссылки на основную библиотеку и библиотеку устройств. Ваше приложение теперь использует фабрику для создания экземпляров объектов, которые соответствуют интерфейсу IDevice.

Это один из многих возможных способов решения ваших проблем.

ПРИМЕРЫ:

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.
            }
        }
    }
}
Прайс Джонс
источник
Так с этой реализацией, куда идет бетон?
Снуп
Я могу говорить только за то, что я хотел бы сделать. Если вы управляете устройствами, вы все равно поместите конкретные устройства в их собственную библиотеку. Если вы не управляете устройствами, вы бы создали другую библиотеку для адаптеров.
Цена Джонс
Я думаю, единственное, как приложение получит доступ к конкретному бетону, который мне нужен?
Снуп
Одним из решений было бы использовать абстрактный шаблон фабрики для создания IDevices.
Цена Джонс
1

Единственное сомнение, на мой взгляд, заключается в том, что теперь и приложение, и конкретный класс устройства зависят от библиотеки, содержащей интерфейс IDevice. Но разве это плохо?

Вам нужно подумать об обратном. Если вы не пойдете по этому пути, приложение должно будет знать обо всех различных устройствах / реализациях. Следует помнить, что изменение этого интерфейса может привести к поломке вашего приложения, если оно уже вышло из-под контроля, поэтому вы должны тщательно разработать этот интерфейс.

Кажется ли это правильным подходом для реализации интерфейса, позволяющего отделить зависимость между моим приложением и устройством, которое оно использует?

Просто да

как бетон на самом деле попадает в приложение

Приложение может загрузить конкретную сборку (dll) во время выполнения. См .: /programming/465488/can-i-load-a-net-assembly-at-runtime-and-instantiate-a-type-knowing-only-the-na

Если вам нужно динамически выгрузить / загрузить такой «драйвер», вам нужно загрузить сборку в отдельный домен приложений .

Heslacher
источник
Спасибо, я действительно хотел бы знать больше об интерфейсах, когда я впервые начал писать код.
Снуп
Извините, забыл добавить одну часть (как бетон на самом деле попадает в приложение). Вы можете обратиться к этому? Смотрите № 5.
Снуп
Вы действительно делаете свои приложения таким образом, загружая библиотеки DLL во время выполнения?
Снуп
Если, например, конкретный класс мне не известен, тогда да. Предположим, что поставщик устройства решает использовать ваш IDeviceинтерфейс, чтобы его устройство можно было использовать с вашим приложением, тогда не было бы другого способа IMO.
Хеслахер