【问题标题】:replace simple factory using polymorphism使用多态性替换简单工厂
【发布时间】:2015-06-05 09:16:22
【问题描述】:

我正在尝试替换简单的工厂 StatsCreatorFactory.java 类,以删除臭气熏天的多次使用 switch case 语句。这是我的情况:

StatsServlet.java

public class StatsServlet extends HttpServlet{ 

    private static final long serialVersionUID = 1L;

    protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        StatsContext context = new StatsContext(request,response);
        **IStatsCreator creator = StatsCreatorFactory.getCreator(context);**
        IChart chart = creator.createChart();
        String jsonChart = creator.chartToJson(chart);
        creator.sendResponse(jsonChart);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response);
    }

}

IStatsCreator.java

public interface IStatsCreator {

    public IChart createChart() throws IOException;
    public  IDetailsTable createDetailsTable(String itemSelected);

    public String chartToJson(IChart chart);
    public String tableToJson(IDetailsTable table);

    public void sendResponse(String resp) throws IOException;


    public List<File> findFiles() throws IOException, ParseException;
    public List<LogLine> parseFiles(List<File> files) throws IOException;
    public IFileIntervalDateDetector getFileDetector() throws IOException;

    public TargetChartOperation getTargetOperation();
    public IChart getChart();
    public IDetailsTable getDetailsTable();
}

AbstractStatsCreator

public abstract class AbstractStatsCreator implements IStatsCreator{

    protected StatsContext context;

    public AbstractStatsCreator(StatsContext context) {
        this.context = context;
    }

    protected abstract ILogParser getParser();
    protected abstract IStatsHelper getHelper();

    @Override
    public IFileIntervalDateDetector getFileDetector() throws IOException {
        IFileIntervalDateDetector fileDetector = new FileDetector();
        fileDetector.addPattern(new FileNamePattern(TypeSubjectEnum.valueOf(context.getSubject().toUpperCase()).getFilePattern()));
        fileDetector.addPattern(new FileNamePattern(context.getInstance()));
        return fileDetector;
    }

    @Override
    public final List<File> findFiles() throws IOException, ParseException{
        if(context.getDateStart().equalsIgnoreCase(StringUtils.EMPTY) && context.getDateEnd().equalsIgnoreCase(StringUtils.EMPTY)){
            return getFileDetector().findDailyFiles();
        }

        Date startDate = new SimpleDateFormat("ddMMyyyy").parse(context.getDateStart());
        Date stopDate = new SimpleDateFormat("ddMMyyyy").parse(context.getDateEnd());
        Date currentDate = new Date(System.currentTimeMillis());

        if(DateUtils.isSameDay(startDate, stopDate) && DateUtils.isSameDay(startDate, currentDate)){
            return getFileDetector().findDailyFiles();
        }

        return getFileDetector().findFilesByInterval(context.getDateStart(), context.getDateEnd()); 
    }

    @Override
    public final List<LogLine> parseFiles(List<File> files) throws IOException{
        return getParser().parseLogFiles(files);
    }

    @Override
    public IChart createChart() throws IOException{

        if(context.needUpdate()){
            List<File> files = null;
            try {
                files = findFiles();
            } catch (ParseException e) {
                files=Lists.newArrayList();
            }
            List<LogLine> logLines = parseFiles(files);
            context.setLogLines(logLines);
            context.updateContext(); 
        }

        IChart chart = getChart().create();
        return chart;
    }

    @Override
    public IDetailsTable createDetailsTable(String itemSelected) {
        IDetailsTable table = getDetailsTable().create(itemSelected);
        return table;
    }

    @Override
    public String chartToJson(IChart chart) {
        StringBuilder json = new StringBuilder(JsonTransformer.renderChart(chart));
        return json.toString();
    }

    @Override
    public String tableToJson(IDetailsTable table) {
        StringBuilder json = new StringBuilder(JsonTransformer.renderDetailsTable(table));
        return json.toString();
    }

    @Override
    public void sendResponse(String resp) throws IOException {
        context.getResponse().setContentType("application/json");
        PrintWriter out = context.getResponse().getWriter();
        out.write(resp.toString());
        out.flush();
    }

}

StatsCreatorFactory.java

public class StatsCreatorFactory {

    public static IStatsCreator getCreator(StatsContext context){

        if(context == null){
            throw new IllegalArgumentException("Context nullo");
        }

        IStatsCreator creator=null;

        switch (context.getOperation()) {
        case "validate":
            creator = new ValidateStatsCreator(context);
            break;
        case "extract":
            creator = new ExtractStatsCreator(context);
            break;
        case "transform":
            creator = new TransformStatsCreator(context);
            break;
        case "view":
            creator = new ViewStatsCreator(context);
            break;  
        default:
            creator = new GeneralStatsCreator(context);
            break;
        }

        return creator;
    }
}

