如果声明,策略模式太多了

时间:2016-10-12 19:58:35

标签: java design-patterns architecture

用户输入代码,该代码的类型由正则表达式确定。有许多不同类型的代码,例如EAN,ISBN,ISSN等。检测到类型后,必须为代码创建自定义查询。我认为为类型创建策略可能是一个好主意,但随着时间的推移它会感觉不对。

public interface SearchQueryStrategie {

    SearchQuery createSearchQuery(String code);
}

-

public class IssnSearchQueryStrategie implements SearchQueryStrategie {

    @Override
    public SearchQuery createSearchQuery(final String code) {
        // Create search query for issn number
    }
}

-

public class IsbnSearchQueryStrategie implements SearchQueryStrategie {

    @Override
    public SearchQuery createSearchQuery(final String code) {
        // Create search query for ISBN number
    }
}

-

public class EanShortNumberSearchQueryStrategie implements SearchQueryStrategie {

    @Override
    public SearchQuery createSearchQuery(final String code) {
        // Create search query for ean short number
    }
}

-

public class TestApplication {

    public static void main(final String... args) {
        final String code = "1144875X";

        SearchQueryStrategie searchQueryStrategie = null;
        if (isIssn(code)) {
            searchQueryStrategie = new IssnSearchQueryStrategie();
        } else if (isIsbn(code)) {
            searchQueryStrategie = new IsbnSearchQueryStrategie();
        } else if (isEan(code)) {
            searchQueryStrategie = new EanShortNumberSearchQueryStrategie();
        }

        if (searchQueryStrategie != null) {
            performSearch(searchQueryStrategie.createSearchQuery(code));
        }
    }

    private SearchResult performSearch(final SearchQuery searchQuery) {
        // perform search
    }

    // ...
}

我不得不说还有更多策略。我该如何将代码发送到正确的策略? 我的第二种方法是在每个策略中放置一个布尔方法,以确定代码是否适合该策略。

public class TestApplication {

    final SearchQueryStrategie[] searchQueryStrategies = {new IssnSearchQueryStrategie(), new IsbnSearchQueryStrategie(),
            new EanShortNumberSearchQueryStrategie()};

    public static void main(final String... args) {
        final String code = "1144875X";

        for (final SearchQueryStrategie searchQueryStrategie : searchQueryStrategie) {
            if (searchQueryStrategie.isRightCode(code)) {
                searchQueryStrategie.createSearchQuery(code);
                break;
            }
        }
    }

    private SearchResult performSearch(final SearchQuery searchQuery) {
        // perform search
    }

    // ...
}

您如何解决这个问题?战略模式是否适合我的目的?

5 个答案:

答案 0 :(得分:2)

如果您使用的是Java 8,并且您可以从功能特性中获益,我认为一个Enum就足够了。

您可以通过使用将返回需要执行的查询的Function映射每种类型的代码来避免使用if / else语句:

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;

public enum CodeType
{
    EAN("1|2|3"),
    ISBN("4|5|6"),
    ISSN("7|8|9");

    String regex;
    Pattern pattern;
    CodeType(String regex)
    {
        this.regex = regex;
        this.pattern = Pattern.compile(regex);
    }

    private static Map<CodeType, Function<String, String>> QUERIES =
            new HashMap<>();
    static 
    {
        QUERIES.put(EAN, (String code) ->  String.format("Select %s from EAN", code));
        QUERIES.put(ISBN, (String code) -> String.format("Select %s from ISBB", code));
        QUERIES.put(ISSN, (String code) -> String.format("Select %s from ISSN", code));
    }

    private static CodeType evalType(String code)
    {
        for(CodeType codeType : CodeType.values())
        {
            if (codeType.pattern.matcher(code).matches())
                return codeType;
        }
        // TODO DON'T FORGET ABOUT THIS NULL HERE
        return null;
    }

    public static String getSelect(String code)
    {
        Function<String, String> function = QUERIES.get(evalType(code));
        return function.apply(code);
    }
}

在主要内容中,您可以测试您的查询:

public class Main
{
    public static void main(String... args)
    {
        System.out.println(CodeType.getSelect("1"));
        // System.out: Select 1 from EAN
        System.out.println(CodeType.getSelect("4"));
        // System.out: Select 4 from ISBB
        System.out.println(CodeType.getSelect("9"));
        // System.out: Select 9 from ISSN
    }
}

我通常倾向于保持代码尽可能紧凑。

有些人不喜欢枚举,所以我相信你可以使用普通的课程。

您可以进一步设计获得QUERIES(选择)的方式,因此您可以在那里拥有一个Runnable而不是String模板。

如果您不想使用Java 8的功能方面,可以使用与每种类型的代码相关联的Strategy对象:

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;

public enum CodeType2
{
    EAN("1|2|3", new StrategyEAN()),
    ISBN("4|5|6", new StrategyISBN()),
    ISSN("7|8|9", new StrategyISSN());

    String regex;
    Pattern pattern;
    Strategy strategy;

    CodeType2(String regex, Strategy strategy)
    {
        this.regex = regex;
        this.pattern = Pattern.compile(regex);
        this.strategy = strategy;
    }

    private static CodeType2 evalType(String code)
    {
        for(CodeType2 codeType2 : CodeType2.values())
        {
            if (codeType2.pattern.matcher(code).matches())
                return codeType2;
        }
        // TODO DON'T FORGET ABOUT THIS NULL HERE
        return null;
    }

