1
\$\begingroup\$

I'm working on building a library to help you bootstrap the development of a Java API. The premise is, you can have your controllers implement interfaces like IGetController<SomeEntity> or IListController<SomeEntity>. These interfaces have default methods to implement their endpoint, and use a service factory to get the appropriate service to do the bulk of the work. These services are also interfaces, and have default methods which get a repository (by calling getRepository on the IBaseService they extend). There's a lot of "magic" going on here to inspect the genericInterfaces on the class, and then getting the type arguments to determine what entity we're working with.

The problem I'm having is consolidating my factories. Take a look at these two factory classes:

GetServiceFactory:

package apibuilder.service.factory;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

import apibuilder.entity.BaseEntity;
import apibuilder.service.IGetService;

public class GetServiceFactory {
    private static final Map<Type, IGetService<? extends BaseEntity>>
            registrationMap = new HashMap<>();

    public static <T extends BaseEntity> IGetService<T> getService(final Type entityType) {
        if (registrationMap.containsKey(entityType)) {
            //noinspection unchecked
            return (IGetService<T>) registrationMap.get(entityType);
        }
        return null;
    }

    public static <T extends BaseEntity> void registerServices(final IGetService<T>[] services) {
        for (IGetService<T> service : services) {
            Type entityType = ((ParameterizedType) service.getClass().getGenericInterfaces()[0])
                    .getActualTypeArguments()[0];
            registrationMap.put(entityType, service);
        }
    }
}

ListServiceFactory:

package apibuilder.service.factory;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

import apibuilder.entity.BaseEntity;
import apibuilder.service.IListService;

public class ListServiceFactory {
    private static final Map<Type, IListService<? extends BaseEntity>>
            registrationMap = new HashMap<>();

    public static <T extends BaseEntity> IListService<T> getService(final Type entityType) {
        if (registrationMap.containsKey(entityType)) {
            //noinspection unchecked
            return (IListService<T>) registrationMap.get(entityType);
        }
        return null;
    }

    public static <T extends BaseEntity> void registerServices(final IListService<T>[] services) {
        for (IListService<T> service : services) {
            Type entityType = ((ParameterizedType) service.getClass().getGenericInterfaces()[0])
                    .getActualTypeArguments()[0];
            registrationMap.put(entityType, service);
        }
    }
}

As you can see, they're nearly identical. They create a map to get the proper service per entity, and then have methods to build the map, and get a service from the map. I feel like these can be consolidated into some sort of BaseServiceFactory, but I'm having a hard time actually pulling it off.

\$\endgroup\$
  • \$\begingroup\$ I actually ended up abandoning this project once I discovered spring-data-rest, which accomplishes what I was trying to accomplish here. \$\endgroup\$ – Ryan Caskey Aug 28 '17 at 2:22
2
\$\begingroup\$

Problem is that you prefer static methods. This way you effectively throw away benefits of polymorphism.

Also, when ever you find duplicated code you should carefully think if it might belong into a class of its own.

If you could open your mint to an OO approach you could move common behavior into a (non static) utility class and inject that common behavior into the concrete Factories. Or you go the usual OO way to create an abstract factory having the common code and have the concrete factories inheriting from it.


but why do static methods throw away polymorphism benefits? – Thufir

Static method imply early binding. All accessors to a method are known at compile time of the method implementation. Also all implementors of a static method are known at compile time of the accessors. You cannot provide a new behavior of a method unless you compile the accessors needing this new behavior.

class A {
  static void method(){
     System.out.println("static method in A");
  }
}


class B extends A {
  static void method(){
     System.out.println("static method B");
  }
}

class  C {
  static void main(String[] args){
     A.method(); // outputs "static method in A"
     B.method(); // outputs "static method in B"
     A[] a = {new A(), new B()};
     a[0].method();  // outputs "static method in A"
     a[1].method();  // outputs "static method in A" although instance is f type `B` 
  }
}

And, is polymorphism really so beneficial? – Thufir

Polymorphism is no value as such.

But if you doubt the value of polymorphism in general you might rather use a programing language that does not bother you with such gratuitous concepts...

\$\endgroup\$
  • \$\begingroup\$ but why do static methods throw away polymorphism benefits? (And, is polymorphism really so beneficial?) \$\endgroup\$ – Thufir Dec 23 '18 at 18:00
0
\$\begingroup\$

Actually that what you have implemented is a Registry not a Factory, so probably you should remove Factory from the class name and use registry.

You could do the following to generalize the class (I am reusing your code):

package apibuilder.service.factory;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

import apibuilder.service.Service;

public class ServiceRegistry {
    private static final Map<Type, Service> registrationMap = new HashMap<>();

    public static Service getService(final Type entityType) {
        if (registrationMap.containsKey(entityType)) {
            return registrationMap.get(entityType);
        }

        return null;
    }

    public static void registerServices(final Service[] services) {
        for (Service service : services) {
            Type entityType = ((ParameterizedType) service.getClass().getGenericInterfaces()[0])
                    .getActualTypeArguments()[0];
            registrationMap.put(entityType, service);
        }
    }
}

Then you could define the following interface:

public interface Service {

}

It will serve as a marker interface and just declare all of your other interfaces as follow (IGetService for example):

public interface IGetService<T extends BaseEntity> extends Service {

   ... whatever methods you have ...

}
\$\endgroup\$
  • \$\begingroup\$ Ok, won't i be running into issues where my ServiceRegistry's registrationMap is trying to put multiple services in for the same entity type (since entity X could have a create service and an update service)? \$\endgroup\$ – Ryan Caskey Aug 22 '17 at 2:55
  • \$\begingroup\$ Yes, unfortunately you will encounter it. What I think you should do is to create a method which will allow registration only of a single service for a given key and as a key you should create your own key which should be a composition of entity and service type and then retrieve the service by this key. If you don't know how to implement proper map key, just search for it \$\endgroup\$ – RaISug Aug 22 '17 at 13:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.