Я использую стороннюю библиотеку. Они передают мне 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, ограничивая его распространение во всей моей системе. Однако ... это решение GROSS! В Чистом Коде, страница 40:
Более трех аргументов (полиадические) требуют особого обоснования, и их не следует использовать в любом случае.
Вещи, которые я рассмотрел:
- Создание фабричного объекта, а не статического вспомогательного метода
- Не решить проблему с баджиллионными аргументами
- Создание подкласса DataTypeOne и DataTypeTwo с зависимым конструктором
- Все еще имеет конструктор с полиадной защитой
- Создайте совершенно отдельные реализации, которые соответствуют одному и тому же интерфейсу
- Множество вышеперечисленных идей одновременно
Как справиться с этой ситуацией?
Обратите внимание, что это не ситуация с антикоррупционным уровнем . Нет ничего плохого в их API. Проблемы:
- Я не хочу, чтобы мои структуры данных имели
import com.third.party.library.SomeDataStructure;
- Я не могу построить их структуры данных в моих тестовых примерах
- Мое текущее решение приводит к очень очень большому количеству аргументов. Я хочу, чтобы количество аргументов было низким, БЕЗ передачи их структур данных.
- Этот вопрос « что такое антикоррупционный слой?». Мой вопрос: « Как я могу использовать шаблон, любой шаблон, чтобы решить этот сценарий?»
Я также не прошу код (в противном случае этот вопрос был бы о SO), я просто спрашиваю достаточно ответа, чтобы я мог эффективно написать код (который этот вопрос не предоставляет).
источник
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.
Ответы:
Стратегия, которую я использовал, когда есть несколько параметров инициализации, заключается в создании типа, который просто содержит параметры для инициализации
Затем конструктор для DataTypeTwo принимает объект DataTypeTwoParameters, а DataTypeTwo создается с помощью:
Это дает много возможностей прояснить, что представляют собой все параметры, входящие в DataTypeTwo, и что они означают. Вы также можете указать разумные значения по умолчанию в конструкторе DataTypeTwoParameters, чтобы можно было задавать только те значения, которые необходимо установить, в любом порядке, который нравится потребителю API.
источник
Integer.parseInt
? В сеттере или вне класса параметров?p.bar = Integer.parseInt("4")
.DataTypeTwoParameters
вDataTypeTwo
.У вас действительно есть две отдельные проблемы: обертывание API и сохранение низкого количества аргументов.
Идея заключается в том, чтобы при создании API создавать интерфейс с нуля, не зная ничего, кроме требований. Вы говорите, что нет ничего плохого в их API, а затем в одном и том же списке перечислите несколько проблем с их API: тестируемость, конструктивность, слишком много параметров в одном объекте и т. Д. Напишите API, который вы хотели бы иметь. Если для этого требуется несколько объектов вместо одного, сделайте это. Если это требует переноса на один уровень выше, для объектов, которые создают POJO, сделайте это.
Затем, когда у вас есть желаемый API, количество параметров может перестать быть проблемой. Если это так, существует ряд общих шаблонов, которые следует учитывать:
Обратите внимание, что эти шаблоны создания часто заканчивают тем, что вызывают полиадический конструктор, который следует учитывать при его инкапсуляции. Проблема с полиадическими конструкторами не вызывает их один раз, а когда вы вынуждены вызывать их каждый раз, когда вам нужно создать объект.
Обратите внимание, что, как правило, гораздо проще и проще обслуживать переход к базовому API, сохраняя ссылку на
OurData
объект и перенаправляя вызовы метода, а не пытаясь переопределить его внутренние компоненты. Например:источник
OurData
объект» - это то, чего я пытаюсь избежать, по крайней мере, в базовом классе, чтобы гарантировать отсутствие зависимости.DataInterface
. Вы создаете другую реализацию для своих фиктивных объектов.Я думаю, что вы, возможно, интерпретируете рекомендацию дяди Боба слишком строго. Для нормальных классов, с логикой, методами, конструкторами и т.п., полиадический конструктор действительно очень похож на запах кода. Но для чего-то, что является строго контейнером данных, который предоставляет поля и генерируется тем, что по сути уже является объектом Factory, я не думаю, что это слишком плохо.
Вы можете использовать шаблон Parameter Object, как предложено в комментарии, можете обернуть эти параметры конструктора для вас, то есть, что ваша локальная оболочка типа данных уже , по сути, объект Parameter. Все, что будет делать ваш объект Parameter - это упаковывать параметры (как вы их создадите? С помощью полиадического конструктора?), А затем распаковывать их через секунду в объект, который почти идентичен.
Если вы не хотите выставлять сеттеры для своих полей и вызывать их, я думаю, что придерживаться полиадического конструктора внутри четко определенной и инкапсулированной фабрики - это хорошо.
источник