    public static void doQuery(String code)
    {
        evalType(code).strategy.doQuery(code);
    }
}

interface Strategy { void doQuery(String code); }
class StrategyEAN implements Strategy {
    @Override
    public void doQuery(String code)
    {
        System.out.println("EAN-" + code);
    }
}
class StrategyISBN implements Strategy
{

    @Override
    public void doQuery(String code)
    {
        System.out.println("ISBN-" + code);
    }
}
class StrategyISSN implements Strategy
{

    @Override
    public void doQuery(String code)
    {
        System.out.println("ISSN-" + code);
    }
}

主要方法如下:

public class Main
{
    public static void main(String... args)
    {
        CodeType2.doQuery("1");
        CodeType2.doQuery("4");
        CodeType2.doQuery("9");
    }
}

答案 1 :(得分:1)

所以,策略模式确实是正确的选择,但strategy本身是不够的。您有几种选择:

  1. 使用简单的if / else或switch Factory。它很丑陋,容易因新策略而扩展,但实施简单快捷。

  2. 使用注册表。在应用程序初始化阶段,您可以使用正确的代码在registry每个SearchQueryStratgeyFactory中注册。例如,如果您使用简单的Map,您可以这样做: strategyRegistry.put("isbn", new IsbnSearchStrategyFactory()); strategyRegistry.put("ean", new EanSearchStrategyFactory()); .... and so on 然后,当您需要使用代码ID从地图中get()策略工厂时,您需要获得正确的策略。如果您有很多策略,这种方法会更好,但在应用程序启动期间需要一个附加的初始化步骤。

  3. 使用服务定位器。 ServiceLocator是一种支持动态查找实现的模式。 Java附带了ServiceLocator模式的实现 - &gt;臭名昭着的ServiceLoader班。这是我最喜欢的方法,因为它允许消费者和实现的完全解耦。使用服务定位器,您可以轻松添加新策略,而无需修改现有代码。我不会解释如何使用ServiceLoader - 网上有大量信息。我只想提一下,使用服务定位器,你需要实现“可以处理这样的代码吗?”每个战略工厂的逻辑。例如,如果工厂无法为“isbn”创建策略,则返回null并尝试使用下一个工厂。

  4. 另请注意,在所有情况下,您都与制作策略实施的工厂合作。

    PS:strategy而非strategie:)

答案 2 :(得分:0)

我建议使用Factory模式。它更好地描述和处理您的场景。

Factory Pattern

答案 3 :(得分:0)

您可以按以下方式设计(使用工厂DP和多态的概念):

  1. Code作为界面。

  2. ISSNCodeISBNCodeEANCode作为具体类 实现Code接口,让单arg构造函数将文本作为String。

  3. Code有方法getInstanceOfCodeType(String text),它返回Code子类的实例(通过检查传递给它的text的类型来决定)。我们假设返回的值为code

  4. 班级SearchQueryStrategieFactory getSearchQueryStrategie(code)方法。它使用步骤3中返回的值,并生成不同的值 使用SearchQueryStrategie运算符基于code类型的new 子类的实例,然后返回相同的内容。

  5. 因此,您需要从任何地方调用两个方法getInstanceOfCodeType(text)getSearchQueryStrategie(code)

    代替 隐式 在main中实现工厂,将整个 factory 代码分开,使其易于维护和扩展

答案 4 :(得分:0)

您的方法不是战略模式。策略模式是通过将备用Context对象传递给它来自定义对象(Strategy在此模式方面)的行为。通过这种方式,我们不需要修改Context类的源代码,但仍然可以自定义从其实例化的对象的行为。

您的问题与您有一个请求(您的code)的责任链(CoR)模式有些相关,需要弄清楚预定义列表中的哪个SearchQueryStrategie应该处理请求。

你提到的第二种方法 - 使用数组 - 很好。但是,为了使它在生产代码中可用,您必须有另一个对象 - 比方说Manager - 管理数组并负责查找每个请求的相关元素。因此,您的客户端代码必须依赖于两个对象:Manager和结果SearchQueryStrategie。正如您所看到的,Manager类的源代码往往会经常更改,因为SearchQueryStrategie的新实现可能会出现。这可能会让您的客户感到恼火。

这就是CoR Pattern使用链表机制而不是数组的原因。每个SearchQueryStrategie对象A将保存对下一个SearchQueryStrategie B的引用。如果A无法处理请求,它将委托给B(它甚至可以在委托之前修饰请求)。当然,某些地方仍然必须知道所有类型的策略并创建SearchQueryStrategie的链接列表,但是您的客户端将仅依赖于SearchQueryStrategie对象(列表中的头部)。

以下是代码示例:

class SearchQueryConsumer {
    public void consume(SearchQuery sq) {
        // ...
    }
}

abstract class SearchQueryHandler {
    protected SearchQueryHandler next = null;
    public void setNext(SearchQueryHandler next) { this.next = next; }
    public abstract void handle(String code, SearchQueryConsumer consumer); 
}

class IssnSearchQueryHandler extends SearchQueryHandler {
    @Override
    public void handle(String code, SearchQueryConsumer consumer) {
        if (issn(code)) {
            consumer.consume(/* create a SearchQuery */);
        } else if (next != null) {
            next.handle(code, consumer);
        }
    }

    private boolean issn(String code) { ... }
}