简体   繁体   English

使用 Generics 在 java 中实现工厂模式

[英]Implements a Factory Pattern in java with using Generics

I need help with Java Generics.我需要 Java Generics 方面的帮助。 My model is: I have some classes that extends a Dto (Data Transfer Object) and some classes that extends Entity (The model of my object to DB).我的 model 是:我有一些扩展 Dto(数据传输对象)的类和一些扩展实体的类(我的 object 的 model 到 DB)。 I have我有

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

And I have some classes that implements this interface (ie PersonMapper, BookMapper and so far and so on).而且我有一些实现这个接口的类(即PersonMapper、BookMapper 等等)。

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

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

What I want to do is to use Factory Pattern in order to select at runtime my Mapper, that depends from a String that I pass in input.我想要做的是使用工厂模式以便在我的 Mapper 运行时使用 select,这取决于我在输入中传递的字符串。

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

With this situation I have the following compile error:在这种情况下,我有以下编译错误:

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

My solutions:我的解决方案:

1) 1)

return (Mapper<D, E>) personMapper;

but I have a Warning:但我有一个警告:

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

2) 2)

Using WildCard and castingb使用通配符和 castb

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

But in this case but I have another time a Warning:但在这种情况下,我还有一次警告:

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

It works but it doesn't seems to be a clean solution它有效,但似乎不是一个干净的解决方案

3) Using wildcard as return type: 3)使用通配符作为返回类型:

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

but you know, using wildcard as return type is not recommended at all and also doesn't help me because I would like to use this mapper and call mapper.toDto ensuring that the return type is an something that extends Dto.但是您知道,根本不建议使用通配符作为返回类型,也对我没有帮助,因为我想使用这个映射器并调用 mapper.toDto 以确保返回类型是扩展 Dto 的东西。

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

I don't explain why If I write a class constructor like that我不解释为什么如果我这样写一个 class 构造函数

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

and than I inject (for example) bookMapper it works.并且比我注入(例如) bookMapper 它有效。

If, instead, the Mapper<D,E> is in return type I cannot do such a kind of operation.相反,如果 Mapper<D,E> 是返回类型,则我无法执行此类操作。

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

The help that I ask to you is:我向您请求的帮助是:

how can I write a solution using clean code principles (avoiding compile warnings, sonarlint issue etc.) in order to implement this kind of logic?为了实现这种逻辑,我如何使用干净的代码原则(避免编译警告、sonarlint 问题等)编写解决方案?

Thank you very much, I appreciate a lot if you dedicate a little bit of your time helping me to solve my problem.非常感谢您,如果您能抽出一点时间帮助我解决我的问题,我将不胜感激。

Those vars (D and E) about the caller and not about your code.那些关于调用者而不是你的代码的变量(D 和 E)。 The D and E are decided by the caller, so there is absolutely no way to guarantee that PersonDTO fits. D 和 E 由调用者决定,因此绝对无法保证PersonDTO适合。

Make that Mapper<? extends DTO, ? extends Entity>制作那个Mapper<? extends DTO, ? extends Entity> Mapper<? extends DTO, ? extends Entity> Mapper<? extends DTO, ? extends Entity> (and no variables), and given that those are already the lower bounds, just Mapper<?, ?> - that'll work, you can write your return statements without any casts and without compiler errors or warnings. Mapper<? extends DTO, ? extends Entity> (并且没有变量),并且鉴于这些已经是下限,只需Mapper<?, ?> - 这将起作用,您可以编写return语句而无需任何强制转换,也不会出现编译器错误或警告。

Of course, it means the caller has a mostly useless type.当然,这意味着调用者有一个几乎没用的类型。

Generics are entirely 'compile time / write time' based. Generics 完全基于“编译时间/写入时间”。 The JVM ( java.exe ) has no idea what generics are, and in fact most of them don't survive the compilation process. JVM ( java.exe ) 不知道 generics 是什么,实际上它们中的大多数都无法在编译过程中存活下来。 The one and only purpose of generics is to make the compiler flag incorrect code and avoid some casting, that is all . generics 的唯一目的是使编译器标记不正确的代码并避免一些强制转换,仅此而已

The nature of turning that string into a Mapper is entirely runtime.将该字符串转换为 Mapper 的性质完全是运行时的。

Ergo, if Mapper<?, ?> isn't sufficient, what you want isn't possible.因此,如果Mapper<?, ?>还不够,那么您想要的就是不可能的。 You'd need to write compile/write-time checkable stuff, so the moment you use a String, it's impossible.您需要编写编译/写入时可检查的内容,因此在您使用字符串的那一刻,这是不可能的。 For example, a method getPersonMapper() can of course return a Mapper<PersonDTO, PersonEntity> , no problem.例如,方法getPersonMapper()当然可以返回Mapper<PersonDTO, PersonEntity> ,没问题。

More generally (heh) it sounds like you're badly reinventing various wheels here.更一般地说(呵呵)听起来你在这里严重地重新发明了各种轮子。 Look at tutorials of JDBI, JOOQ, and Hibernate to get some ideas about how java code is commonly written to interact with databases.查看 JDBI、JOOQ 和 Hibernate 的教程,了解如何编写 java 代码以与数据库交互。

Factory Pattern is pattern that assemble or create something by factory methods, in you case what you need is just to get corresponding mapper by name, so there is a simple ways to do that since the mapper beans are autowired, adding String getName() to Mapper interface then implements it for earch implementation, eg in BookMapper工厂模式是通过工厂方法组装或创建某些东西的模式,在您的情况下,您只需要按名称获取相应的映射器,因此有一种简单的方法可以做到这一点,因为映射器 bean 是自动装配的,将String getName()添加到然后 Mapper 接口为每个实现实现它,例如在 BookMapper 中

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

use mapper name as key and mapper bean as value to store mapper beans in a map, then you can retrieve it by its name:使用映射器名称作为键和映射器 bean 作为值将映射器 bean 存储在 map 中,然后您可以通过其名称检索它:

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

and you can cast it to corresponding mapper without warning.您可以将其投射到相应的映射器而不会发出警告。

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

or you can put different mapper in their service and use the service to handle you biz likes codes below, after all, you need specified mappers to do specified operations:或者您可以在他们的服务中放置不同的映射器并使用该服务来处理您喜欢的代码,毕竟您需要指定的映射器来执行指定的操作:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM