【问题标题】:Constructor over-injection and Facade Service concept构造函数过度注入和门面服务概念
【发布时间】:2018-07-30 17:38:00
【问题描述】:

我有一个非常简单的界面来管理业务提案的更新,特别是在每晚的批处理过程中,每条记录都在这里提交(但它可能用于其他场景)。 此接口在 EJB 2.0 Bean 内部使用,它获取记录并“循环”它们。 注意名字是从意大利语翻译成英语的,所以请原谅可能的错误。我还简化了一些概念。

public interface ProposalUpdateService {
    void updateProposal(final ProposalFirstType proposal);
    void updateProposal(final ProposalSecondType proposal);
}

这个接口的实现有相当多的依赖:

public class ProposalUpdateDefaultService implements ProposalUpdateService {
    private final ComplexService complexService;
    private final OtherComplexService otherComplexService;

    private final ProposalStep<Proposal> stepOne;
    private final ProposalStep<Proposal> stepTwo;
    private final ProposalStep<ProposalTypeTwo> stepThree;
    private final ProposalStep<Proposal> stepFour;

    public ProposalUpdateDefaultService(
            final ComplexService complexService,
            final OtherComplexService otherComplexService,
            final YetAnotherComplexService yetAnotherComplexService,
            final SimpleService simpleService,
            final OtherSimpleService otherSimpleService,
            final YetAnotherSimpleService yetAnotherSimpleService,
            final Converter<ProposalTypeOne, ComplexServiceType> converterProposalTypeOne,
            final Converter<ProposalTypeTwo, OtherComplexServiceType> converterProposalTypeTwo) {
        this.complexService = complexService;
        this.otherComplexService = otherComplexService;

        stepOne = new StepOne(yetAnotherComplexService);
        stepTwo =
                new StepTwo(
                        complexService,
                        otherComplexService,
                        yetAnotherComplexService,
                        converterProposalTypeOne,
                        converterProposalTypeTwo);
        stepThree =
                new StepThree(
                        simpleService,
                        otherSimpleService,
                        yetAnotherSimpleService);
        stepFour = new StepFour();
    }

    ...

如你所见,这个类封装了一个Proposal对象的更新,这个过程分为四个阶段,每个阶段代表一个单一的概念(例如,“这个提案应该过期吗?”或“我应该提前它的状态?”)。这四个阶段在不同类型的Proposal 之间可能会有不同的安排。

这是这两个updateProposal 方法的高度简化实现:

@Override
public void updateProposal(final ProposalTypeOne proposal) {
   stepOne.process(proposal);
   stepTwo.process(proposal);

   if (...) {
      stepFour.process(proposal);
   }
}

@Override
public void updateProposal(final ProposalTypeTwo proposal) {
   stepOne.process(proposal);
   stepTwo.process(proposal);
   stepThree.process(proposal);
   stepFour.process(proposal);
}

两个私有字段

private final ComplexService complexService;
private final OtherComplexService otherComplexService;

用于辅助私有方法。

正如您所见,这个类只是组织和委派工作,但是,它确实依赖于太多其他类。对于某些ProposalStep(s) 也可以这样说。

*Service(s) 在每个步骤中用于从数据库中检索详细信息、更新相关条目等。

您会接受这么多的依赖项吗?
您将如何重构以简化?

我已经阅读了有关 Facade Service 概念作为减少依赖项的一种方式,以及我应该如何将依赖项集群组合在一起,但在这里我真的不明白该怎么做。

我可以将使用它们的Converter(s) 和Service(s) 分组,但无论如何它们会太多。

如果需要其他详细信息,请告诉我。

【问题讨论】:

  • 有多种方法可以降低类在责任和依赖方面的复杂性,但在这里您没有提供足够的输入来说明使用依赖项为您提供具体建议的方式。除了这个问题不适合SO。这个更好:codereview.stackexchange.com
  • @davidxxx 我已经尝试过很多次 CodeReview(例如 stackoverflow.com/questions/51340234/…),我发现 SO 是一个更好的地方。让我知道您需要哪种类型的详细信息,我会提供。
  • @davidxxx “这是这两个 updateProposal 方法的高度简化实现” 表示这是假设/存根代码,在代码审查中偏离主题。
  • 这很好。你有一个特定的编程问题,你带着一个最小的例子来重现这个问题。只是人们经常将问题重定向到代码审查,在那里他们可能会被关闭。
  • @davidxxx 在这种情况下是错误的。请看A guide to Code Review for Stack Overflow users

标签: java design-patterns


【解决方案1】:

我看到的问题是ProposalUpdateDefaultService 做的事情太多,知道的太多。它接受大量服务,创建步骤并执行步骤,而它应该只接受单个参数对象并在不知道步骤是什么的情况下进行更新。

首先,我会尝试通过创建一个包含服务和转换器的单独类来减少构造函数 ProposalUpdateDefaultService 的参数。

public class ServicesAndConverters {
    ComplexService complexService;
    OtherComplexService otherComplexService

