首页 > 解决方案 > Java, Inheritance, Generics - using parameters of subtypes in template method implementations

问题描述

I am building an application that is generating PDF documents for sales orders and sales invoices. For simplicity I have exluded redundant logic and fields.

Here is my class structure:

public class SalesEntity {
    public String name;
    public String createdDate;
}

public class SalesOrder extends SalesEntity {
}

public class SalesInvoice extends SalesEntity {
    public String invoiceSpecificField;
}

and similar scturcure for wrapper and list items:

public class SalesEntityItem {
    public String name;
    public String price;
}

public class SalesOrderItem extends SalesEntityItem {
}

public class SalesInvoiceItem extends SalesEntityItem {
    public String invoiceItemSpecificField;
}

public class SalesEntityResponse {
    public SalesEntity salesEntity;
    public List<SalesEntityItem> salesEntityItems;
}

and here is first part of the problem.

public class SalesOrderEntityResponse extends SalesEntityResponse {
//    public SalesOrder salesEntity; <-- say somehow to java that in this subclass the property type should also be subclass
//    public List<SalesOrderItem> salesEntityItems;
}

For building mechanism I am using template method:

public class PDFBuilder extends AbstractPDFBuilder {
    protected void buildPdfDocument(Map<String, Object> model /*...*/) throws Exception {
        /*...*/
        addEntityNumber(document, salesEntityResponse);
        addItems(document, salesEntityResponse);
    }
}

public class OrderPDFBuilder extends PDFBuilder {
    /*...*/
    @Override
    protected void addEntityNumber(Document document, SalesEntityResponse entityResponse) throws DocumentException {
        /*...*/
        PdfPTable documentNameTable = new PdfPTable(1);
        Phrase documentNamePhrase = new Phrase(entityResponse.labels.account_number, timesFont);
        PdfPCell documentNameCell = new PdfPCell(documentNamePhrase);
        documentNameTable.addCell(documentNameCell);
        document.add(documentNameTable);
    }

    @Override
    protected void addItems(Document document, SalesEntityResponse entityResponse) throws DocumentException {
        /*...*/
        for (SalesEntityItem salesEntityItem : entityResponse.salesEntityItems) {
            /* Items adding specific logic */
        }
    }
}

And if first part of somwhow solveable, here comes main part: Question: How can I make specific template method implementations (OrderPDFBuilder, InvoicePDFBuilder) receive parameters of subtype SalesEntityResponse (SalesOrderResponse and SalesInvoiceResponse) respectively? So that in specific implementations I can use specific fields of those entities. Does it make sense? I assume here is something related to bounded types, but I am not sure how to use it properly.

标签: javagenericsinheritancetypes

解决方案


As you've established generics are how you achieve this. So firstly you'll need to stick your generics decleration on the SalesEntityResponse:

public class SalesEntityResponse<T extends SalesEntity, U extends SalesEntityItem> {
    public T salesEntity;
    public List<U> salesEntityItems;
}

Then in the declaration of your subtypes you tell it what concrete types they hold:

public class SalesOrderEntityResponse extends SalesEntityResponse<SalesOrder, SalesOrderItem> {

}

To make the "magic" work in your addItems method you'll have to also have to add generics to which ever class/interface declares the addItems method, this is presumably AbstractPDFBuilder? So something like:

public abstract class AbstractPDFBuilder<T extends SalesEntity, U extends SalesEntityItem, V extends SalesEntityResponse<T, U>> {
    protected abstract void addItems(Document document, V entityResponse) throws DocumentException;
    }
}

And then your concrete PDFBuilder types need to supply the relavant generics:

public class SalesOrderPDFBuilder extends AbstractPDFBuilder<SalesOrder, SalesOrderItem, SalesOrderEntityResponse> {
    protected void addItems(Document document, SalesOrderEntityResponse entityResponse) {
    }
}

推荐阅读