Як один аргумент утримує низький підсумок і все ще тримає окремі залежності від сторонніх?


13

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

public class OurData {
  private String foo;
  private String bar;
  private String baz;
  private String quux;
  // A lot more than this

  // IMPORTANT: NOTE THAT THIS IS A PACKAGE PRIVATE CONSTRUCTOR
  OurData(/* I don't know what they do */) {
    // some stuff
  }

  public String getFoo() {
    return foo;
  }

  // etc.
}

З багатьох причин, включаючи, але не обмежуючись тим, щоб капсулювати їх API та полегшити тестування одиниць, я хочу обернути їхні дані. Але я не хочу, щоб мої основні класи залежали від їх даних (знову ж таки, з тестових причин)! Тож зараз у мене є щось подібне:

public class DataTypeOne implements DataInterface {
  private String foo;
  private int bar;
  private double baz;

  public DataTypeOne(String foo, int bar, double baz) {
    this.foo = foo;
    this.bar = bar;
    this.baz = baz;
  }
}

public class DataTypeTwo implements DataInterface {
  private String foo;
  private int bar;
  private double baz;

  public DataTypeOne(String foo, int bar, double baz, String quux) {
    this.foo = foo;
    this.bar = bar;
    this.baz = baz;
    this.quux = quux;
  }
}

І тоді це:

public class ThirdPartyAdapter {
  public static makeMyData(OurData data) {
    if(data.getQuux() == null) {
      return new DataTypeOne(
        data.getFoo(),
        Integer.parseInt(data.getBar()),
        Double.parseDouble(data.getBaz()),
      );
    } else {
      return new DataTypeTwo(
        data.getFoo(),
        Integer.parseInt(data.getBar()),
        Double.parseDouble(data.getBaz()),
        data.getQuux();
      );
  }
}

Цей клас адаптерів поєднується з іншими кількома класами, які ПОВИНЕН знати про API сторонніх виробників, обмежуючи його поширеність через решту моєї системи. Однак ... це рішення ВІДКРИТЕ! У Чистому коді, стор. 40:

Більше трьох аргументів (поліадичних) вимагає дуже спеціального обґрунтування - і тоді їх не слід використовувати.

Речі, які я врахував:

  • Створення фабричного об'єкта, а не статичний метод помічника
    • Це не вирішує проблему з аргументом мільйона
  • Створення підкласу DataTypeOne та DataTypeTwo, який має залежний конструктор
    • Досі має поліадний захищений конструктор
  • Створіть цілком окремі реалізації, які відповідають одному інтерфейсу
  • Кілька перерахованих вище ідей одночасно

Як слід вирішувати цю ситуацію?


Зауважте, це не антикорупційний рівень . З їх API немає нічого поганого. Проблеми:

