最佳的序列化复合对象的方式 -(设计模式)

huangapple go评论86阅读模式
英文:

Best way to serialize composite - (design pattern)

问题

以下是您提供的Java代码的翻译部分:

// 实现组合设计模式的 Java 代码如下:

// 为了类型安全而设计的组合接口(仅在叶子中进行仅限叶子操作)
interface Component extends Visitable {
  void enable();
  void disable();
}

class CompositeA implements Component {
  private String compositeData;
  private boolean enabled;
  private Set<Component> components = new HashSet<>();

  CompositeA(String compositeData) {
    this.compositeData = compositeData;
  }

  void addChild(Component component){
    this.components.add(component);
  }

  String getCompositeData() {
    return compositeData;
  }

  Set<Component> getComponents() {
    return components;
  }

  @Override
  public void enable() {
    this.enabled = true;
  }

  @Override
  public void disable() {
    this.enabled = false;
  }

  @Override
  public Object accept(ComponentVisitor visitor) {
    return visitor.visit(this);
  }
}

class CompositeB implements Component{
  private int compositeData;
  private boolean enabled;
  private Set<Component> components = new HashSet<>();

  CompositeB(int compositeData) {
    this.compositeData = compositeData;
  }

  void addChild(Component component){
    this.components.add(component);
  }

  int getCompositeData() {
    return compositeData;
  }

  Set<Component> getComponents() {
    return components;
  }

  @Override
  public void enable() {
    this.enabled = true;
  }

  @Override
  public void disable() {
    this.enabled = false;
  }

  @Override
  public Object accept(ComponentVisitor visitor) {
    return visitor.visit(this);
  }
}

class Leaf implements Component {
  private boolean enabled;
  private String[] leafData;

  Leaf(String[] leafData) {
    this.leafData = leafData;
  }

  String[] getLeafData() {
    return leafData;
  }

  @Override
  public void enable() {
    this.enabled = true;
  }

  @Override
  public void disable() {
    this.enabled = false;
  }

  @Override
  public Object accept(ComponentVisitor visitor) {
    return visitor.visit(this);
  }
}

// 这里有两个可能的组合根(`CompositeA` 和 `CompositeB`)和一个叶子组件(`Leaf`)。

// 下面是定义用于保存序列化数据的 DTO 类:

class WholeCompositeASerialized {
  String content;
  List<Object> serializedChildren;
}

class WholeCompositeBSerialized{
  String content;
  List<Object> serializedChildren;
}

class WholeLeafSerialized{
  String content;
}

// 如果使用访问者模式进行序列化,可以得到如下代码:

interface ComponentVisitor {
  WholeCompositeASerialized visit(CompositeA compositeA);
  WholeCompositeBSerialized visit(CompositeB compositeB);
  WholeLeafSerialized visit(Leaf leaf);
}

class SerializableComponentVisitor implements ComponentVisitor{
  @Override
  public WholeCompositeASerialized visit(CompositeA compositeA) {
    WholeCompositeASerialized wcas = new WholeCompositeASerialized();
    wcas.serializedChildren = compositeA
        .getComponents()
        .stream()
        .map(c -> c.accept(this))
        .collect(Collectors.toList());
    wcas.content = compositeA.getCompositeData();
    return wcas;
  }

  @Override
  public WholeCompositeBSerialized visit(CompositeB compositeB) {
    WholeCompositeBSerialized wcbs = new WholeCompositeBSerialized();
    wcbs.serializedChildren = compositeB
        .getComponents()
        .stream()
        .map(c -> c.accept(this))
        .collect(Collectors.toList());
    wcbs.content = String.valueOf(compositeB.getCompositeData());
    return wcbs;
  }

  @Override
  public WholeLeafSerialized visit(Leaf leaf) {
    WholeLeafSerialized wls = new WholeLeafSerialized();
    wls.content = Arrays.toString(leaf.getLeafData());
    return wls;
  }
}

interface Visitable{
  Object accept(ComponentVisitor visitor);
}

// 如果使用 `instanceof` 进行同样的操作,代码如下:

class SerializerUsingInstanceOf {
  Object decide(Component component){
    if(component instanceof CompositeA){
      return serialize((CompositeA)component);
    }
    else if(component instanceof CompositeB){
      return serialize((CompositeB)component);
    }
    else{
      return serialize((Leaf)component);
    }
  }

