Complementary generic types

2019-05-30 08:01发布

问题:

Premise: In my project I have two generically typed interfaces defining Request and Response respectively. A request is processed to yield a response, hence every response is built based on a request. A Processor interface processes a request to build the corresponding response.

Code: The request and response interfaces are:

interface Request<T1>

and

interface Response<T2>

respectively, where T2 and T1 represent generic request and response types (I am deliberately calling them by different names for clarity).

Now, since T2 is a Request, and T1 is a response, so the above code evolves to:

interface Request<T1 extends Response>

and

interface Response<T2 extends Request>

Note that: Request and Response interfaces do not share any inheritance relationship - what the above code only intends to communicate is: Request is typed with only some other type which is-a Response.

Now, consider the Request interface: since Response is again typed, and the response built out of a request will be tied to the original request type, hence, the above code evolves to:

interface Request<T1 extends Response<? extends Request<T1>>>

and

interface Response<T2 extends Request<? extends Response<T2>>

Now, the Processor interface is defined as:

interface Processor<R1 extends Request<R2>, R2 extends Response<R1>> {
    R2 process(R1 request);
}

Concrete classes:

Request implementation:

class ConcreteRequest implements Request<ConcreteResponse> {
    ConcreteResponse response;
    ...`
}

Response implementation:

class ConcreteResponse implements Response<ConcreteRequest> {
    ConcreteRequest request;
    ...
}

Processor implementation:

class ConcreteProcessor implements Processor<ConcreteRequest, ConcreteResponse> {
    ConcreteResponse process(ConcreteRequest request) {
    ...
    }
}

Question: Is the above code over-designed? Is there a simplified way to represent a tuple of complementary input-output objects?

回答1:

Unless I've totally misunderstood your question, you don't - and shouldn't - use generic for this kind of problem. Using polymorphism and/or composition will be much more appropriate. For example, if you need to integrate a copy of the request in the response (hardly necessary but thinkable) then you can add a reference to a request object in your response class.

Technically, this reference to a Request object could be defined using a type; however, you shouldn't do that because it will always be a Request object (either a base class or a derived subclass) and not some kind of arbitrary class that could change with each instanciation of a response.

You use generic when the type of each referenced object is totally different (for example, a List <String> or a List<Request>: there is no subclassing relationship between a String and a Request object) or when the use of polymorphism will not be sufficient because you are defining one or more new virtual functions in a subclass that are not present in the superclass.

Building a Response to be based on a Request because a Request is processed to yield a Response is definitely not the way to go and your current Processor interface is a testimony to that.



回答2:

I think you don't need to link the Request and the Response in the type definition. They are tied by the Processor. Isn't something like

interface Requestable {
    ...
}

class Request<T extends Requestable> {
    ...
}

class Response<T extends Requestable> {
    ...
}

class Processor<T extends Requestable> {
    Response<T> process(Request<T> request) {
        ...
    }
}

sufficient ? Actually I'm not sure you need generic at all.



回答3:

There is one use case where it's useful to have a generified Request/Response (or well, at least the Request). If the request is generified to contain the response type, the following call is "typesafe"

public <messy define of T> T sendRequest(Request<T> request)  

Now users of that method will see "typesafe" request-response calling. I'm calling that only "typesafe" because the implementation of that method probably has to cast the responses to T, so ClassCastExceptions are theoretically possible, but in most cases they would be considered as application logic errors.

I would not put the actual fields of Request/Response inside other, just use the generic typeinformation for "typesafe" request-response calling.