【问题标题】:God object - decrease coupling to a 'master' object上帝对象 - 减少与“主”对象的耦合
【发布时间】:2010-12-07 12:04:39
【问题描述】:

我有一个名为Parameters 的对象,它在调用树中从一个方法到另一个方法在调用树中上下颠倒,跨越包边界。它有大约五十个状态变量。每种方法都可能使用一个或两个变量来控制其输出。

我认为这是一个坏主意,因为我无法轻易看到一个方法需要运行什么,甚至如果模块 Y 的参数组合与我当前的模块完全无关,甚至可能会发生什么。

有什么好的技术可以减少与这个上帝对象的耦合,或者理想地消除它?

        public void ExporterExcelParFonds(ParametresExecution parametres)
    {
        ApplicationExcel appExcel = null;
        LogTool.Instance.ExceptionSoulevee = false;


        bool inclureReferences = parametres.inclureReferences;
        bool inclureBornes = parametres.inclureBornes;
        DateTime dateDebut = parametres.date;
        DateTime dateFin = parametres.dateFin;

        try
        {
            LogTool.Instance.AfficherMessage(Variables.msg_GenerationRapportPortefeuilleReference);

            bool fichiersPreparesAvecSucces = PreparerFichiers(parametres, Sections.exportExcelParFonds);
            if (!fichiersPreparesAvecSucces)
            {
                parametres.afficherRapportApresGeneration = false;
                LogTool.Instance.ExceptionSoulevee = true;
            }
            else
            {

调用者会这样做:

                PortefeuillesReference pr = new PortefeuillesReference();
            pr.ExporterExcelParFonds(parametres);

【问题讨论】:

  • “参数”是配置对象吗?
  • 是的。用于让 UI 携带业务层可能需要的任何参数。

标签: oop refactoring god-object


【解决方案1】:

首先,冒着说明显而易见的风险:传递方法使用的参数,而不是上帝对象。

但是,这可能会导致某些方法需要大量参数,因为它们调用其他方法,而这些方法又调用其他方法等等。这大概就是把所有东西都放在神器里的灵感吧。我将给出一个带有太多参数的方法的简化示例;你必须想象这里的“太多”== 3 :-)

public void PrintFilteredReport(
   Data data, FilterCriteria criteria, ReportFormat format)
{
   var filteredData = Filter(data, criteria);
   PrintReport(filteredData, format);
}

那么问题来了,我们如何在不求助于上帝对象的情况下减少参数的数量呢?答案是摆脱过程式编程,充分利用面向对象的设计。对象可以相互使用,而无需知道用于初始化其协作者的参数:

// dataFilter service object only needs to know the criteria
var dataFilter = new DataFilter(criteria);

// report printer service object only needs to know the format
var reportPrinter = new ReportPrinter(format);

// filteredReportPrinter service object is initialized with a
// dataFilter and a reportPrinter service, but it doesn't need
// to know which parameters those are using to do their job
var filteredReportPrinter = new FilteredReportPrinter(dataFilter, reportPrinter);

现在FilteredReportPrinter.Print方法只需要一个参数就可以实现了:

public void Print(data)
{
   var filteredData = this.dataFilter.Filter(data);
   this.reportPrinter.Print(filteredData);
}

顺便说一句,这种关注点分离和依赖注入的好处不仅仅是消除参数。如果你通过接口访问合作者对象,那么你的类就完成了

  • 非常灵活:您可以使用任何可以想象的过滤器/打印机实现来设置 FilteredReportPrinter
  • 非常可测试:您可以通过预设响应传入模拟协作者,并验证它们在单元测试中的使用是否正确

【讨论】:

    【解决方案2】:

    如果你所有的方法都使用同一个Parameters类,那么它可能应该是一个包含相关方法的类的成员变量,那么你可以将Parameters传递给这个类的构造函数,将它分配给一个成员变量,你的所有方法都可以使用它,而必须将它作为参数传递。

    开始重构这个上帝类的一个好方法是将它分成更小的部分。找到相关的属性组,并将它们分解到自己的类中。

    然后,您可以重新访问依赖于 Parameters 的方法,看看是否可以将其替换为您创建的较小类之一。

    如果没有一些代码示例和实际情况,很难给出一个好的解决方案。

    【讨论】:

      【解决方案3】:

      听起来您没有在设计中应用面向对象 (OO) 原则。既然你提到了“对象”这个词,我想你是在某种 OO 范式中工作。我建议您将“调用树”转换为模拟您正在解决的问题的对象。 “神物”绝对是要避免的。我认为你可能遗漏了一些基本的东西......如果你发布一些代码示例,我可能能够更详细地回答。

      【讨论】:

      • 是的,这是一个遗留系统,而且写得不好,我同意。
      • 请贴一个代码示例...您如何处理这种情况取决于细节。
      【解决方案4】:

      查询每个客户端所需的参数并注入?

      示例:每个需要“参数”的“对象”都是“客户端”。每个“客户端”都公开一个接口,“配置代理”通过该接口向客户端查询其所需参数。然后,配置代理“注入”参数(并且仅是客户端所需的参数)。

      【讨论】:

      • 您能否对此进行扩展,也许可以举个例子?我想我同意这一点,但我想确定一下。
      • 很可爱。我正在考虑您的解决方案的后果。
      【解决方案5】:

      对于指示行为的参数,可以实例化一个显示配置行为的对象。然后客户端类只需使用实例化的对象——客户端和服务都不必知道参数的值是什么。例如,对于告诉从何处读取数据的参数,让 FlatFileReader、XMLFileReader 和 DatabaseReader 都继承相同的基类(或实现相同的接口)。根据参数的值实例化其中一个,然后读取器类的客户端只需向实例化的读取器对象请求数据,而不知道数据是来自文件还是来自数据库。

      首先,您可以将大的 ParametresExecution 类分解为多个类,每个包一个,它们只保存包的参数。

      另一个方向可能是在构造时传递 ParametresExecution 对象。您不必在每次函数调用时都传递它。

      【讨论】:

        【解决方案6】:

        (我假设这是在 Java 或 .NET 环境中)将类转换为单例。添加一个名为“getInstance()”的静态方法或类似于 call 的方法来获取名称-值包(并停止“践踏”它——参见“代码完成”一书的第 10 章)。

        现在是困难的部分。据推测,这是在 Web 应用程序或其他一些非批处理/单线程环境中。因此,要在对象不是真正的单例时访问正确的实例,您必须在静态访问器中隐藏选择逻辑。

        在java中,你可以设置一个“线程本地”引用,并在每个请求或子任务启动时对其进行初始化。然后,根据该线程本地对访问器进行编码。我不知道 .NET 中是否存在类似的东西,但您总是可以使用 Dictionary (Hash, Map) 来伪造它,它使用当前线程实例作为键。

        这是一个开始...(总是会分解 blob 本身,但我构建了一个框架,其中包含一个非常相似的半全局值存储)

        【讨论】:

        • 我试图避免单身。我实际上不喜欢单例,因为我在哪里见过它。在这里使用它会退后一步。
        • -1 您需要 threadlocal 来减少“单例”的“全局性”这一事实表明您首先不应该使用全局变量。无论如何,OP 可能正在谈论单线程 Winforms 应用程序。
        猜你喜欢
        • 1970-01-01
        • 2015-11-07
        • 1970-01-01
        • 2017-05-09
        • 2019-08-09
        • 1970-01-01
        • 2012-07-29
        • 1970-01-01
        • 2012-05-14
        相关资源
        最近更新 更多