  WholeCompositeASerialized serialize(CompositeA compositeA) {
    WholeCompositeASerialized wcas = new WholeCompositeASerialized();
    wcas.serializedChildren = compositeA
        .getComponents()
        .stream()
        .map(this::decide)
        .collect(Collectors.toList());
    wcas.content = compositeA.getCompositeData();
    return wcas;
  }

  WholeCompositeBSerialized serialize(CompositeB compositeB) {
    WholeCompositeBSerialized wcbs = new WholeCompositeBSerialized();
    wcbs.serializedChildren = compositeB
        .getComponents()
        .stream()
        .map(this::decide)
        .collect(Collectors.toList());
    wcbs.content = String.valueOf(compositeB.getCompositeData());
    return wcbs;
  }

  WholeLeafSerialized serialize(Leaf leaf) {
    WholeLeafSerialized wls = new WholeLeafSerialized();
    wls.content = Arrays.toString(leaf.getLeafData());
    return wls;
  }
}

// 我猜想使用访问者模式是首选的,因为当添加新的 `Component` 时,我们需要实现 `Object accept(ComponentVisitor visitor)` 方法,以便不能忘记为新组件添加序列化代码。如果我们在使用 `instanceof` 时做同样的事情,可能会忘记将其添加到该检查中。

// 现在 - 我的问题是 - 有没有办法在 `Object accept(ComponentVisitor visitor)` 方法签名中摆脱那个不好的 `Object` 返回类型?我脑海中唯一的另一种选择是使用一些标记接口(例如 `interface SerializedComponent {}`),然后让所有序列化类实现该空接口,就像这样 `class WholeCompositeASerialized implements SerializedComponent`,但这仍然不太合适。
英文:

I have following java code that is implementation of Composite Design pattern:

//composite designed for type safety (all Leaf-only operations only in leaf)
interface Component extends Visitable {
void enable();
void disable();
}
class CompositeA implements Component {
private String compositeData;
private boolean enabled;
private Set&lt;Component&gt; components = new HashSet&lt;&gt;();
CompositeA(String compositeData) {
this.compositeData = compositeData;
}
void addChild(Component component){
this.components.add(component);
}
String getCompositeData() {
return compositeData;
}
Set&lt;Component&gt; getComponents() {
return components;
}
@Override
public void enable() {
this.enabled = true;
}
@Override
public void disable() {
this.enabled = false;
}
@Override
public Object accept(ComponentVisitor visitor) {
return visitor.visit(this);
}
}
class CompositeB implements Component{
private int compositeData;
private boolean enabled;
private Set&lt;Component&gt; components = new HashSet&lt;&gt;();
CompositeB(int compositeData) {
this.compositeData = compositeData;
}
void addChild(Component component){
this.components.add(component);
}
int getCompositeData() {
return compositeData;
}
Set&lt;Component&gt; getComponents() {
return components;
}
@Override
public void enable() {
this.enabled = true;
}
@Override
public void disable() {
this.enabled = false;
}
@Override
public Object accept(ComponentVisitor visitor) {
return visitor.visit(this);
}
}
class Leaf implements Component {
private boolean enabled;
private String[] leafData;
Leaf(String[] leafData) {
this.leafData = leafData;
}
String[] getLeafData() {
return leafData;
}
@Override
public void enable() {
this.enabled = true;
}
@Override
public void disable() {
this.enabled = false;
}
@Override
public Object accept(ComponentVisitor visitor) {
return visitor.visit(this);
}
}

There are 2 possible composite roots here (CompositeA and CompositeB) and one leaf component (Leaf).

Here I define DTOs that will hold serialized data:

class WholeCompositeASerialized {                     
String content;                                     
List&lt;Object&gt; serializedChildren;                    
}                                                     
class WholeCompositeBSerialized{                      
String content;                                     
List&lt;Object&gt; serializedChildren;                    
}                                                     
class WholeLeafSerialized{                            
String content;                                     
}                                                     

Now if I use visitor pattern for serialization, I get something like this:

