Refactorizar polimorfismo utilizando Java 8

8

Tengo una base de código antigua que necesito refactorizar con Java 8, por lo que tengo una interfaz que indica si mi sitio actual es compatible con la plataforma.

public interface PlatformSupportHandler {   
  public abstract boolean isPaltformSupported(String platform);
}

y tengo varias clases que lo implementan y cada clase admite una plataforma diferente.

Algunas de las clases de implementación son:

@Component("bsafePlatformSupportHandler")
public class BsafePlatoformSupportHandler implements PlatformSupportHandler {

  String[] supportedPlatform = {"iPad", "Android", "iPhone"};
  Set<String> supportedPlatformSet = new HashSet<>(Arrays.asList(supportedPlatform)); 

  @Override
  public boolean isPaltformSupported(String platform) {

    return supportedPlatformSet.contains(platform);
  }

}

Otra implementación:

@Component("discountPlatformSupportHandler")
public class DiscountPlatoformSupportHandler implements PlatformSupportHandler{

  String[] supportedPlatform = {"Android", "iPhone"};
  Set<String> supportedPlatformSet = new HashSet<>(Arrays.asList(supportedPlatform)); 

  @Override
  public boolean isPaltformSupported(String platform) {

  return supportedPlatformSet.contains(platform);
  }
}

En tiempo de ejecución en mi filtro, obtengo el bean requerido que quiero:

platformSupportHandler = (PlatformSupportHandler) ApplicationContextUtil
  .getBean(subProductType + Constants.PLATFORM_SUPPORT_HANDLER_APPEND);

y llame isPlatformSupportedpara saber si mi sitio actual es compatible con la siguiente plataforma o no.

Soy nuevo en Java 8, ¿hay alguna forma de refactorizar este código sin crear varias clases? Como la interfaz solo contiene un método, ¿puedo usar lambda de alguna manera para refactorizarlo?

Shubham Chopra
fuente
1
Salte a un lado, claro: todo lo que está haciendo es un control de contenido en un conjunto. Inyecte el conjunto como un parámetro constructor.
Andy Turner

Respuestas:

5

Si desea apegarse al diseño actual, podría hacer algo como esto:

public class MyGeneralPurposeSupportHandler implements PlatformSupportHandler {
   private final Set<String> supportedPlatforms;
   public MyGeneralPurposeSupportHandler(Set<String> supportedPlatforms) {
      this.supportedPlatforms = supportedPlatforms;
   } 

   public boolean isPlatformSupported(String platform) {
     return supportedPlatforms.contains(platform);
   }
}

// now in configuration:

@Configuration
class MySpringConfig {

    @Bean
    @Qualifier("discountPlatformSupportHandler") 
    public PlatformSupportHandler discountPlatformSupportHandler() {
       return new MyGeneralPurposeSupportHandler(new HashSefOf({"Android", "iPhone"})); // yeah its not a java syntax, but you get the idea
    }

    @Bean
    @Qualifier("bsafePlatformSupportHandler") 
    public PlatformSupportHandler bsafePlatformSupportHandler() {
       return new MyGeneralPurposeSupportHandler(new HashSefOf({"Android", "iPhone", "iPad"}));
    }   

}

Este método tiene la ventaja de no crear clases por tipo (descuento, bsafe, etc.), por lo que esto responde la pregunta.

Yendo un paso más allá, qué sucede si no se solicitó ningún tipo, actualmente fallará porque el bean no existe en el contexto de la aplicación, no es un enfoque realmente bueno.

Por lo tanto, podría crear un mapa de tipo para el conjunto de plataformas compatibles, mantener el mapa en la configuración o algo y dejar que el resorte haga su magia. Terminarás con algo como esto:

public class SupportHandler {

   private final Map<String, Set<String>> platformTypeToSuportedPlatforms;

   public SupportHandler(Map<String, Set<String>> map) {
       this.platformTypeToSupportedPlatforms = map; 
   }

   public boolean isPaltformSupported(String type) {
        Set<String> supportedPlatforms = platformTypeToSupportedPlatforms.get(type);
        if(supportedPlatforms == null) {
          return false; // or maybe throw an exception, the point is that you don't deal with spring here which is good since spring shouldn't interfere with your business code
        }
        return supportedPlatforms.contains(type);

   }
}

@Configuration 
public class MyConfiguration {

    // Configuration conf is supposed to be your own way to read configurations in the project - so you'll have to implement it somehow
    @Bean
    public SupportHandler supportHandler(Configuration conf) {
      return new SupportHandler(conf.getRequiredMap());
    }
}

Ahora, si sigue este enfoque, agregar nuevos tipos admitidos se vuelve sin código, solo agrega una configuración, con mucho, es el mejor método que puedo ofrecer.

Sin embargo, ambos métodos carecen de las características de Java 8;)

Mark Bramnik
fuente
4

Puede usar lo siguiente en su clase de configuración donde puede crear beans:

@Configuration 
public class AppConfiguration {

    @Bean(name = "discountPlatformSupportHandler")
    public PlatformSupportHandler discountPlatformSupportHandler() {
        String[] supportedPlatforms = {"Android", "iPhone"};
        return getPlatformSupportHandler(supportedPlatforms);
    }

    @Bean(name = "bsafePlatformSupportHandler")
    public PlatformSupportHandler bsafePlatformSupportHandler() {
        String[] supportedPlatforms = {"iPad", "Android", "iPhone"};
        return getPlatformSupportHandler(supportedPlatforms);
    }

    private PlatformSupportHandler getPlatformSupportHandler(String[] supportedPlatforms) {
        return platform -> Arrays.asList(supportedPlatforms).contains(platform);
    }
}

Además, cuando quieras usar el bean, nuevamente es muy fácil:

@Component
class PlatformSupport {

    // map of bean name vs bean, automatically created by Spring for you
    private final Map<String, PlatformSupportHandler> platformSupportHandlers;

    @Autowired // Constructor injection
    public PlatformSupport(Map<String, PlatformSupportHandler> platformSupportHandlers) {
        this.platformSupportHandlers = platformSupportHandlers;
    }

    public void method1(String subProductType) {
        PlatformSupportHandler platformSupportHandler = platformSupportHandlers.get(subProductType + Constants.PLATFORM_SUPPORT_HANDLER_APPEND);
    }
}

rohanagarwal
fuente
3

Como estaba escrito en la respuesta de Mark Bramnik, puede mover esto a la configuración.

Supongamos que estaría en yaml de esa manera:

platforms:
    bsafePlatformSupportHandler: ["iPad", "Android", "iPhone"]
    discountPlatformSupportHandler: ["Android", "iPhone"]

Entonces puedes crear una clase de configuración para leer esto:

@Configuration
@EnableConfigurationProperties
@ConfigurationProperties
public class Config {

    private Map<String, List<String>> platforms = new HashMap<String, List<String>>();

    // getters and setters

Puede crear un controlador con código de verificación. O colóquelo en su filtro como a continuación:

@Autowired
private Config config;

...

public boolean isPlatformSupported(String subProductType, String platform) {
    String key = subProductType + Constants.PLATFORM_SUPPORT_HANDLER_APPEND;
    return config.getPlatforms()
        .getOrDefault(key, Collections.emptyList())
        .contains(platform);
}
lczapski
fuente