  • Я не хочу, щоб мої структури даних мали import com.third.party.library.SomeDataStructure;
  • Я не можу побудувати їх структуру даних у своїх тестових випадках
  • Моє поточне рішення призводить до дуже високих підрахунків аргументів. Я хочу, щоб кількість аргументів залишалася низькою, БЕЗ передачі їхніх структур даних.
  • Це питання " що таке антикорупційний шар?". Моє запитання: " як я можу використовувати шаблон, будь-яку схему для вирішення цього сценарію?"

Я також не прошу коду (інакше це питання буде на ЗО), просто прошу достатньо відповіді, щоб я міг ефективно написати код (чого це питання не передбачає).


Якщо існує декілька таких сторонніх POJO, можливо, варто докласти зусиль, щоб написати власний тестовий код, який використовує Map з деякими умовами (наприклад, назвіть ключі int_bar) в якості тестового вводу. Або використовуйте JSON або XML з деяким спеціальним посередницьким кодом. Насправді, це свого роду DSL для тестування com.thirdparty.
користувач949300

Повна цитата з чистого коду:The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification — and then shouldn’t be used anyway.
Lilienthal

11
Сліпе дотримання шаблону або настанови програмування - це його власний антидіаграма .
Ліліенталь

2
"інкапсуляція їх API та полегшення тестування блоку" Звучить так, що це може бути випадком перенапруження та / або тестування, спричиненого тестом, для мене (або показово, що ви могли б спроектувати це інакше для початку). Запитайте себе в цьому: чи справді це полегшує ваш код для розуміння, зміни та повторного використання? Я б поклав свої гроші на "ні". Наскільки реально ймовірно, що ви коли-небудь поміняєте цю бібліотеку? Напевно, не дуже. Якщо ви заміняєте це, чи справді це полегшує скидання зовсім іншого на місце? Знову я ставлю на "ні".
jpmc26

1
@JamesAnderson Я просто відтворив повну цитату, тому що мені здалося цікавим, але з фрагмента мені не було зрозуміло, чи стосується він функцій взагалі чи конкретно до конструкторів. Я не хотів схвалювати претензію, і, як сказав jpmc26, наступний мій коментар повинен дати вам певну ознаку того, що я цього не робив. Я не впевнений, чому ви відчуваєте необхідність нападати на науковців, але використання багатоскладних матеріалів не робить когось академічного елітиста, що сидить на башті зі слонової кістки над хмарами.
Ліліенталь

Відповіді:


10

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

public class DataTypeTwoParameters {
    public String foo;  // use getters/setters instead if it's appropriate
    public int bar;
    public double baz;
    public String quuz;
}

Тоді конструктор DataTypeTwo приймає об’єкт DataTypeTwoParameters, а DataTypeTwo будується за допомогою:

DataTypeTwoParameters p = new DataTypeTwoParameters();
p.foo = "Hello";
p.bar = 4;
p.baz = 3;
p.quuz = "World";

DataTypeTwo dtt = new DataTypeTwo(p);

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


Цікавий підхід. Де б ви поставили відповідне Integer.parseInt? У сеттер або поза класом параметрів?
durron597

5
Поза класом параметрів. Клас параметрів повинен бути "німим" об'єктом, і він не повинен намагатися робити нічого, крім висловлення необхідних входів та їх типів. Синтаксичний має бути зроблено в іншому місці, наприклад: p.bar = Integer.parseInt("4").
Ерік

7
це звучить як шаблон об’єкта параметра
gnat

9
... або анти-візерунок.
Теластин

1
... або ви могли б просто перейменувати DataTypeTwoParametersв DataTypeTwo.
користувач253751

14

Тут у вас є дві особливі проблеми: завершення API та утримання низької кількості аргументів.

Під час обгортання API ідея полягає в тому, щоб створити інтерфейс як би з нуля, не знаючи нічого, крім вимог. Ви говорите, що з їх API немає нічого поганого, тоді в одному диханні перелічіть ряд речей, що не відповідають їх API: перевіряемость, конструктивність, занадто багато параметрів в одному об’єкті тощо. Напишіть API, який ви хотіли б. Якщо для цього потрібно кілька об’єктів замість одного, зробіть це. Якщо для цього потрібно завернути на один рівень вище, для об’єктів, які створюють POJO, зробіть це.

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

  • Об'єкт параметра, як у відповіді Еріка .
  • Шаблон будівельника , де ви створюєте окремий об'єкт будівельника, а потім викликати ряд сеттерів для установки параметрів по окремо, а потім створити кінцевий об'єкт.
  • Модель прототипу , де ви клонувати підкласи потрібного об'єкту з поля вже встановлені всередині.
  • Фабрика, яку ви вже знайомі.
  • Деяке поєднання перерахованого.

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

Зауважте, що, як правило, набагато простіше і рентабельніше пройти до базового API, зберігаючи посилання на OurDataоб'єкт і пересилаючи виклики методу, а не намагаючись повторно реалізувати його внутрішні. Наприклад:

public class DataTypeTwo implements DataInterface {
  private OurData data;

  public DataTypeOne(OurData data) {
    this.data = data;
  }

   public String getFoo() {
    return data.getFoo();
  }

  public int getBar() {
    return Integer.parseInt(data.getBar());
  }
  ...
}

Перша половина цієї відповіді: чудово, дуже корисно, +1. Друга половина цієї відповіді: "перейти до базового API, зберігаючи посилання на OurDataОб'єкт" - саме цього я намагаюся уникати, принаймні в базовому класі, щоб переконатися у відсутності залежності.
durron597

1
Тому ви робите це лише в одній із своїх реалізацій DataInterface. Ви створюєте іншу реалізацію для макетних об'єктів.
Карл Білефельдт

@ durron597: так, але ви вже знаєте, як вирішити цю проблему, якщо вона насправді вас турбує.
Док Браун

1

Я думаю, ви, можливо, занадто суворо трактуєте рекомендацію дядька Боба. Для звичайних класів, з логікою, методами і конструкторами, і подібний поліадний конструктор справді дуже схожий на запах коду. Але для чогось, що є суто контейнером даних, який відкриває поля, і генерується тим, що вже є фактично об'єктом Factory, я не думаю, що це занадто погано.

Ви можете використовувати шаблон "Об'єкт параметрів", як запропоновано в коментарі, і може обернути ці параметри конструктора для вас, те, що ваша локальна оболонка типу даних, вже є , по суті, об'єктом Параметра. Усі ваші об'єкти Parameter будуть робити упаковку параметрів (як ви їх створите? Поліадним конструктором?), А потім розпакуйте їх на секунду пізніше в об'єкт, майже однаковий.

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


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