Використання інтерфейсів для слабо пов'язаного коду


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 :)


2
Звідси випливає природно звідси. В основному це стосується і проекту / структури коду. Наявність контейнера IoC може допомогти з DI, але це не обов'язкова умова. Ви можете домогтися DI, ввівши залежність також вручну!
MetaFight

1
@StevieV Так, якщо об'єкт Foo у вашій програмі вимагає IDevice, інверсія залежності може бути такою ж простою, як введення її через конструктор ( new Foo(new DeviceA());), а не мати приватне поле в самому Foo instantiate DeviceA ( private IDevice idevice = new DeviceA();) - ви все одно досягнете DI, як Foo не знає про DeviceA в першій справі
іншомудав

2
@anotherdave ваше та введення MetaFight було дуже корисним.
Снуп

1
@StevieV Коли ви знайдете час, ось приємне вступ до інверсії залежності як концепція (від "дядька Боб" Мартін, хлопець, який її створив), відмінна від рамки інверсії управління / ін'єкцій Depency, як весна (або ще деякі інші популярний приклад у світі С♯ :))
черговий день

1
@anotherdave Однозначно планую це перевірити, ще раз дякую.
Снуп

3

Так, це здається правильним підходом. Ні, для програми та бібліотеки пристроїв непогано залежати від інтерфейсу, особливо якщо ви контролюєте впровадження.

Якщо ви стурбовані тим, що пристрої можуть не завжди реалізовувати інтерфейс, з будь-якої причини ви можете використовувати схему адаптера та адаптувати конкретну реалізацію пристроїв до інтерфейсу.

EDIT

Звертаючись до свого п’ятого занепокоєння, подумайте про таку структуру (я припускаю, що ви контролюєте визначення своїх пристроїв):

У вас є основна бібліотека. У ньому є інтерфейс під назвою IDevice.

У вашій бібліотеці пристроїв ви маєте посилання на свою основну бібліотеку, і ви визначили серію пристроїв, які реалізують IDevice. У вас також є фабрика, яка вміє створювати різні види ідентифікаторів.

У своєму додатку ви включаєте посилання на основну бібліотеку та бібліотеку пристроїв. Тепер ваша програма використовує фабрику для створення примірників об'єктів, які відповідають інтерфейсу 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

Якщо вам потрібно динамічно розвантажувати / завантажувати такий "драйвер", вам потрібно завантажити збірку в окремий AppDomain .


Дякую, я дуже хочу, щоб я знав більше про інтерфейси, коли вперше почав кодувати.
Снуп

Вибачте, забув додати в одній частині (як конкретно насправді потрапляє в додаток). Чи можете ви вирішити це? Див. №5.
Снуп

Ви справді робите свої програми таким чином, завантажуючи DLL під час виконання?
Снуп

Якщо, наприклад, конкретний клас мені не відомий, то так. Припустимо, продавець пристрою вирішить використовувати ваш IDeviceінтерфейс, щоб його пристрій можна було використовувати з вашим додатком, тоді іншого способу IMO не було б.
Геслахер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.