данные без схемы не обязательно означают данные без структуры ;поля обычно известны заранее, и поверх них можно применить некоторый типобезопасный шаблон, чтобы избежать анти-паттерна Magic Container Но это не всегда так.Иногда ключи вводятся пользователем и не могут быть известны заранее.
Я несколько раз использовал шаблон объекта роли , чтобы придать динамическую структуру согласованности.Я думаю, что он хорошо подходит для обоих случаев.
Шаблон объекта роли определяет способ доступа к различным представлениям объекта.Каноническим примером является Пользователь, который может принимать несколько ролей , таких как Клиент, Продавец и Продавец.Каждое из этих представлений имеет различные операции, которые оно может выполнять, и к которым можно получить доступ из любого другого представления.Общие поля обычно доступны на уровне интерфейса (особенно userId()
, или в вашем случае toJson()
).
Вот пример использования шаблона:
public void displayPage(User user) {
display(user.getName());
if (user.hasView(Customer.class))
displayShoppingCart(user.getView(Customer.class);
if (user.hasView(Seller.class))
displayProducts(user.getView(Seller.class));
}
В случаеданных с известной структурой, вы можете иметь несколько представлений, объединяющих различные наборы ключей в единое целое.Эти различные представления могут считывать данные json при построении.
В случае данных с динамической структурой авторитетный RawDataView может иметь данные в динамической форме (т. Е. Волшебный контейнер как HashMap<String, Object>
).Это может быть использовано для запроса динамических данных.В то же время безопасные для типов оболочки могут быть созданы лениво и могут делегироваться в RawDataView для облегчения чтения / поддержки программы:
public class Customer implements User {
private final RawDataView data;
public CustomerView(UserView source) {
this.data = source.getView(RawDataView.class);
}
// All User views must specify this
@Override
public long id() {
return data.getId();
}
@Override
public <T extends UserView> T getView(Class<T> view) {
// construct or look up view
}
@Override
public Json toJson() {
return data.toJson();
}
//
// Specific to Customer
//
public List<Item> shoppingCart() {
List<Item> items = (List<Item>) data.getValue("items", List.class);
}
// etc....
}
Я добился успеха с обоими этими подходами.Вот несколько дополнительных указателей, которые я обнаружил на этом пути:
- Как можно больше статической структуры структуры ваших данных.Это делает вещи намного проще в обслуживании.Мне пришлось нарушить это правило и использовать подход RawDataView при работе на устаревшей системе.Возможно, вам также придется разбить его динамически вводимыми данными пользователя, как указано выше.В этом случае используйте соглашение для нединамических имен полей, таких как начальное подчеркивание (
_userId
) - Реализовано
equals()
и hashcode()
так что user.getView(A.class).equals(user.getView(B.class))
всегда верно для одного и того же пользователя. - Имеет класс UserCore , который выполняет всю тяжелую работу с обычным кодом, например, создает представления;выполнение общих операций (например,
toJson()
), возвращая общие поля (например, userId()
);и реализации equals()
и hashcode()
.Пусть все представления делегируются этому базовому объекту - Имеют AbstractUserView , который делегирует UserCore и реализует equals () и hashcode ()
- Используйте безопасный для типов гетерогенный контейнер(например, ClassToInstanceMap ) создание / кэширование представлений.
- Разрешить запрашивать существование представления.Это можно сделать с помощью метода
hasView()
или с помощью getView return Optional<T>