interface ComponentVisitor {
WholeCompositeASerialized visit(CompositeA compositeA);
WholeCompositeBSerialized visit(CompositeB compositeB);
WholeLeafSerialized visit(Leaf leaf);
}
class SerializableComponentVisitor implements ComponentVisitor{
@Override
public WholeCompositeASerialized visit(CompositeA compositeA) {
WholeCompositeASerialized wcas = new WholeCompositeASerialized();
wcas.serializedChildren = compositeA
.getComponents()
.stream()
.map(c -&gt; c.accept(this))
.collect(Collectors.toList());
wcas.content = compositeA.getCompositeData();
return wcas;
}
@Override
public WholeCompositeBSerialized visit(CompositeB compositeB) {
WholeCompositeBSerialized wcbs = new WholeCompositeBSerialized();
wcbs.serializedChildren = compositeB
.getComponents()
.stream()
.map(c -&gt; c.accept(this))
.collect(Collectors.toList());
wcbs.content = String.valueOf(compositeB.getCompositeData());
return wcbs;
}
@Override
public WholeLeafSerialized visit(Leaf leaf) {
WholeLeafSerialized wls = new WholeLeafSerialized();
wls.content = Arrays.toString(leaf.getLeafData());
return wls;
}
}
interface Visitable{
Object accept(ComponentVisitor visitor);
}

and if I use instanceof this is the code that does the same thing:

class SerializerUsingInstanceOf {
Object decide(Component component){
if(component instanceof CompositeA){
return serialize((CompositeA)component);
}
else if(component instanceof CompositeB){
return serialize((CompositeB)component);
}
else{
return serialize((Leaf)component);
}
}
WholeCompositeASerialized serialize(CompositeA compositeA) {
WholeCompositeASerialized wcas = new WholeCompositeASerialized();
wcas.serializedChildren = compositeA
.getComponents()
.stream()
.map(this::decide)
.collect(Collectors.toList());
wcas.content = compositeA.getCompositeData();
return wcas;
}
WholeCompositeBSerialized serialize(CompositeB compositeB) {
WholeCompositeBSerialized wcbs = new WholeCompositeBSerialized();
wcbs.serializedChildren = compositeB
.getComponents()
.stream()
.map(this::decide)
.collect(Collectors.toList());
wcbs.content = String.valueOf(compositeB.getCompositeData());
return wcbs;
}
WholeLeafSerialized serialize(Leaf leaf) {
WholeLeafSerialized wls = new WholeLeafSerialized();
wls.content = Arrays.toString(leaf.getLeafData());
return wls;
}
}

I guess also that visitor is preferred here because when we add new Component, we are required to implement Object accept(ComponentVisitor visitor) method also - so we cannot forget that we need a code for serialization of this new component. If we do the same when we use instanceof we would possibly forget to add it to that check.

Now - my question is - is there any way that we can get rid of that ugly Object return type in Object accept(ComponentVisitor visitor) method signature? The only other option that comes to my mind is to use some marker interface (eg. interface SerializedComponent {}) and then have all serializer classes implement that empty interface like this class WholeCompositeASerialized implements SerializedComponent but it still does not seem right.

答案1

得分: 1

我认为在这里使用泛型可能是正确的方式

例如https://onlinegdb.com/r1m5Eg4DP

    public class Main {
    
         public static void main(String []args){
            ComponentVisitor<SerializedComponent> serializer = new ComponentSerializer();
            Component componentA = new ComponentA();
            SerializedComponent serializedA = componentA.accept(serializer);
            System.out.println(serializedA);
            
            Component component = new ComponentB();
            SerializedComponent serializedB = component.accept(serializer);
            System.out.println(serializedB);
         }
         
         static interface Component {
            public <V> V accept(ComponentVisitor<V> visitor);
         }
         
         static class ComponentA implements Component {
            public <V> V accept(ComponentVisitor<V> visitor) {
                return visitor.visit(this);
            }
         }
         
         static class ComponentB implements Component {
             public <V> V accept(ComponentVisitor<V> visitor) {
                return visitor.visit(this);
             }
         }
         
         static interface SerializedComponent {}
         
         static class SerializedComponentA implements SerializedComponent {
         }
         
         static class SerializedComponentB implements SerializedComponent {
         }
         
         static interface ComponentVisitor<V> {
            public V visit(ComponentA component);
            public V visit(ComponentB component);
         }
         
         static class ComponentSerializer implements ComponentVisitor<SerializedComponent> {
            public SerializedComponent visit(ComponentA component) {
                return new SerializedComponentA();
            }
            public SerializedComponent visit(ComponentB component) {
                return new SerializedComponentB();
            }
         }
    }