    //...

}

这样代码会更干净

public class ProposalUpdateDefaultService implements ProposalUpdateService {
    ServiceAndConverters serviceAndConvert;

    public ProposalUpdateDefaultService(final ServiceAndConverters serviceAndConverters) {
        this.serviceAndConvert = serviceAndConverters; //maybe group them in two different class??
    }

}

现在我可以看到在ProposalUpdateDefaultService 本身中创建步骤的第二个问题。这应该是不同班级的责任。如下所示

 public class ProposalUpdateDefaultService implements ProposalUpdateService {
         ServiceAndConverters serviceAndConvert;
         StepCreator stepCreator = new StepCreator();

         public ProposalUpdateDefaultService(final ServiceAndConverters serviceAndConverters) {
             this.serviceAndConvert = serviceAndConverters;
             stepCreator.createSteps(this.serviceAndConverter);
         }

     }

StepCreator 类应该是这样的

public class StepCreator implements ProposalUpdateService {
        private final ProposalStep<Proposal> stepOne;
        private final ProposalStep<Proposal> stepTwo;
        private final ProposalStep<ProposalTypeTwo> stepThree;
        private final ProposalStep<Proposal> stepFour;

        public void createSteps(ServiceAndConverters s) {
                // do the step processing here
        }

}

现在ProposalUpdateDefaultService 可以在不知道步骤是什么以及需要执行哪个服务的情况下执行这些步骤

@Override
public void updateProposal(final ProposalTypeOne proposal) {
   stepCreator.getStepOne().process(proposal);
   stepCreator.getStepTwo().process(proposal);

   if (...) {
      stepCreator.getStepFour().process(proposal);
   }
}

【讨论】:

  • 我认为封装这些步骤可能是个好主意,但将所有参数移动到另一个对象中对我来说只是安慰剂。我的意思是它并不能缓解问题,因为新对象必须以相同的方式构造。
  • “步骤”对象可以使用责任链模式来组织步骤。
  • 类似于参数对象模式。我觉得它比在构造函数中传递个人更具可读性。当客户端要创建参数对象的实例时,很容易看到需要做什么,而不是创建接受大量参数的工作类实例。见refactoring.com/catalog/introduceParameterObject.html
  • 您链接的示例(DateRange)非常明确,不会引起混淆。在这种情况下,恕我直言。此外,StepCreator 类是接受这些步骤还是在内部创建它们?如果它确实接受它们作为依赖项,它只是 ProposalUpdateDefaultService 的副本
  • StepCreator 类将通过访问服务在内部创建步骤并保存所有步骤。然后您可以访问像stepCreator.getStepOne() 这样的步骤以在您的工人阶级中使用
【解决方案2】:

我发现更方便的解决方案是删除 ProposalUpdateService 抽象,让 EJB Bean 管理各个步骤。

到目前为止,这个抽象层是不必要的,每个步骤仍然可以单独使用。两个 ProposalUpdateService 方法调用都成为 EJB Bean 中的私有方法。

【讨论】:

  • 事实上,您已经将两种方法的逻辑拆分为两个 EJB bean。听起来有进步。 (+1)
  • @davidxxx 不是。仍然存在单个 EJB Bean,因为公共接口是单个方法“updateAllProposals()”。 ProposalUpdateService 已与 EJB Bean “合并”。
  • 嗯,好的。所以你的意思是你拉起调用者层的逻辑?
  • @davidxxx 完全正确! PS:调用者(EJB Bean)是业务逻辑层
  • @davidxxx 谢谢!它几乎什么都不做,它只有几个辅助方法。一切都委托给步骤。我引入的抽象层是可测试的(与 EJB Bean 不同),但我可以单独测试每个步骤。
猜你喜欢
  • 2014-10-07
  • 1970-01-01
  • 2012-06-19
  • 2014-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多