Передати об'єкт двічі одному методу або консолідувати за допомогою комбінованого інтерфейсу?


15

У мене є метод, який створює файл даних після розмови з цифровою дошкою:

CreateDataFile(IFileAccess boardFileAccess, IMeasurer boardMeasurer)

Тут boardFileAccessі boardMeasurerтой самий екземпляр Boardоб'єкта, який реалізує і IFileAccessі IMeasurer. IMeasurerвикористовується в цьому випадку для одного методу, який встановить один штифт на дошці активним, щоб зробити просте вимірювання. Дані цього вимірювання зберігаються локально на дошці за допомогою IFileAccess. Boardзнаходиться в окремому проекті.

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

Мені здається незручно передавати один і той же об’єкт методу двічі. Я розглядав можливість створення локального інтерфейсу, IDataFileCreatorякий розширюватиметься, IFileAccessа IMeasurerпотім матиме реалізацію, що містить Boardекземпляр, який просто викликає необхідні Boardметоди. Враховуючи, що один і той же об’єкт на дошці завжди буде використовуватися для вимірювання та запису файлів, чи погана практика передавати один і той же об'єкт методу двічі? Якщо так, то чи використовується локальний інтерфейс та реалізація відповідного рішення?


2
Важко неможливо зрозуміти наміри вашого коду з імен, якими ви користуєтесь. Інтерфейс з іменем IDataFileCreator передається методу під назвою CreateDataFile - це глузує. Чи змагаються вони за відповідальність за збереження даних? Який клас CreateDataFile є методом? Вимірювання не має нічого спільного з постійними даними, тому багато чого зрозуміло. Ваше запитання не стосується найбільшої проблеми у вас з кодом.
Мартін

Чи можна коли-небудь уявити, що ваш об'єкт доступу до файлів та ваш вимірювальний об'єкт можуть бути двома різними об'єктами? Я б сказав так. Якщо змінити його зараз, вам доведеться змінити його ще у версії 2, яка підтримує проведення вимірювань по всій мережі.
користувач253751

2
Ось ще одне запитання - чому в першу чергу об'єкти доступу до даних та вимірювання файлів даних однакові?
користувач253751

Відповіді:


40

Ні, це цілком добре. Це просто означає, що API надмірно розроблений щодо вашої поточної програми .

Але це не підтверджує, що ніколи не буде випадку використання, у якому джерело даних та вимірювальний пристрій відрізняються. Сенс API - запропонувати програмістам можливості, не всі з яких будуть використані. Ви не повинні штучно обмежувати, що можуть робити користувачі API, якщо це не ускладнить API, так що чиста зрозумілість знизиться.


7

Погодьтеся з відповіддю @ KilianFoth, що це абсолютно добре.

Тим не менш, якщо ви хочете, ви можете створити метод, який займає один об'єкт, який реалізує обидва інтерфейси:

public object CreateDataFile<T_BoardInterface>(
             T_BoardInterface boardInterface
    )
    where T_BoardInterface : IFileAccess, IMeasurer
{
    return CreateDataFile(
                boardInterface
            ,   boardInterface
        );
}

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


4

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

Я думаю, що це ваша проблема. Метод - це не одна справа. Він виконує дві чіткі операції, які включають введення-виведення для різних пристроїв , обидва з яких не завантажуються на інші об'єкти:

  • Отримати вимірювання
  • Збережіть результат десь у файлі

Це дві різні операції вводу / виводу. Примітно, що перший жодним чином не мутує файлову систему.

Справді, слід зазначити, що мається на увазі середній крок:

  • Отримати вимірювання
  • Серіалізувати вимірювання у відомий формат
  • Збережіть серіалізований вимір у файл

Ваш API повинен надавати кожне з них окремо у певній формі. Як ви знаєте, що абонент не захоче проводити вимірювання, не зберігаючи його ніде? Звідки ви знаєте, що вони не хочуть отримувати вимірювання з іншого джерела? Звідки ви знаєте, що вони не захочуть зберігати його де-небудь, крім пристрою? Є вагомі підстави для відключення операцій. На голій мінімум, кожна окрема частина повинна бути доступна кожному абоненту. Мене не слід змушувати записувати вимірювання у файл, якщо мій випадок використання не вимагає цього.

Як приклад, ви можете розділити такі операції.

IMeasurer є спосіб отримати вимірювання:

public interface IMeasurer
{
    IMeasurement Measure(int someInput);
}

Ваш тип вимірювання може бути просто простим, як, наприклад, stringабо decimal. Я не наполягаю, що вам потрібен інтерфейс чи клас для цього, але це робить приклад більш загальним.

IFileAccess є деякий спосіб збереження файлів:

interface IFileAccess
{
    void SaveFile(string fileContents);
}

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

interface IMeasurement
{
    // As part of the type
    string Serialize();
}

// Utility method. Makes more sense if the measurement is not a custom type.
public static string SerializeMeasurement(IMeasurement m)
{
    return ...
}

