Implementazione del modello di strategia tramite la configurazione

1

Nel mio attuale problema, alcuni problemi sembrano prestarsi abbastanza bene al modello strategico. Ho un processo comune di alto livello - diciamo che si tratta di un processo di vendita - e indipendentemente dal canale di vendita che utilizzo, sellProduct deve accadere ... È solo un po 'diverso da canale a canale.

Quindi diciamo che voglio implementare un modello di strategia per canale.

Quello che mi piacerebbe davvero sapere è se esiste un modo solido e comune che le persone hanno visto implementato. In genere ciò che ho visto in passato è una sorta di ricerca, forse da un file DB o di configurazione, basato sul tipo di canale, che crea un'istanza di una particolare classe e quindi esegue SalesStrategy.execute() su tale implementazione. Detto questo, non sono sicuro che la memorizzazione di nomi di classi pienamente qualificati in un file DB o di configurazione sia davvero la migliore che possiamo fare qui.

Qualcun altro ha altri pensieri? Sto usando Java, quindi sono interessato a che tipo di comuni soluzioni basate sulla configurazione le persone hanno usato attraverso qualcosa come Guice o Spring.

    
posta f1dave 28.02.2018 - 09:01
fonte

2 risposte

2

No, i tuoi file di database / configurazione non dovrebbero menzionare i nomi delle classi.

Invece, dovresti includere il canale nel tuo modello, poiché è una parte rilevante del tuo dominio. Questo non deve essere elaborato - una semplice enumerazione può essere sufficiente. Se la tua strategia dipende da molti parametri, puoi raggrupparli in SalesStrategyConfiguration .

L'effettiva strategia viene quindi istanziata da una fabbrica, che valuta i parametri forniti e crea un'istanza della strategia corretta. Questa decisione si basa esclusivamente su concetti rilevanti per il dominio, quindi non è necessario alcun quadro specifico.

Dove un framework può rivelarsi utile è la traduzione dei dati di configurazione nel modello (cioè creando SalesStrategyConfiguration ). Ma questo è un compito molto generale, in nessun modo legato all'istanziazione della strategia.

    
risposta data 28.02.2018 - 12:25
fonte
0

Ho avuto questo codice ispirato al link come punto di ingresso alla mia libreria di utilità , utilizza i seguenti frammenti di codice a tuo piacimento:

Strategy.java

package ...

@Documented
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Strategy {

    Class<?> type();

    String[] profiles() default {};
}

StrategyFactory.java

package ...

public class StrategyFactory {

    private static final Logger LOG = Logger.getLogger( StrategyFactory.class );

    private Map<Class<?>, Strategy> strategiesCache = new HashMap<Class<?>, Strategy>();

    private String[] packages;

    public void init() {
        if (this.packages != null) {
            Set<Class<?>> annotatedClasses = new HashSet<Class<?>>();
            for (String pack : this.packages) {
                Reflections reflections = new Reflections( pack );
                annotatedClasses.addAll( reflections.getTypesAnnotatedWith( Strategy.class ) );
            }
            this.sanityCheck( annotatedClasses );
        }
    }

    public <T> T getStrategy(Class<T> strategyClass) {
        return this.getStrategy( strategyClass, null );
    }

    @SuppressWarnings("unchecked")
    public <T> T getStrategy(Class<T> strategyClass, String currentProfile) {
        Class<T> clazz = (Class<T>) this.findStrategyMatchingProfile( strategyClass, currentProfile );
        if (clazz == null) {
            throw new StrategyNotFoundException( String.format( "No strategies found of type '%s', are the strategies marked with @Strategy?", strategyClass.getName() ) );
        }
        try {
            return (T) clazz.newInstance();
        } catch (Exception e) {
            throw ExceptionUtils.rethrowAs( e, StrategyException.class );
        }
    }

