Refatorar o polimorfismo usando Java 8

8

Como tenho uma base de código antiga que preciso refatorar usando o Java 8, tenho uma interface que informa se meu site atual suporta a plataforma.

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

e eu tenho várias classes implementando-o e cada classe suporta uma plataforma diferente.

Algumas das classes de implementação são:

@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);
  }

}

Outra implementação:

@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);
  }
}

Em tempo de execução no meu filtro, recebo o bean necessário que desejo:

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

e ligue isPlatformSupportedpara saber se meu site atual é compatível com a plataforma a seguir ou não.

Eu sou novo no Java 8, então existe alguma maneira de refatorar esse código sem criar várias classes? Como a interface contém apenas um método, posso de alguma forma usar o lambda para refatorá-lo?

Shubham Chopra
fonte
1
Deixe de lado, com certeza: tudo o que você está fazendo é uma verificação de um conjunto. Injete o conjunto como um parâmetro construtor.
Andy Turner

Respostas:

5

Se você deseja manter o design atual, pode fazer algo assim:

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 tem a vantagem de não criar classe por tipo (desconto, bsafe, etc), portanto, isso responde à pergunta.

Indo um passo adiante, o que acontece se não houver um tipo solicitado, atualmente ele falhará porque o bean não existe no contexto do aplicativo - não é uma abordagem realmente boa.

Assim, você pode criar um mapa do tipo para o conjunto de plataformas suportadas, manter o mapa na configuração ou algo e deixar a primavera fazer sua mágica. Você vai acabar com algo assim:

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());
    }
}

Agora, se você seguir essa abordagem, a adição de novos tipos suportados ficará sem código, você só adiciona uma configuração, de longe o melhor método que posso oferecer.

Porém, ambos os métodos não possuem os recursos do java 8;)

Mark Bramnik
fonte
4

Você pode usar o seguinte em sua classe de configuração, onde você pode criar 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);
    }
}

Além disso, quando você deseja usar o bean, é novamente muito 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
fonte
3

Como foi escrito na resposta de Mark Bramnik, você pode mover isso para a configuração.

Suponha que seria no yaml dessa maneira:

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

Então você pode criar uma classe de configuração para ler isto:

@Configuration
@EnableConfigurationProperties
@ConfigurationProperties
public class Config {

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

    // getters and setters

Você pode criar um manipulador com o código de verificação. Ou coloque-o no seu filtro como abaixo:

@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
fonte