Незрозуміло, чи ви ще не відокремили цю операцію серіалізації.

Такий поділ покращує ваш API. Це дає змогу абоненту вирішити, що їм потрібно і коли, а не змушувати заздалегідь задумані ідеї щодо того, які введення-виведення виконувати. Абоненти повинні мати контроль за виконанням будь-якої дійсної операції, чи вважаєте ви, що це корисно чи ні.

Коли у вас є окремі реалізації для кожної операції, ваш CreateDataFileметод стає лише скороченням

fileAccess.SaveFile(SerializeMeasurement(measurer.Measure()));

Зокрема, ваш метод додає дуже мало значення, коли ви все це зробите. Вищевказаний рядок коду вашим абонентам не важко використовувати безпосередньо, а ваш метод - максимально для зручності. Це має бути і є чимось необов’язковим . І це правильний спосіб поведінки API.


Після того, як всі відповідні частини будуть розроблені, і ми визнали, що метод є лише зручністю, нам потрібно переформулювати ваше запитання:

Який би найчастіший випадок використання ваших абонентів?

Якщо вся справа в тому, щоб зробити типовий випадок використання вимірювань і запису на одну плату трохи зручніше, то має сенс просто зробити доступним для Boardкласу безпосередньо:

public class Board : IMeasurer, IFileAccess
{
    // Interface methods...

    /// <summary>
    /// Convenience method to measure and immediate record measurement in
    /// default location.
    /// </summary>
    public void ReadAndSaveMeasurement()
    {
        this.SaveFile(SerializeMeasurement(this.Measure()));
    }
}

Якщо це не покращує зручність, я б взагалі не переймався методом.


Цей метод зручності викликає ще одне питання.

Чи повинен IFileAccessінтерфейс знати про тип вимірювання та як його серіалізувати? Якщо так, ви можете додати метод до IFileAccess:

interface IFileAccess
{
    void SaveFile(string fileContents);
    void SaveMeasurement(IMeasurement m);
}

Тепер абоненти просто роблять це:

fileAccess.SaveFile(measurer.Measure());

який настільки ж короткий і, ймовірно, більш зрозумілий, ніж ваш метод зручності, як задумано у питанні.


2

Клієнт, що споживає, не повинен мати справу з парою предметів, коли одного товару вистачає. У вашому випадку їх майже немає, до виклику CreateDataFile.

Потенційне рішення, яке ви пропонуєте, - створити комбінований похідний інтерфейс. Однак для цього підходу потрібен єдиний об'єкт, який реалізує обидва інтерфейси, що є досить обмежуючим, імовірно, хиткою абстракцією, оскільки в основному він налаштований під конкретну реалізацію. Поміркуйте, як складніше було б, якби хтось захотів реалізувати два інтерфейси в окремих об'єктах: їм доведеться проксі всі методи в одному з інтерфейсів, щоб переадресувати на інший об’єкт. (FWIW, інший варіант - просто об'єднати інтерфейси, а не вимагати того, щоб один об'єкт повинен реалізувати два інтерфейси через похідний інтерфейс.)

Однак інший підхід, який менш обмежує / диктує виконання, полягає в тому, що IFileAccessвін поєднується з IMeasurerкомпозицією, так що один з них пов'язаний і посилається на інший. (Це дещо збільшує абстракцію однієї з них, оскільки вона тепер також представляє спарювання.) Тоді CreateDataFileможна було б взяти лише одне з посилань, скажімо IFileAccess, і все ж отримати інше за потребою. Ваша поточна реалізація як об'єкт , який реалізує обидва інтерфейсу буде просто return this;для довідки композиції, тут геттер для IMeasurerв IFileAccess.

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


В іншій записці я можу запитати, хто є власником CreateDataFile, і питання стосується того, хто ця третя сторона. У нас вже є клієнт, що споживає, який викликає CreateDataFile, об'єкт / клас власника CreateDataFileта, IFileAccessі IMeasurer. Іноді, коли ми розглядаємо більш широкий погляд на контекст, можуть з'являтися чергові, іноді кращі організації. Тут важко зробити, оскільки контекст неповний, тому просто їжа для роздумів.


0

Деякі виховували, що CreateDataFileробить занадто багато. Я можу припустити, що натомість Boardробиться занадто багато, оскільки доступ до файлів здається окремим клопотом від решти ради.

Однак, якщо припустити, що це не помилка, більша проблема полягає в тому, що в цьому випадку інтерфейс повинен визначатися клієнтом CreateDataFile.

Інтерфейс Сегрегація Принцип говорить , що клієнт не повинен залежати від більш інтерфейсу , ніж те , що йому потрібно. Запозичивши фразу з цієї іншої відповіді , це можна перефразовувати як «інтерфейс визначається тим, що потрібно клієнту».

Тепер можна скласти цю клієнт-специфічний інтерфейс , використовуючи IFileAccessі IMeasurerінші відповіді припускають, але в кінцевому рахунку, цей клієнт повинен мати інтерфейс спеціально для нього.


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