2016-09-27 5 views
0

Ich versuche, einen Mapper zu entwerfen, die einen Objekttyp in einer anderen in der Abbildung unten gezeigt konvertieren:Map-Objekttypen mit benutzerdefinierten Konverter

enter image description here

Die Objektstruktur hat wie folgt (es ist kein JSON):

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

Meine erste Lösung war Mapper für jeden Typ zu haben, aber das ist eine Menge von Code-Duplizierung erforderlich gemeinsame Attribute (zB Name, Typ, contentId) abzubilden.

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; 
} 

Ich habe versucht, diesen Code zu Refactoring und ich komme mit generic 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; 
} 

Die documentsMapperFactory kehrt spezifischen Mapper, die nur Typ bezogene Attribute abbildet und zurück, dass die Objektinstanz.

Allerdings gibt es noch mehr Ebenen in der Vererbung und ich habe nur einen Typ Wert, den ich in konkrete Mapper auflösen kann. Also muss ich die Zuordnung spezifischer Komponenten in jedem Komponenten-Mapper wiederholen. Ich dachte, dass ich, da ich die Hierarchie kenne, einige Mapper erstellen könnte, die die Dokumente von oben nach unten abbilden würden, d. H. Zuerst DownloadAppComponent erstellen und dann mit Component und dann mit BaseDocument-spezifischen Eigenschaften erweitern. Ich habe jedoch keine gute Lösung gefunden, außer abstrakte Klassen und Vererbung in Mappern zu verwenden.

Könnte mir jemand sagen, ob dies ein guter Ansatz ist oder ob es Probleme oder andere bessere Lösungen für meinen Fall gibt?

Vielen Dank.

+0

Nun, ich würde zuerst versuchen, vorhandene Bibliotheken zu verwenden, wie http://dozer.sourceforge.net/. – Tom

Antwort

2

Es gibt mindestens drei Alternativen. Das erste ist das allgemeinere, das das Konzept von CmsDocument völlig getrennt von BaseDocument lässt.

Die anderen beiden Optionen verknüpfen BaseDocument und CmsDocument Klassen, so ist es eine Design-Wahl, die Option auswählen.

Erste Option Sie eine Methode erstellen können gemeinsame Werte basiert auf der Tatsache eingestellt, dass sowohl Objekt aus BaseDocument ableitet.

.... 

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; 
} 

Und in ähnlicher Weise für die andere Funktion

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; 
} 

Zweite Option

Erstellen Sie eine Methode init in der BaseDocument Klasse

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

Und im Körper der Karte

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; 
} 

Und in ähnlicher Weise für die andere Funktion

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

    // Call init 
    document.init(cmsDocument); 

    return document; 
} 

dritten Option

einen Konstruktor auf BaseDocument erstellen CmsDocument als Parameter genommen

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

Und in 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)); 

} 

In diesem Fall, dass Sie keine Karte Methode benötigen, können Sie direkt die Objekte den Aufruf der Konstruktor mit Parametern erstellen.


Wenn Sie eine Klasse mit der Methode map haben müssen, die zwei verschiedene Instanzen zurückkehren können Sie den gewünschten Typ als Parameter übergeben:

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; 
    } 
} 

Sie können es als aufrufen:

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

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

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

Ich muss sagen, dass ich die erste Methode mehr mag, weil, wie Sie sagten, es diese beiden Klassen nicht abhängig macht und es hilft, den Code sauberer zu halten, indem es die SoC- und Single-Responsibility-Prinzipien einhält. Ich verstehe immer noch nicht, wo ich diese 'setCommonValues ​​()' Methode einfügen soll. Sie können nicht alle in derselben Klasse sein, weil sie nicht kompiliert werden. Die 'map()' Methode wäre mehrdeutig. –

+0

Die Hilfsmethode muss wie 'super.setCommonValues ​​()' aufgerufen werden, aber ich muss eine weitere Vererbungsebene für die Mapper-Klassen hinzufügen (d. H. 'DownloadAppComponentMapper erweitert ComponentMapper' und' ComponentMapper erweitert BaseDocumentMapper' etc ...). Kann ich es irgendwie vermeiden? –

+0

@MariuszMiesiak die Methoden 'map' können umbenannt werden, wenn sie in der gleichen Klasse hinzugefügt werden müssen. Sie können jedoch auch zwei Klassen für sie erstellen, ohne sie umzubenennen. Wenn Sie eine Klasse mit zwei umbenannten 'map'-Methoden auswählen, können Sie dieser Klasse die Methode' setCommonValues' hinzufügen. Andernfalls ist die beste Lösung, eine abstrakte Klasse mit einer konkreten Methode 'setCommonValue' und einer abstrakten Methode' map' zu erstellen und zwei Unterklassen für jede benutzerdefinierte Implementierung von 'map' zu erstellen. –

Verwandte Themen