0

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?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
  • I would use a simple interface rather than a base class. Something like `interface Named` that contains `getName()` is generic enough to be usable by classes that have nothing in common except a name. – k314159 Jul 18 '22 at 15:38
  • Are you sure you are using `extends` keyword in your abstract class? I think you should use `implements` before interface? – Ashish Patil Jul 18 '22 at 15:38
  • No need - ChaosFire has already posted almost exactly the same answer I would have written. – k314159 Jul 18 '22 at 15:45
  • `T` is unknown, therefore how would compiler know that `T` will have `getName` method? The only thing that compiler knows about what `T` might be, is that it will be an `Object` – Antoniossss Jul 18 '22 at 15:46
  • 1
    What Ashish was saying was that you wrote `AbstractPDFGenerator extends PDFGenerator` which is incorrect syntax. It should be `AbstractPDFGenerator implements PDFGenerator` because PDFGenerator is an interface, not a class. – k314159 Jul 18 '22 at 15:48
  • @LeeVanCleef, please share full `AbstractPDFGenerator` (may be pseudo-code ), will have a look once you share that – Ashish Patil Jul 18 '22 at 16:10
  • @LeeVanCleef, so as I was thinking, `protected abstract T getDTO(UUID uuid)` resolved your issue right? I was thinking of posting answer but thought to ask before. – Ashish Patil Jul 18 '22 at 16:23
  • @LeeVanCleef, regarding new doubt, I think you can post new question if your original issue for which you have raised question is resolved; thanks. – Ashish Patil Jul 19 '22 at 10:09

1 Answers1

0

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.

Chaosfire
  • 4,818
  • 4
  • 8
  • 23