java - 将 Java 数据模型转换为 XLS 工作簿表的设计模式
问题描述
基本上,我想将 Java 对象模型转换为 XLS 工作簿的两个不同工作表。到目前为止,我已经解决了这个问题,但我正试图在设计模式方面找到更好的解决方案。
这是响应对象模型。
class Response {
List<Model1> list;
}
class Model1 {
String attr1;
String attr2;
List<Model2> list;
@JsonProperty(value="attr1")
String getAttr1() {..}
}
class Model2 {
String attr1;
String attr2;
@JsonProperty(value="attr1")
String getAttr1() {..}
}
对于转换层,我创建了一个具有不同实现的父接口,如下所示。
public interface Converter<T> {
Sheet convert(T t);
}
public class DataConverter {
public Workbook getWorkBook(Response res) {
Sheet model1Sheet = new Model1SheetConverter().convert(res);
Sheet model2Sheet = new Model2SheetConverter().convert(res);
return new Workbook().sheets(List.of(model1Sheet, model2Sheet));
}
}
public class Model1SheetConvert implements Converter<Response> {
public sheet convert(Response res) {
I do loop over here for data model 1 and then with some business logic generate sheet.
}
}
public class Model2SheetConvert implements Converter<Response> {
public sheet convert(Response res) {
I do loop over here for each data model 1 then I pass data model 2 and then with some business logic generate sheet.
}
}
现在这种方法的问题是:
- 我为数据模型 1 迭代了两次。
- 基于数据模型 1 的工作表 1 和工作表 2 将有一些动态行。我不能用这种方法在这里处理这部分。
有人可以为这种方法提出一个好的设计模式吗?或者,有没有其他方法,我可以做不同的事情。
解决方案
作为一种可能设计的基础,根据我的理解,这些是主要需求 -
- 模型的每种类型(类)都需要一个转换器——例如,Model1 应该有它的转换器,而 Model2 应该有它自己的转换器。然后转换器生成(填充或写入)单个工作簿
Sheet
。我将进一步将转换器命名为 aSheetWriter
,并将其多态方法命名为write(M model)
method 。也就是说,接受其指定类型的模型。 - 我们需要 2 个类 - a
Model1Writer
和 aModel2Writer
- 每个都是这样创建的,它拥有一个 Sheet 实例并不断将数据(即行)附加到其中。每次调用其write(M model)
方法时,都会返回相同的 Sheet 对象。因此,如果我们为 19 个实例和它们的 55 个“子”实例调用这两个write(M model)
方法,那么我们仍然必须得到正好两张表:Sheet 包含这 19 个实例的所有信息(即 19 行),而Sheet 包含所有这 55个实例的信息(即 55 行)。Model1
Model2
Model1
Model1
Model2
Model2
- 因此,我们需要流式传输
SheetWriter
Response 中的所有模型 - 包括其模型以及它们的所有子模型 - 并使用适当的实例(取决于模型的类)将它们转换(映射)到这些唯一的 2 张工作表。
考虑到上述基础 - 以下设计及其实现可能是我们的起始解决方案。它结合了工厂模式和策略。Factory 是具体 SheetWriters 的来源(给定一个具体模型),而 Strategy 是具体的 SheetWriter 例如写入逻辑,用于其相应的模型类。
下面是 ResponseConverter 的实现:
public class ResponseConverter {
private final Writers factory;
private final Map<Class<? extends WritableModel>, SheetWriter<? extends WritableModel>> writers;
public ResponseConverter(Writers factory) {
this.factory = factory;
this.writers = new HashMap<>();
}
public Workbook toWorkbook(Response response) {
Set<Sheet> sheets = streamAllModelsIn(response)
.map(model -> getWriter(model).write(model))
.collect(Collectors.toCollection(LinkedHashSet::new));
return new Workbook(sheets.toArray(new Sheet[] {}));
}
private Stream<WritableModel> streamAllModelsIn(Response response) {
List<Model1> mainModels = response.getModels();
Stream<WritableModel> childrenStream = mainModels.stream().flatMap(this::streamChildren);
return Stream.concat(mainModels.stream(), childrenStream);
}
private Stream<? extends WritableModel> streamChildren(Model1 model) {
return model.getChildren().stream();
}
@SuppressWarnings("unchecked")
private <M extends WritableModel> SheetWriter<M> getWriter(M model) {
if (!writers.containsKey(model.getClass())) {
writers.put(model.getClass(), factory.writerFor(model));
}
return (SheetWriter<M>) writers.get(model.getClass());
}
}
在上面的清单中,主键是私有getWriter(M model)
方法。它使用内部“缓存映射”writers
来获取与当前模型的类对应的正确SheetWriter
实例 - 或者,如果还没有这样的映射条目,则使用Writers factory
来初始化它 - 询问适合给定模型的新实例.
第二部分是writers
包的实现——意思是实际的工厂机制+具体的策略,SheetWriter
将你的Model1
和Model2
实例写入它们的输出表。这包括外部代码可以使用的“writers”包的三个“公共”组件(白色类),以及一些只在包机制内部使用且对 ResponseHandler 不可见的“隐藏”组件(灰色类)或者外面的任何东西。目标是能够在内部添加更多受包保护的 SheetWriter 类及其映射,而不会影响任何外部代码,也无需更改。
以下是“writers”包组件的代码清单
包的公开可用部分(API) -
public interface WritableModel { }
public interface SheetWriter<M extends WritableModel> {
Sheet write(M model);
}
public class Writers {
private static final Map<Class<? extends WritableModel>,
WriterFactory<? extends WritableModel, ? extends SheetWriter<?>>> factories;
static {
factories = new HashMap<>();
factories.put(Model1.class, new Model1WriterFactory());
factories.put(Model2.class, new Model2WriterFactory());
}
@SuppressWarnings("unchecked")
public <M extends WritableModel> SheetWriter<M> writerFor(M model) {
Class<? extends WritableModel> modelClass = model.getClass();
if (isNull(factories.get(modelClass))) {
throw new UnsupportedOperationException("No Writer for the provided Model's type (" + modelClass + ")");
}
return (SheetWriter<M>) factories.get(modelClass).get();
}
}
以及所需工厂和策略的内部(包保护)实施 -
interface WriterFactory<M extends WritableModel, W extends SheetWriter<M>> {
W get();
}
class Model1WriterFactory implements WriterFactory<Model1, Model1Writer> {
@Override
public Model1Writer get() {
return new Model1Writer();
}
}
class Model1Writer implements SheetWriter<Model1> {
private final Sheet outputSheet;
public Model1Writer() {
this.outputSheet = new Sheet();
}
@Override
public Sheet write(Model1 model) {
//this is your model-dependent logic of how Sheet is filled from the model
outputSheet.addRow(model.getName() + ": has " + model.getChildCount() + " children");
return outputSheet;
}
}
class Model2WriterFactory implements WriterFactory<Model2, Model2Writer> {
@Override
public Model2Writer get() {
return new Model2Writer();
}
}
class Model2Writer implements SheetWriter<Model2&t; {
private final Sheet outputSheet;
public Model2Writer() {
this.outputSheet = new Sheet();
}
@Override
public Sheet write(Model2 model) {
//this is your model-dependent logic of how Sheet is filled from the model
outputSheet.addRow(model.toString());
return outputSheet;
}
}
上面的实现允许我们通过主要只添加新代码来扩展新(即Model3)类的工作表编写逻辑 - 并且只需稍微修改 Writers 类静态工厂映射 - 通过添加新条目。
但是,作为这种设计的一个限制 - 两个具体SheetWriter
类都只接受它们相应的模型实例,以保持良好的狭窄责任焦点。但是,如果您想使用 Model1 中包含的有关 Model2 实例的信息并将其写入第一张表中 - 那么您可以这样做, Model1Writer
因为您包含了那些 Model2 对象,但不能反过来 - 例如,如果在 Model2 表中,您还需要写一些来自响应的 Model1 对象的信息,那么目前这种设计是不可用的。如果确实有必要,您可以解决此问题 - 通过传递整个“上下文”,例如通过使SheetWriter
接口方法也需要整个Response
作为第二个参数:
public interface SheetWriter<M extends WritableModel> {
Sheet write(M model, Response response);
}
为了前面代码的完整性,下面的清单还包括我对Response
、Model1
、Model2
和Workbook
的“虚拟”实现Sheet
public class Response {
private final List<Model1> models = new ArrayList<>();
public Response(Model1... models) {
this.models.addAll(asList(models));
}
public List<Model1> getModels() {
return new ArrayList<>(models);
}
public void addModel(Model1 model) {
models.add(model);
}
}
@lombok.RequiredArgsConstructor
public class Model1 implements WritableModel {
private final String name;
private final List<Model2> children = new ArrayList<>();
@Override
public String toString() {
return this.getClass().getSimpleName() + " = " + this.name;
}
public void setChildren(List<Model2> children) {
this.children.clear();
this.children.addAll(children);
}
public String getName() {
return name;
}
public int getChildCount() {
return children.size();
}
public List<Model2> getChildren() {
return new ArrayList<>(this.children);
}
}
@lombok.RequiredArgsConstructor
public class Model2 implements WritableModel {
private final String title;
private final int value;
public String toString() {
return this.getClass().getSimpleName() + ": " + this.title + " (" + this.value + ")";
}
}
@lombok.RequiredArgsConstructor
public class Workbook {
private final List<Sheet> sheets;
public Workbook(Sheet... sheets) {
this.sheets = asList(sheets);
}
public List<Sheet> getSheets() {
return sheets;
}
}
public class Sheet {
private List<String> rows = new ArrayList<>();
public void addRow(String value) {
rows.add(value);
}
public String toString() {
return String.join("\n", rows);
}
}
编辑:用下面的部分更新了答案,以包括数据模型委托
这是包含整个实现的我的存储库的链接:
https://github.com/Ristox/sheetwriters
数据模型的代码已经更新以实现委托——我们自己的data
包定义了自定义模型类,它们都“包装”了包中原始的、不可修改/不可扩展的模型类blackbox
,如下所示:
package org.example.workbook.sheetwriters.blackbox;
import java.util.ArrayList;
import java.util.List;
/**
* Pretend this is a "black-box" library class eg you know the public interface, but cannot change it
*/
public final class ExternalModel1 {
private final String name;
private final List<ExternalModel2> children = new ArrayList<>();
public ExternalModel1(String name) {
this.name = name;
}
@Override
public String toString() {
return this.getClass().getSimpleName() + " = " + this.name;
}
public void setChildren(List<ExternalModel2> children) {
this.children.clear();
this.children.addAll(children);
}
public String getName() {
return name;
}
public int getChildCount() {
return children.size();
}
public List<ExternalModel2> getChildren() {
return new ArrayList<>(this.children);
}
}
package org.example.workbook.sheetwriters.blackbox;
/**
* Pretend this is a "black-box" library class eg you know the public interface, but cannot change it
*/
public final class ExternalModel2 {
private final String title;
private final int value;
public ExternalModel2(String title, int value) {
this.title = title;
this.value = value;
}
public String toString() {
return this.getClass().getSimpleName() + ": " + this.title + " (" + this.value + ")";
}
}
package org.example.workbook.sheetwriters.data;
import java.util.List;
import org.example.workbook.sheetwriters.blackbox.ExternalModel1;
import org.example.workbook.sheetwriters.writers.WritableModel;
public class Model1 implements WritableModel {
private final ExternalModel1 delegate;
public Model1(String name) {
delegate = new ExternalModel1(name);
}
@Override
public String toString() {
return this.delegate.toString();
}
public void setChildren(List<Model2> children) {
this.delegate.setChildren(Model2.toDelegates(children));
}
public String getName() {
return this.delegate.getName();
}
public int getChildCount() {
return this.delegate.getChildCount();
}
public List<Model2> getChildren() {
return Model2.wrapAll(this.delegate.getChildren());
}
}
package org.example.workbook.sheetwriters.data;
import static java.util.stream.Collectors.toList;
import java.util.List;
import org.example.workbook.sheetwriters.blackbox.ExternalModel2;
import org.example.workbook.sheetwriters.writers.WritableModel;
public class Model2 implements WritableModel {
public static Model2 wrap(ExternalModel2 externalModel) {
return new Model2(externalModel);
}
public static List<Model2> wrapAll(List<ExternalModel2> externalModels) {
return externalModels.stream().map(Model2::wrap).collect(toList());
}
public static List<ExternalModel2> toDelegates(List<Model2> model2List) {
return model2List.stream().map(Model2::toDelegate).collect(toList());
}
private final ExternalModel2 delegate;
public Model2(String title, int value) {
this.delegate = new ExternalModel2(title, value);
}
private Model2(ExternalModel2 delegate) {
this.delegate = delegate;
}
public String toString() {
return this.delegate.toString();
}
public ExternalModel2 toDelegate() {
return this.delegate;
}
}
如果最初的“黑盒”模型类有一个很大的公共接口,那么手动重复 + 委托整个接口可能看起来很乏味。在这种情况下,您可能希望查看编译时预处理器并使用它们的样板注释,例如Lombok 的 @Delegate。它有其起起落落,但如果使用得当、有意识地使用,应该能够以少得多的样板代码实现整个必要的委托。
推荐阅读
- collections - 如何根据其中的消息对集合之间的 mpsc::Receivers 进行排序?
- python-3.x - 将函数动态添加到类并使其成为绑定方法
- python - 为 Conv2d 层手动分配权重
- javascript - Veutify 多选 - 将所选项目作为 JSON 对象获取
- javascript - 如何将 php 正则表达式转换为 javascript?
- optimization - GPU 利用率低是否表明不适合 GPU 加速?
- apache-kafka - 使用 kafka 密钥的 kafka s3 连接器分区
- r - 如何比较 netlogit 函数中 R (QAP) 中网络数据的优势比?
- vue.js - 无法使用 Vue Loader 覆盖 Bulma 变量
- firebase - Google Firebase 云功能上的图像审核总是超时?