【问题标题】:What's an appropriate design for creating different objects/invoking different methods depending on use case?根据用例创建不同对象/调用不同方法的合适设计是什么?
【发布时间】:2021-05-28 18:17:45
【问题描述】:

我不太确定这个用例叫什么,但让我解释一下:我正在开发一个上传数据的服务。我的服务处理不同类型的上传,并且每种上传类型都是唯一的,例如UPLOAD_A 的内容!= UPLOAD_B 的内容。

我收到的数据是一个 json 文件。从 json 文件中,我知道它是什么类型的上传。我需要将此 json 解析为适当的 UploadDataX 对象。例如,如果我得到一个 UPLOAD_A 的 json 数据文件,我需要将它序列化为一个 UploadDataA 对象。

拥有UploadDataX 对象后,我需要上传数据。我通过使用外部客户端对象来做到这一点:UploadClient()。但是,我需要根据数据类型调用特定的上传方法。例如,对于UPLOAD_A 类型,我需要调用UploadClient().uploadDataA(UploadDataA),对于UPLOAD_B 类型,我需要调用UploadClient().uploadDataB(uploadDataB)

问题是我不拥有这些UploadDataX 和客户端对象中的任何一个。不幸的是,UploadDataX 不属于超类。我想知道处理这个用例的有效设计模式是什么?

我可以想到一个蛮力解决方案,使用简单的 switch-case 语句:

// BRUTE FORCE SOLUTION
public void upload(UploadType uploadType, String jsonString) {
   switch (uploadType) {
      case UPLOAD_A: {
         UploadDataA uploadDataA = Gson().fromJson(jsonString, UploadDataA.class);
         UploadClient().uploadDataA(uploadDataA);
         break;
      }
      case UPLOAD_B: {
         UploadDataB uploadDataB = Gson().fromJson(jsonString, UploadDataB.class);
         UploadClient().uploadDataB(uploadDataB);
         break;
      }
   }
   ...
}

如您所见,这似乎是重复的,我不禁觉得有更好的设计方法。我在考虑工厂模式,但我想看看其他人的想法。

【问题讨论】:

  • 你需要的是一个简单的多态性:) 如果你有一个UploadType,让它成为一个单一方法upload(json)的接口。然后你可以有不同的实现:UplaodA implements UploadType 等,每个upload() 方法都会有来自你上面的 switch arm 的相应 impl :)

标签: java design-patterns


【解决方案1】:

假设你事先知道上传类型,如你的例子,那么你只需要一个简单的多态:

interface UploadType{
   void upload(String json) throws SomeException;
}

class UploadTypeA implements UploadType{
    void upload(String json) throws SomeException{
        //code from your example
        UploadDataA uploadDataA = Gson().fromJson(jsonString, UploadDataA.class);
        UploadClient().uploadDataA(uploadDataA);
    }
}

class UploadTypeB implements UploadType{
    void upload(String json) throws SomeException{
        //code from your example
        UploadDataB uploadDataB = Gson().fromJson(jsonString, UploadDataB.class);
        UploadClient().uploadDataB(uploadDataB);
    }
}

那么您示例中的上传方法将如下所示:

public void upload(UploadType uploadType, String jsonString) {
    uploadType.upload(jsonString);
}

所以如果里面没有任何其他代码,你可以删除/内联它

【讨论】:

  • 问题:这似乎需要将我的 UploadType 从 Enum 移动到接口。除此之外,我正在努力理解这有什么好处,因为我只是将所有序列化和特定调用移到另一个类中
  • 枚举也可以实现接口,而且每个变体可以有不同的实现(有或没有接口)。关于您问题的第二部分,请参阅replace conditional with polymorphism
【解决方案2】:

试试这个:

public enum UploadType implements BiConsumer<UploadClient, String> {
    UPLOAD_A(UploadDataA.class, (ulc, obj) -> ulc.uploadDataA((UploadDataA) obj)),
    UPLOAD_B(UploadDataA.class, (ulc, obj) -> ulc.uploadDataB((UploadDataB) obj))
    // etc
    ;

    private Class<?> clazz;
    private BiConsumer<UploadClient, Object> uploader;

    UploadType(Class<?> clazz, BiConsumer<UploadClient, Object> uploader) {
        this.clazz = clazz;
        this.uploader = uploader;
    }

    @Override
    public void accept(UploadClient uploadClient, String jsonString) {
        Object obj = Gson().fromJson(jsonString, clazz);
        uploader.accept(uploadClient, obj);
    }
}

然后:

public void upload(UploadType uploadType, String jsonString) {
    uploadType.accept(UploadClient(), jsonString);
}

瞧!

这最大限度地提高了代码重用率,并最大限度地减少了添加更多上传类型的工作量。


为了保持分离,使用映射来查找类和 lambda:

private Map<UploadType, Class<?>> uploadClasees = new HashMap<>() {{
    put(UploadType.UPLOAD_A, UploadDataA.class);
    put(UploadType.UPLOAD_B, UploadDataB.class);
}};

private Map<UploadType, BiConsumer<UploadClient, Object>> uploaders = new HashMap<>() {{
    put(UploadType.UPLOAD_A, (ulc, obj) -> ulc.uploadDataA((UploadDataA) obj));
    put(UploadType.UPLOAD_B, (ulc, obj) -> ulc.uploadDataB((UploadDataB) obj));
}};

public void upload(UploadType uploadType, String jsonString) {
    BiConsumer<UploadClient, Object> uploader = uploaders.get(uploadType);
    Class<?> clazz = uploadClasees.get(uploadType);
    Object obj = Gson().fromJson(jsonString, clazz);
    uploader.accept(new UploadClient(), obj);
}

【讨论】:

  • 我喜欢这个解决方案,因为调用部分非常简单。我有点担心的是有点小。我的 UploadType 和 UploadClient 位于不同的包中(本质上是不同的存储库),因为我想遵循良好的职责分离。如果我采用这种方法,那么我的 UploadType 包将不得不依赖于我的客户端包,这并不理想,因为反向依赖已经存在。循环依赖是个问题;我想我必须将客户端移动到另一个包然后
  • 我知道你的意思,这是一个公平的批评。您可以通过使用 UploadType 到 lambda 的映射来将它们分开,并且该映射可以就在您的代码中。
  • 能不能详细说明一下地图建议,我不关注
  • @Kiwibreeder 查看解耦版本的编辑答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-14
  • 2022-01-21
  • 2016-12-26
相关资源
最近更新 更多