我会尝试找到一种方法来实例化 ICreator 类,避免使用简单的工厂类,我可以使用任何重构或设计模式吗? 阅读 Martin Fawler 的书,我想知道我是否可以使用多态,但我找不到任何方法在我的代码中复制它。

【问题讨论】:

  • 正在使用多态性。这对我来说看起来很干净(除了带有 I 前缀的接口命名)。您可以用枚举替换您的操作字符串,并让每个枚举实例创建 StatsCreator。
  • 我同意你的看法,我会应用这些改进。实际上,我的代码和我的 Simple 工厂似乎很弱并且无法修改,如果我要添加另一种类型的创建者,我会一遍又一遍地修改该方法。
  • 这就是工厂的重点。但其余代码将是相同的。在某些时候,如果您想要一些新功能,您需要添加对代码的更改。只要它仅限于工厂(和新的创建者类),就可以了

标签: java design-patterns refactoring factory-pattern


【解决方案1】:

我会首先尝试修改 IStatsCreator 和 AbstractStatsCreator 以获得无状态 bean。

在您的示例中,您只需摆脱在 AbstractStatsCreator 中定义为类变量的 StatsContext 上下文。所以上下文不应该绑定到类实例。当需要上下文的创建者调用方法时,它应该从外部传入。为此,您可以重构您的 IStatsCreator 并将上下文添加到所有需要它的方法中。

例如:

public IChart createChart() 抛出 IOException;

新:

public IChart createChart(StatsContext context) 抛出 IOException;

等等。 之后,您不必为每个上下文调用创建 AbstractStatsCreator 实现的新实例。您只需要每种类型都有一个实例。这种类型的实例可以在 StatsCreatorFactory 中映射,并在需要时获取。我还建议摆脱静态方法。使 StatsCreatorFactory 成为一个真正的 bean,它更易于管理,也更易于模拟测试。:

public class StatsCreatorFactory {

    private Map<String, IStatsCreator> statsCreators = new HashMap<String, IStatsCreator>();

    public void registerStatsCreator( String type, IStatsCreator creator ) {
        statsCreators.put( type, creator );
    }

    public IStatsCreator getCreator( String type ){

        IStatsCreator creator= statsCreators.get( type );

        if(creator == null){
            throw new IllegalArgumentException("no creator registered for type : " + type);
        }

        return creator;
    }

}

最后,StatsCreatorFactory 更像是提供者而不是工厂。也许您也可以将其重命名为 StatsCreatorProvider。

public class StatsServlet extneds HttpServlet{

private static final long serialVersionUID = 1L;

private StatsCreatorProvider statsCreatorProvider;

public void init() {
    statsCreatorProvider = new StatsCreatorProvider();
    statsCreatorProvider.registerStatsCreator( "validate", new ValidateStatsCreator() );
    statsCreatorProvider.registerStatsCreator( "extract" new ExtractStatsCreator() );
    ...
}

protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    StatsContext context = new StatsContext(request,response);
    IStatsCreator creator = statsCreatorProvider.getCreator( context.getOperation() );
    IChart chart = creator.createChart( context );
    String jsonChart = creator.chartToJson(chart);
    creator.sendResponse(jsonChart);
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    processRequest(request, response);
}

}

【讨论】:

  • 如果我在我的 servlet 中很好地理解了您的 StatsCreatorProvider,我将不得不在我想稍后使用的所有创建者之前注册,然后调用传递 StatsContext 实例的方法。但是并不是说客户端必须知道所有的创建者API,我想尝试封装一下,这样客户端就不需要知道需要什么样的创建者了。
  • 不是客户端必须知道,只有服务器必须定义它所服务的创建者,并且它们必须在 Servlet (init()) 的初始化中被初始化一次
  • 好吧,也许我的疑问与我之前发布的代码没有密切相关,但更笼统:当您需要根据参数类型实例化一个类时,如何避免条件。 也许在这个问题(stackoverflow.com/questions/4866873/…)中解释得更好,我看不出有任何方法可以避免有条件的。你能给我一个例子,你可以做到这一点吗?如果更适合您,也可以使用链接示例。提前致谢
  • “当你需要根据参数类型实例化一个类时,如何避免条件。”:是的,我只看到了创建 paremterType 到 classType 或实例的(预先准备好的)映射的可能性为了避免 switch:case 之后的条件,只需从映射中获取实例。这里没有发生多态性,所以也许这不是您要寻找的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多