【问题标题】:Map object types using custom converter使用自定义转换器映射对象类型
【发布时间】:2016-09-28 17:46:41
【问题描述】:

我正在尝试设计一个映射器,它将一种对象类型转换为下图中显示的另一种对象类型:

对象的结构如下(不是 JSON):

{
"type": "DownloadAppComponent",
"name": "Download App",
"contentId": "download-app",
"properties": {
    "iosUrl": "http://apple.com",
    "androidUrl": "http: //google.com",
    "promoText": "Download our app",
    "hidden": false
}

我的第一个解决方案是为每种类型设置映射器,但这需要大量重复代码来映射公共属性(即名称、类型、contentId)。

public DownloadAppComponent map(CmsDocument cmsDocument) {
    DownloadAppComponent downloadAppComponent = new DownloadAppComponent();

    downloadAppComponent.setType(cmsDocument.getType()); // <-- this will be duplicated in each mapper
    downloadAppComponent.setName(cmsDocument.getName()); // <-- this will be duplicated in each mapper
    downloadAppComponent.setContentId(cmsDocument.getText(CONTENT_ID_PATH)); // <-- this will be duplicated in each mapper
    downloadAppComponent.setIosURL(cmsDocument.getText(IOS_URL_PATH));
    downloadAppComponent.setAndroidURL(cmsDocument.getText(ANDROID_URL_PATH));
    downloadAppComponent.setHidden(Boolean.parseBoolean(cmsDocument.getText(HIDE_PATH)));
    downloadAppComponent.setPromoText(cmsDocument.getText(DOWNLOAD_PROMO_TEXT_PATH));

    return downloadAppComponent;
}

我一直在尝试重构该代码,并提出了通用 BaseDocumentMapper:

public BaseDocument map(CmsDocument cmsDocument) {
    BaseDocument document = documentsMapperFactory.getMapper(cmsDocument.getType()).map(cmsDocument);
    document.setType(cmsDocument.getType());
    document.setName(cmsDocument.getName());
    document.setContentId(cmsDocument.getText(CONTENT_ID_PATH));
    return document;
}

documentsMapperFactory 返回特定的映射器,该映射器将仅映射类型相关的属性并返回该对象实例。

但是,继承还有更多级别,我只有一个类型值可以解析为具体的映射器。所以我必须在每个组件映射器中重复组件的特定字段映射。我在想,因为我知道层次结构,我可以创建一些映射器,将文档从上到下映射,即创建第一个 DownloadAppComponent,然后使用 Component 扩展它,然后使用 BaseDocument 特定属性。但是,除了在映射器中使用抽象类和继承之外,我还没有找到任何好的解决方案。

如果这是一种好方法,或者对于我的情况有任何问题或其他更好的解决方案,有人可以给我建议吗?

谢谢。

【问题讨论】:

标签: java inheritance mapping


【解决方案1】:

至少有三种选择。第一个是更通用的,将CmsDocument 的概念与BaseDocument 完全分开。

其他两个选项链接 BaseDocumentCmsDocument 类,因此它是选项选择的设计选择。

第一个选项 您可以根据两个对象都派生自BaseDocument这一事实创建一个方法来设置公共值。

....

private void setCommonValues(BaseDocument doc, CmsDocument cmsDocument) {
    doc.setType(cmsDocument.getType());
    doc.setName(cmsDocument.getName());
    doc.setContentId(cmsDocument.getText(CONTENT_ID_PATH));
}



public DownloadAppComponent map(CmsDocument cmsDocument) {
    DownloadAppComponent downloadAppComponent = new DownloadAppComponent();

    // Call setCommonValues 
    setCommonValues(downloadAppComponent, cmsDocument);

    downloadAppComponent.setIosURL(cmsDocument.getText(IOS_URL_PATH));
    downloadAppComponent.setAndroidURL(cmsDocument.getText(ANDROID_URL_PATH));
    downloadAppComponent.setHidden(Boolean.parseBoolean(cmsDocument.getText(HIDE_PATH)));
    downloadAppComponent.setPromoText(cmsDocument.getText(DOWNLOAD_PROMO_TEXT_PATH));

    return downloadAppComponent;
}

对于其他功能也是如此

public BaseDocument map(CmsDocument cmsDocument) {
    BaseDocument document = documentsMapperFactory.getMapper(cmsDocument.getType()).map(cmsDocument);

    // Call setCommonValues to remove duplication of code
    setCommonValues(document, cmsDocument);
    return document;
}

第二个选项

BaseDocument 类中创建方法init

private void init(CmsDocument cmsDocument) {
    this.setType(cmsDocument.getType());
    this.setName(cmsDocument.getName());
    this.setContentId(cmsDocument.getText(CONTENT_ID_PATH));
}

在地图正文中

public DownloadAppComponent map(CmsDocument cmsDocument) {
    DownloadAppComponent downloadAppComponent = new DownloadAppComponent();

    // Call init 
    downloadAppComponent.init(cmsDocument);

    downloadAppComponent.setIosURL(cmsDocument.getText(IOS_URL_PATH));
    downloadAppComponent.setAndroidURL(cmsDocument.getText(ANDROID_URL_PATH));
    downloadAppComponent.setHidden(Boolean.parseBoolean(cmsDocument.getText(HIDE_PATH)));
    downloadAppComponent.setPromoText(cmsDocument.getText(DOWNLOAD_PROMO_TEXT_PATH));

    return downloadAppComponent;
}

对于其他功能也是如此

public BaseDocument map(CmsDocument cmsDocument) {
    BaseDocument document = documentsMapperFactory.getMapper(cmsDocument.getType()).map(cmsDocument);

    // Call init 
    document.init(cmsDocument);

    return document;
}

第三个选项

BaseDocument 上创建一个以CmsDocument 为参数的构造函数

public BaseDocument(CmsDocument cmsDocument) {
    this.setType(cmsDocument.getType());
    this.setName(cmsDocument.getName());
    this.setContentId(cmsDocument.getText(CONTENT_ID_PATH));
}

在 DownloadAppComponent 中

 public DownloadAppComponent(CmsDocument cmsDocument) {
     super(cmsDocument);
     this.setIosURL(cmsDocument.getText(IOS_URL_PATH));
     this.setAndroidURL(cmsDocument.getText(ANDROID_URL_PATH));
     this.setHidden(Boolean.parseBoolean(cmsDocument.getText(HIDE_PATH)));
    this.setPromoText(cmsDocument.getText(DOWNLOAD_PROMO_TEXT_PATH));

 }

这种情况下不需要map方法,直接构建对象调用带参数的构造函数即可。


如果您需要一个类,其方法map 可以返回两个不同的实例,您可以将所需的类型作为参数传递:

public class Mapper {
    private void setCommonValues(BaseDocument doc, CmsDocument cmsDocument) {
        doc.setType(cmsDocument.getType());
        doc.setName(cmsDocument.getName());
        doc.setContentId(cmsDocument.getText(CONTENT_ID_PATH));
    }

    public BaseDocument map(CmsDocument cmsDocument, Class<? extends BaseDocument> clazz) {
        BaseDocument doc = null;
        if (clazz.getCanonicalName().equals(DownloadAppComponent.class.getCanonicalName()) {
            DownloadAppComponent appComponent = new DownloadAppComponent();
            doc = appComponent;
            appComponent.setIosURL(cmsDocument.getText(IOS_URL_PATH));
            appComponent.setAndroidURL(cmsDocument.getText(ANDROID_URL_PATH));
            appComponent.setHidden(Boolean.parseBoolean(cmsDocument.getText(HIDE_PATH)));
            appComponent.setPromoText(cmsDocument.getText(DOWNLOAD_PROMO_TEXT_PATH));

        } else {
             doc = new BaseDocument();
        }
        setCommonValues(doc);
        return doc;
    }
}

你可以这样调用它:

Mapper mapper = new Mapper();
CmsDocument cmsDocument = ...

BaseDocument doc = mapper.map(cmsDocument, BaseDocument.class);

DownloadAppComponent downloadAppComponent = (DownloadAppComponent) mapper.map(cmsDocument, DownloadAppComponent.class);

【讨论】:

  • 我不得不说我更喜欢第一种方法,因为正如您所说,它不会使这两个类相互依赖,并且通过保持 SoC 和单一职责原则来帮助代码更清洁。我仍然不明白我应该把setCommonValues() 方法放在哪里。它们不能都在同一个类中,因为它不会编译。 map() 方法会模棱两可。
  • 必须像super.setCommonValues() 一样调用辅助方法,但我必须为映射器类添加另一个级别的继承(即DownloadAppComponentMapper extends ComponentMapperComponentMapper extends BaseDocumentMapper 等...)。我能以某种方式避免它吗?
  • @MariuszMiesiak 方法 map 如果必须添加到同一个类中,则可以重命名。但是您也可以为它们创建两个类而不重命名它。如果您选择一个具有两个重命名的map 方法的类,您可以将方法setCommonValues 添加到该类。否则最好的解决方案是使用具体方法setCommonValue 和抽象方法map 创建一个抽象类,并为map 的每个自定义实现创建两个子类。
  • @MariuszMiesiak 如果您只需要一个类和一个方法传递一个带有要创建的类类型的参数。我将此解决方案添加到答案中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-10-09
  • 1970-01-01
  • 2012-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-30
相关资源
最近更新 更多