Refactor-Polymorphismus mit Java 8

8

Ich habe eine alte Codebasis, die ich mit Java 8 umgestalten muss, daher habe ich eine Schnittstelle, die angibt, ob meine aktuelle Site die Plattform unterstützt.

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

und ich habe mehrere Klassen, die es implementieren, und jede Klasse unterstützt eine andere Plattform.

Einige der implementierenden Klassen sind:

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

}

Eine weitere Implementierung:

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

Zur Laufzeit in meinem Filter erhalte ich die gewünschte Bean, die ich möchte:

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

und rufen Sie isPlatformSupportedan, um zu erfahren, ob meine aktuelle Site die folgende Plattform unterstützt oder nicht.

Ich bin neu in Java 8, gibt es also eine Möglichkeit, diesen Code umzugestalten, ohne mehrere Klassen zu erstellen? Kann ich Lambda verwenden, um es umzugestalten, da die Schnittstelle nur eine Methode enthält?

Shubham Chopra
quelle
1
Frühling beiseite, klar: Alles, was Sie tun, ist eine Überprüfung eines Sets. Injizieren Sie die Menge als Konstruktorparameter.
Andy Turner

Antworten:

5

Wenn Sie sich an das aktuelle Design halten möchten, können Sie Folgendes tun:

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

}

Diese Methode hat den Vorteil, dass keine Klasse pro Typ erstellt wird (Rabatt, Sicherheit usw.), sodass die Frage beantwortet wird.

Wenn Sie noch einen Schritt weiter gehen, was passiert, wenn kein Typ angefordert wurde, schlägt dies derzeit fehl, da die Bean im Anwendungskontext nicht vorhanden ist - kein wirklich guter Ansatz.

Sie können also eine Karte des Typs für die unterstützten Plattformen erstellen, die Karte in der Konfiguration beibehalten oder etwas, das der Frühling seine Magie entfalten lässt. Sie werden am Ende so etwas haben:

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

Wenn Sie nun diesem Ansatz folgen und neue unterstützte Typen hinzufügen, wird das Codieren überhaupt nicht mehr möglich. Sie fügen nur eine Konfiguration hinzu. Dies ist bei weitem die beste Methode, die ich anbieten kann.

Bei beiden Methoden fehlen jedoch die Java 8-Funktionen;)

Mark Bramnik
quelle
4

In Ihrer Konfigurationsklasse können Sie Folgendes verwenden, um Beans zu erstellen:

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

Auch wenn Sie die Bohne verwenden möchten, ist es wieder sehr einfach:

@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
quelle
3

Wie in Mark Bramniks Antwort geschrieben, können Sie dies in die Konfiguration verschieben.

Angenommen, es wäre auf diese Weise in Yaml:

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

Dann können Sie eine Konfigurationsklasse erstellen, um Folgendes zu lesen:

@Configuration
@EnableConfigurationProperties
@ConfigurationProperties
public class Config {

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

    // getters and setters

Sie können dann einen Handler mit Prüfcode erstellen. Oder legen Sie es wie folgt in Ihren Filter:

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