3

I'm trying to build a factory of object instances of a type T extends BaseClient, with the returned instances having one of the methods of T overloaded to perform some additional logic. I cannot modify the code of the T classes (which is why I need the overloading), nor that of the base abstract class BaseClient.

This is easy to do with concrete, well-defined types. For example, given a FooClient extends BaseClient, I can do this:

public class FooClientFactory {
    public static FooClient create() {
        return new FooClient() {
            @Override
            public void theMethod() {
                super.theMethod();
                someAdditionalLogic();
            }
        };
    }
}

But in a world where I need to deal with potentially many different types of clients (all subclasses of BaseClient), I would ideally like to have something like this:

public class ClientFactory<T extends BaseClient> {
    public static T create() {
        return ...;
    }
}

So I don't have to duplicate the above code for each concrete subclass.

Unfortunately, it's not possible to write new T() { ... }. It's also not possible to use a Class<T> and call newInstance() on it, since that doesn't allow me to override the behavior of theMethod(). Using a Proxy instance would allow me to intercept method calls and match on the method name (ugly) to perform someAdditionalLogic() when required, but because BaseClient is not an interface -- and I can't make it be one -- I can't build a proxy (to be more precise in my actual use case, I do have an interface I can use, specific to each client, but there are places where the returned instance needs to be cast to a BaseClient, which the returned Proxy instance does not "technically" implement/advertise because of how proxy instances work, leading to ClassCastExceptions).

So, what gives? Is this at all possible in Java, or is copy-pasting FooClientFactory for each XXClient type my only option?

  • Would passing a `Class` object and calling `newInstance()`, per [this answer](https://stackoverflow.com/a/6093448/4742570), be an option? – jsheeran Jan 29 '18 at 11:42
  • 2
    As I said in the second to last paragraph of my question, unfortunately no. It would indeed allow me to create an instance, but I still wouldn't be able to override `theMethod()`s behavior. – Maxime Petazzoni Jan 29 '18 at 17:20
  • I guess that ```MyClient``` means to be ```FooClient``` – Valentin Ruano Jan 29 '18 at 18:39
  • Yup, thanks, went back and forth while writing my question. Fixed now. – Maxime Petazzoni Jan 29 '18 at 18:50
  • what is the problem of using lambdas to declare such factories? Parameter less example: ```() -> new FooClient() { return new FooClient() { ... overrides ... }}```. This would by typed as ```Supplier extends FooClient>```. – Valentin Ruano Jan 29 '18 at 18:54
  • In any case, using "standard" practices you cannot create classes dynamically and all anonymous inner classes are composed and compiled at compilation time... Every time you create an override of ```theMethod``` you are actually creating a different class so it dictates that somewhere you have a explicitly declaration (top or inner) of such class. An attempt to template it out using ```new T``` fails as the compiler cannot be certain of what ```T``` class actually is except if ```T``` is trivially bounded to extend a final class such as String in which case a extension is impossible. – Valentin Ruano Jan 29 '18 at 19:06
  • There are libraries that allow you to engineer classes at run-time like apache's BCEL and passing your client class (FooClient.class) and a lambda with the changed behavior you could achieve what you intent... but that is rather overkill in most settings. – Valentin Ruano Jan 29 '18 at 19:13
  • So, you **do** have an interface for each client, but when using the client, you might need to cast the client to `BaseClient`, but never to the class that actually implements the interface. Is this correct? In your example, if `FooClient` implements interface `Foo`, this would be the actual type you'd need the factory to return. Did I get it right? – fps Jan 29 '18 at 20:42
  • Also, do you have a common interface for all clients? – fps Jan 29 '18 at 20:55
  • @ValentinRuano, nothing wrong with using a lambda here to make the code more compact, but it doesn't change the fact that it's not generic for all client types. – Maxime Petazzoni Jan 29 '18 at 20:58
  • @FedericoPeraltaSchaffner, I do not have a common interface for all clients. – Maxime Petazzoni Jan 29 '18 at 20:58
  • 1
    If you are looking for a ```new T() { ... override ... }``` solution, that is not possible in Java, there is no debate about it (with the "overkill" exception of using instrumentation libraries such as BCEL). I get that your actual intention here is either maximize code sharing or more generally minimize code lines in general. I think the lambda is probrably the closest you can get to that without changing the BaseClient/Factory framework much. – Valentin Ruano Jan 29 '18 at 21:22
  • However if the ```someAdditionalLogic()``` part could be enclosed in a lambda BaseClient would accepts the lambda its constructor and extending client classes would also accept such a lambda in their constructors you could create a factory that could use reflection (something like ```clazz.newInstance(lambda)```) to create the client given its class and the lambda with the extra logic. If you cannot change BaseClient this solution is toasted and in that case you may need to consider proxy-delegation but you wouldn't have access to the client sub-class methods. – Valentin Ruano Jan 29 '18 at 21:27

2 Answers2

1

This solution is probably no more ideal than what's been discussed in the comments. However, if composition is an acceptable solution in your system, you might consider something like this before resorting to instrumentation:

public class WrapperClient<T extends BaseClient> extends BaseClient {
    T realClient;

    public WrapperClient(T realClient) {
        this.realClient = realClient;
    }

    public void theMethod() {
        realClient.theMethod();
        someAdditionalLogic();
   }
}

Then for the factory, have an abstract layer:

public abstract class WrapperClientFactory<T extends BaseClient> {
    public final WrapperClient<T> create() {
        T realClient = createRealClient();
        return new WrapperClient<T>(realClient);
    }

    protected abstract T createRealClient();
}

Then the factory implementation could look like this

public class FooClientFactory extends WrapperClientFactory<FooClient> {
    protected FooClient createRealClient() {
        return new FooClient();
    }
}

and then consumers would just do this:

BaseClient client = FooClientFactory.create();
client.theMethod();

Here, client would actually be an instance of WrapperClient, which would in turn delegate to FooClient, and not FooClient itself, so it's kind of hacky. But it would allow you to extend logic in arbitrary subclasses at the abstract layer.

If it's not important that consumers have a direct reference to the subclass they are actually using, I would prefer this approach to some of what has been discussed in the comments.

If this approach is mostly okay but the client subclasses need to be able to provide a different interface than the pure BaseClient, you could add another layer and make WrapperClient abstract.

Another downside to this approach is you lose the static methods in the factories. If this is important in your system, you could create a factory of factories that would hide instantiation from the consumer.

1

If Ethan's answer isn't what you were looking for, then the short answer is "no there is nothing in core Java that will let you do what you are trying to do".

That being said it's not impossible, but you will need to use bytecode manipulation libraries. This is how Spring is able to proxy all non-interfaced beans (all injected beans in Spring are actually proxies if you didn't know), it uses CGLIB(source). Based on this other Stackoverflow answer, it might also be possible do proxy non-interface classes using Javasist; the later seems to be rather simple and close to how a "normal" proxy would have looked.

Simon Berthiaume
  • 643
  • 4
  • 11