【问题标题】:Implements a Factory Pattern in java with using Generics使用泛型在java中实现工厂模式
【发布时间】:2022-01-08 23:28:31
【问题描述】:

我需要 Java 泛型方面的帮助。 我的模型是:我有一些扩展 Dto(数据传输对象)的类和一些扩展实体的类(我的对象到 DB 的模型)。 我有

interface Mapper<D extends Dto, E extends Entity>{
//Convert a Entity to Dto.
D toDto(E entity);

我有一些实现这个接口的类(即 PersonMapper、BookMapper 等等)。

@Component    
public class PersonMapper implements Mapper<PersonDto, PersonEntity> {
    //implementation
    }

@Component    
public class BookMapper implements Mapper<BookDto, BookEntity> {
    //implementation
    }

我想要做的是使用工厂模式以便在运行时选择我的映射器,这取决于我在输入中传递的字符串。

@Autowired
private PersonMapper personMapper;
@Autowired
private BookMapper bookMapper;    

public <D extends Dto, E extends Entity> Mapper<D, E> selectMapper(String entity){
      if ("Person".equalsIgnoreCase(entity))
         return personMapper;
      if("Book".equalsIgnoreCase(entity))
        return bookMapper;
      ...
    }

在这种情况下,我有以下编译错误:

Type mismatch: cannot convert from PersonMapper to Mapper<D,E>

我的解决方案:

1)

return (Mapper<D, E>) personMapper;

但我有一个警告:

Type Safety: `Unchecked class from personMapper to Mapper<D,H>`

2)

使用通配符和强制转换b

public Mapper<Dto, Entity> selectMapper(String entity){
          Mapper<? extends Dto, ? extends Entity> toReturn = null;
          if ("Person".equalsIgnoreCase(entity))
             toReturn = personMapper;
          else if("Book".equalsIgnoreCase(entity))
            toReturn = bookMapper;
          ...
          return (Mapper<Dto, Entity>) toReturn;
        }

但在这种情况下,我还有一次警告:

Type safety: Unchecked cast from Mapper<capture#29-of ? extends Dto,capture#30-of ? extends Entity> to Mapper<Dto,Entity>

它有效,但似乎不是一个干净的解决方案

3) 使用通配符作为返回类型:

public Mapper<? extends Dto, ? extends HistoryEntity> selectMapper(String entity)

但你知道,根本不建议使用通配符作为返回类型,也对我没有帮助,因为我想使用这个映射器并调用 mapper.toDto 以确保返回类型是扩展 Dto 的东西。

============================================== =========================

我不解释为什么如果我写一个这样的类构造函数

public Service<D extends Dto, E extends Entity>{  
   public Service(Mapper<D,E> mapper){ 
      this.mapper = mapper; 
   } 
} 

然后我注入(例如)bookMapper 就可以了。

如果 Mapper 是返回类型,我不能做这种操作。

============================================== =========================

我请求你的帮助是:

如何使用干净的代码原则(避免编译警告、sonarlint 问题等)编写解决方案以实现这种逻辑?

非常感谢,如果您能抽出一点时间帮助我解决问题,我将不胜感激。

【问题讨论】:

  • 这有点像一个经典问题,正如@rzwitserloot 所建议的那样——它正在重新发明一个轮子。无论如何,使用 sting + if-else 链并不是一个好习惯,因此最好将参数 String entity 至少替换为 Class&lt;? extends Entity&gt; entity 并使用 class =&gt; mapper 的映射来查找映射器。但即使这样也不会为您提供足够的运行时类型安全性,因为 ED 是不相关的类型。技术上可能存在许多 ED 映射器,除非您显式绑定(通过泛型)Entity XDto Y

标签: java generics design-patterns factory


【解决方案1】:

那些关于调用者而不是你的代码的变量(D 和 E)。 D 和 E 由调用者决定,因此绝对无法保证 PersonDTO 适合。

制作Mapper&lt;? extends DTO, ? extends Entity&gt;(并且没有变量),并且鉴于这些已经是下限,只需Mapper&lt;?, ?&gt; - 这会起作用,您可以编写您的return 语句而无需任何强制转换并且没有编译器错误或警告。

当然,这意味着调用者有一个几乎没用的类型。

泛型完全基于“编译时间/写入时间”。 JVM (java.exe) 不知道泛型是什么,事实上它们中的大多数都无法在编译过程中存活下来。 泛型的唯一目的是让编译器标记不正确的代码并避免一些强制转换,仅此而已

