关于oop:上帝对象 – 减少与“主”对象的耦合

God object - decrease coupling to a 'master' object

我有一个名为参数的对象,它在调用树上下、上、下、跨包边界从一个方法传递给另一个方法。它有大约50个状态变量。每个方法可能使用一个或两个变量来控制其输出。

我认为这是个坏主意,因为我不容易看出一个方法需要什么样的功能,甚至不知道如果模块y的某些参数组合与我当前的模块完全无关,会发生什么。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
        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
            {

打电话的人会这样做:

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


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

然而,这可能会导致某些方法需要大量参数,因为它们调用其他方法,而其他方法反过来调用其他方法,等等。这可能是把所有东西都放在神的物体里的灵感。我将给出一个带有太多参数的这种方法的简化示例;您必须想象"太多"==3这里:—)

1
2
3
4
5
6
public void PrintFilteredReport(
   Data data, FilterCriteria criteria, ReportFormat format)
{
   var filteredData = Filter(data, criteria);
   PrintReport(filteredData, format);
}

所以问题是,我们如何减少参数的数量而不求助于上帝的物体?解决方法是摆脱程序化编程,充分利用面向对象的设计。对象可以彼此使用,而无需知道用于初始化其合作者的参数:

1
2
3
4
5
6
7
8
9
10
// 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方法只能用一个参数实现:

1
2
3
4
5
public void Print(data)
{
   var filteredData = this.dataFilter.Filter(data);
   this.reportPrinter.Print(filteredData);
}

顺便说一句,这种关注点分离和依赖注入不仅有利于消除参数。如果您通过接口访问合作者对象,那么这将使您的类

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


如果您所有的方法都使用同一个Parameters类,那么它可能是一个包含相关方法的类的成员变量,那么您可以将Parameters传递到这个类的构造函数中,将它赋给一个成员变量,并且您所有的方法都可以使用它作为参数传递。

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

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

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


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

首先,您可以将大型的ParameterExecution类划分为几个类,每个包一个类,这些类只保存包的参数。

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


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


查询每个客户机的所需参数并注入它们?

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


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

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

在爪哇中,可以设置一个"线程本地"引用,并在每个请求或子任务启动时初始化它。然后,根据该线程本地对访问器进行编码。我不知道.NET中是否存在类似的东西,但您可以使用字典(哈希、映射)来伪造它,字典使用当前线程实例作为键。

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