    /**
     * Checks to make sure there is only one strategy of each type(Interface) annotated for each profile Will throw an exception on startup if multiple strategies are mapped to the same profile.
     * @param annotatedClasses a list of classes
     */
    private void sanityCheck(Set<Class<?>> annotatedClasses) {
        Set<String> usedStrategies = new HashSet<String>();
        for (Class<?> annotatedClass : annotatedClasses) {
            Strategy strategyAnnotation = AnnotationUtils.findAnnotation( annotatedClass, Strategy.class );
            if (!strategyAnnotation.type().isAssignableFrom( annotatedClass )) {
                throw new StrategyProfileViolationException( String.format( "'%s' should be assignable from '%s'", strategyAnnotation.type(), annotatedClass ) );
            }
            this.strategiesCache.put( annotatedClass, strategyAnnotation );

            if (this.isDefault( strategyAnnotation )) {
                this.ifNotExistAdd( strategyAnnotation.type(), "default", usedStrategies );
            } else {
                for (String profile : strategyAnnotation.profiles()) {
                    this.ifNotExistAdd( strategyAnnotation.type(), profile, usedStrategies );
                }
            }
        }
    }

    private void ifNotExistAdd(Class<?> type, String profile, Set<String> usedStrategies) {
        String key = this.createKey( type, profile );
        if (usedStrategies.contains( key )) {
            throw new StrategyProfileViolationException( String.format( "There can only be a single strategy for each type, found multiple for type '%s' and profile '%s'", type, profile ) );
        }
        usedStrategies.add( key );
    }

    private String createKey(Class<?> type, String profile) {
        return String.format( "%s_%s", type, profile ).toLowerCase();
    }

    private boolean isDefault(Strategy strategyAnnotation) {
        return (strategyAnnotation.profiles().length == 0);
    }

    private Class<?> findStrategyMatchingProfile(Class<?> strategyClass, String currentProfile) {
        for (Map.Entry<Class<?>, Strategy> strategyCacheEntry : this.strategiesCache.entrySet()) {
            Strategy strategyCacheEntryValue = strategyCacheEntry.getValue();
            if (strategyCacheEntryValue.type().equals( strategyClass )) {
                if (currentProfile != null) {
                    for (String profile : strategyCacheEntryValue.profiles()) {
                        if (currentProfile.equals( profile )) {
                            Class<?> result = strategyCacheEntry.getKey();
                            if (LOG.isDebugEnabled()) {
                                LOG.debug( String.format( "Found strategy [strategy=%s, profile=%s, strategyImpl=%s]", strategyClass, currentProfile, result ) );
                            }
                            return result;
                        }
                    }
                } else if (this.isDefault( strategyCacheEntryValue )) {
                    Class<?> defaultClass = strategyCacheEntry.getKey();
                    if (LOG.isDebugEnabled()) {
                        LOG.debug( String.format( "Found default strategy [strategy=%s, profile=%s, strategyImpl=%s]", strategyClass, currentProfile, defaultClass ) );
                    }
                    return defaultClass;
                }
            }
        }
        return null;
    }

    public void setPackages(String[] packages) {
        this.packages = packages;
    }
}

StrategyException.java

package ...

public class StrategyException extends RuntimeException {
...
}

StrategyNotFoundException.java

package ...

public class StrategyNotFoundException extends StrategyException {
...
}

StrategyProfileViolationException.java

package ...

public class StrategyProfileViolationException extends StrategyException {
...
}

Utilizzo senza Spring :

NavigationStrategy.java

package com.asimio.core.test.strategy.strategies.navigation;

public interface NavigationStrategy {

    public String naviateTo();
}

FreeNavigationStrategy.java

package com.asimio.core.test.strategy.strategies.navigation;

@Strategy(type = NavigationStrategy.class)
public class FreeNavigationStrategy implements NavigationStrategy {

    public String naviateTo() {
        return "free";
    }
}

LimitedPremiumNavigationStrategy.java

package com.asimio.core.test.strategy.strategies.navigation;

@Strategy(type = NavigationStrategy.class, profiles = { "limited", "premium" })
public class LimitedPremiumNavigationStrategy implements NavigationStrategy {

    public String naviateTo() {
        return "limited+premium";
    }
}

Poi

...
StrategyFactory factory = new StrategyFactory();
factory.setPackages( new String[] { "com.asimio.core.test.strategy.strategies.navigation" } );
this.factory.init();

NavigationStrategy ns = this.factory.getStrategy( NavigationStrategy.class );
String result = ns.naviateTo();
Assert.assertThat( "free", Matchers.is( result ) );
...
Or
...
String result = factory.getStrategy( NavigationStrategy.class, "limited" ).naviateTo();
Assert.assertThat( "limited+premium", Matchers.is( result ) );
...
    
risposta data 02.03.2018 - 03:49
fonte

Leggi altre domande sui tag