英文:

I think the proper way could be to use generics here.

e.g. https://onlinegdb.com/r1m5Eg4DP

public class Main {
public static void main(String []args){
ComponentVisitor&lt;SerializedComponent&gt; serializer = new ComponentSerializer();
Component componentA = new ComponentA();
SerializedComponent serializedA = componentA.accept(serializer);
System.out.println(serializedA);
Component component = new ComponentB();
SerializedComponent serializedB = component.accept(serializer);
System.out.println(serializedB);
}
static interface Component {
public &lt;V&gt; V accept(ComponentVisitor&lt;V&gt; visitor);
}
static class ComponentA implements Component {
public &lt;V&gt; V accept(ComponentVisitor&lt;V&gt; visitor) {
return visitor.visit(this);
}
}
static class ComponentB implements Component {
public &lt;V&gt; V accept(ComponentVisitor&lt;V&gt; visitor) {
return visitor.visit(this);
}
}
static interface SerializedComponent {}
static class SerializedComponentA implements SerializedComponent {
}
static class SerializedComponentB implements SerializedComponent {
}
static interface ComponentVisitor&lt;V&gt; {
public V visit(ComponentA component);
public V visit(ComponentB component);
}
static class ComponentSerializer implements ComponentVisitor&lt;SerializedComponent&gt; {
public SerializedComponent visit(ComponentA component) {
return new SerializedComponentA();
}
public SerializedComponent visit(ComponentB component) {
return new SerializedComponentB();
}
}
}

答案2

得分: 0

你正在试图从访问者中返回具体类型信息。这不是该模式的目的。访问者在内部封装(和处理)具体类型。

解决方案在于将与 ComponentA(或您可能将其转换为的任何 A 特定类型)相关的所有逻辑都移动到 visit(ComponentA) 方法中,对于 ComponentB 也是如此。

如果您不想使用访问者的类型封装,那么不同的设计更适合,比如模式匹配。


对评论的评论...

public static void main(String[] args) {
    // 在这里使用具体类型会破坏这些模式的目的。
    // 相反,应该面向接口编程:
    // Component c1 = new CompositeA("root");
    CompositeA c1 = new CompositeA("root");
    c1.addChild(new Leaf(new String[]{"leaf11", "leaf12"}));

    CompositeA c2 = new CompositeA("composite1");
    c2.addChild(new Leaf(new String[]{"leaf21", "leaf22"}));
    c1.addChild(c2);

    SerializableComponentVisitor scv = new SerializableComponentVisitor();
    // 客户端从不直接调用访问方法,
    // 因为他们没有类型信息来进行这些调用。
    // 客户端将执行 c1.accept(scv)
    WholeCompositeASerialized wcas1 = scv.visit(c1);
}
英文:

You are attempting to return concrete type information from the Visitor. This is not the purpose of the pattern. The Visitor encapsulates (and handles) the concrete type internally.

The solution here is to move all logic specific to ComponentA (or any A-specific type you might convert it to) into the visit(ComponentA) method, and likewise for ComponentB.

If you don't want the type encapsulation of the Visitor, then a different design is more suitable, such as pattern matching.


Comments on the comment...

public static void main(String[] args) {
// Using a concrete type here defeats the purpose of these patterns.
// Instead, program to an interface:
// Component c1 = new CompositeA(&quot;root&quot;);
CompositeA c1 = new CompositeA(&quot;root&quot;);
c1.addChild(new Leaf(new String[]{&quot;leaf11&quot;, &quot;leaf12&quot;}));
CompositeA c2 = new CompositeA(&quot;composite1&quot;);
c2.addChild(new Leaf(new String[]{&quot;leaf21&quot;, &quot;leaf22&quot;}));
c1.addChild(c2);
SerializableComponentVisitor scv = new SerializableComponentVisitor();
// Clients never invoke visit methods directly,
//  because they do not have the type information to make these calls.
// A client would execute, c1.accept(scv)
WholeCompositeASerialized wcas1 = scv.visit(c1);
}

huangapple
  • 本文由 发表于 2020年10月13日 21:29:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/64336144.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定