Ответ 1
Данные схемы не обязательно означают бесструктурные данные; поля обычно известны заранее, и поверх них можно применить некоторый тип безопасного шаблона, чтобы избежать антипатового шаблона 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 returnOptional<T>