javadesign-patternsinterfaceabstract-classtemplate-method-pattern

Cannot use generic in abstract class


In my Spring Boot app, I am trying to implement Template Method and in my concrete class, I am trying to use generic as shown below:

template interface: Not sure if I need to use it?

public interface PDFGenerator {

    String createHtml(UUID uuid);
}

template abstract class:

public abstract class AbstractPDFGenerator<T> implements PDFGenerator {

    @Override
    public String createHtml(UUID uuid) {
        T dto = getDTO(uuid);

        Context context = new Context();        
        context.setVariable(getName(), dto.getName());

        // ...
    }

    protected abstract T getDTO(UUID uuid);

    protected abstract String getName();

    // code omitted
}

concrete classes:

@Service
@RequiredArgsConstructor
public class BrandPDFGenerator extends AbstractPDFGenerator<BrandDTO> {

    private static final String NAME = "brandName";
    private final BrandService brandService;

    @Override
    protected String getName() {
        return NAME;
    }

    @Override
    protected BrandDTO getDTO(UUID uuid) {
        return brandService.findByUuid(uuid);
    }

    // ...
}
@Service
@RequiredArgsConstructor
public class ProductPDFGenerator extends AbstractPDFGenerator<ProductDTO> {

    private static final String NAME = "productName";
    private final ProductService productService;

    @Override
    protected String getName() {
        return NAME;
    }

    @Override
    protected ProductDTO getDTO(UUID uuid) {
        return productService.findByUuid(uuid);
    }

    // ...
}

I get "Cannot resolve method 'getName' in 'T'" at the dto.getName() line in AbstractPDFGenerator.

My questions are:

  1. In order to fix the problem, I think of extending T from a base class from which BrandDTO and ProductDTO are inherit. However, I do not want to inherit them from a base class as they have not used similar purpose. So, how can I fix that problem ("Cannot resolve method 'getName' in 'T'")?

  2. Do I need to use PDFGenerator interface? Or should I remove it?


Solution

  • Option 1: Use interface, not abstract class. Abstract class will work, but dtos must have correct relationship with each other. If they don't have is a relationship with the base class, it will be wrong.

    public interface HasName {
      String getName();
    }
    

    Then make BrandDTO and ProductDTO implement it, and introduce a bound for T - only implementations of HasName.

    public abstract class AbstractPDFGenerator<T extends HasName> implements PDFGenerator
    

    Option 2: Don't use generics. Keep in mind this option won't let you use template method pattern and will lead to code duplication.

    public abstract class AbstractPDFGenerator implements PDFGenerator {
    
      @Override
      public String createHtml(UUID uuid) {
        Context context = new Context();
        this.setDataInContext(context, uuid);
        //other operations
        return ...;
      }
    
      protected abstract void setDataInContext(Context context, UUID uuid);
    }
    

    Concrete implementations of setDataInContext will take care to set correct data, they will work with the actual dto, and generic is not needed.

    public class ProductPDFGenerator extends AbstractPDFGenerator {
    
      private static final String NAME = "productName";
    
      private final ProductService productService;
    
      @Override
      protected void setDataInContext(Context context, UUID uuid) {
        ProductDTO dto = productService.findByUuid(uuid);
        context.setVariable(NAME, dto.getName());
        //other operations
      }
    }
    

    The implementation of BrandPDFGenerator will be the same, except finding the dto.