将该字符串转换为 Mapper 的性质完全是运行时的。

所以,如果Mapper&lt;?, ?&gt; 不够用,你想要的就不可能了。您需要编写编译/写入时可检查的内容,因此在您使用字符串的那一刻,这是不可能的。比如方法getPersonMapper()当然可以返回Mapper&lt;PersonDTO, PersonEntity&gt;,没问题。

更一般地说(呵呵)听起来你在这里严重地重新发明了各种轮子。查看 JDBI、JOOQ 和 Hibernate 的教程,了解 Java 代码通常是如何与数据库交互的。

【讨论】:

  • “如果Mapper&lt;?, ?&gt; 不够用,那么你想要的就是不可能的。”这是正确的,应该是这个答案的关键。
  • 您好,感谢您的回答。我知道使用通配符我没有编译警告,但你知道,根本不建议在返回类型中使用通配符。我不解释为什么如果我编写一个像 public Service&lt;D extends Dto, E extends Entity&gt;{ public Service(Mapper&lt;D,E&gt; mapper){ this.mapper = mapper; } } 这样的类构造函数,然后注入(例如)bookMapper 它就可以工作。相反,如果 Mapper 是返回类型,我不能执行这样的操作
  • 不建议在返回类型中使用通配符 - 这是完全错误的建议。告诉你的人/书是完全不正确的,应该被视为无能,或者更可能是在该建议的作者说/写它和你听到/读它之间的翻译中丢失了一些重要的东西。跨度>
  • 我假设这些是 Oracle 的 Guidelines for Wildcard UseEffective Java
  • 托管在 oracle.com 上的事实并不是一个好的建议。在这种情况下,他们试图强调您强迫呼叫者处理它的问题(我特别在我的回答中强调了它!)——但这并不总是一个问题,这取决于API。我可以向您展示很多很多返回通配符的java.* API,因此,他们的手很明显这是过于简单的建议,并不总是适用。
【解决方案2】:

Factory Pattern 是通过工厂方法组装或创建某些东西的模式,在您的情况下,您只需要按名称获取相应的映射器,因此有一种简单的方法可以做到这一点,因为映射器 bean 是自动装配的,添加 @987654321 @ to Mapper 接口然后为每个实现实现它,例如在 BookMapper 中

    @Override
    public String getName() { return "Book"; }

使用 mapper name 作为 key 和 mapper bean 作为 value 将 mapper beans 存储在 map 中,然后你可以通过它的 name 来检索它:

@Service
public class SimpleService {
    private BookMapper bookMapper;
    private PersonMapper personMapper;
    private Map<String, Mapper<? extends DTO, ? extends Entity>> mappers = new HashMap<>();

    public SimpleService(BookMapper bookMapper, PersonMapper personMapper) {
        this.bookMapper = bookMapper;
        this.personMapper = personMapper;
        mappers.put(bookMapper.getName(), bookMapper);
        mappers.put(personMapper.getName(), personMapper);
    }

    public Mapper<? extends DTO, ? extends Entity> getMapperByName(String mapperName) {
        return mappers.get(mapperName);
    }
}

您可以将其转换为相应的映射器而不会发出警告。

        PersonMapper p = (PersonMapper) simpleService.getMapperByName("Person");

或者你可以在他们的服务中放置不同的映射器,并使用该服务来处理你喜欢的代码,毕竟你需要指定的映射器来做指定的操作:

     if(personThings){
         personService.doSomeThing();
      }
     if(bookThings){
         bookService.doSomething();
     }

【讨论】:

  • 解决方案容易出错。 1。名称和映射器之间没有 100% 的保证连接。可能有多个版本的PersonMappers 对不同 DTO 或从不同实体到相同 DTO 的映射逻辑略有不同,并且可能存在不同命名中的人为错误。 2。它不是 null 安全的,除非它返回 Optional 或抛出异常。 3。强制转换实际上并不安全,并且可能很容易在运行时导致 class-cass 异常。此外,它会针对在返回类型上使用通配符发出严重的声纳警告
  • @dshelya 谢谢你的评论,我考虑过,最后提到可以使用服务来封装Entity,Dto关系和业务逻辑,然后操作服务而不是mapper,不需要担心类型转换问题
  • 感谢您的评论。我会考虑你的建议。我很感激
猜你喜欢
  • 2016-03-21
  • 1970-01-01
  • 2014-04-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多