这些对象很难分解,因为它们只是许多小应用程序对象之间的中介者。但是,在应用程序中拥有具有数百或数千个方法的类,即使这些方法是从一个对象委派到另一个对象的平凡任务,也不太方便。 C#通过允许将类实现拆分为多个源文件来解决此类问题:您可以按任意方式划分God对象,并且它将起作用。
有没有在Java中分解这些对象的实践?
一种开始拆分如此庞大的对象的方法是首先找到由大对象管理的一组良好的字段或属性子集,这些字段相关并且不与对象的其他字段或属性交互。然后,只使用这些字段创建一个新的、更小的对象。也就是说,将全部逻辑从大类移动到新的较小类中。在原始的大类中,创建一个委托方法,只需简单地将请求传递下去。这是一个很好的第一步,只涉及改变大对象,虽然它并没有减少方法数量,但它可以大大减少大类中需要的逻辑。
经过几轮这样的操作后,您可以通过直接指向新的较小对象来删除部分委托,而不是通过以前处于所有事情中间的巨大对象。
请参见维基百科的委托模式讨论,以获取示例。
举个简单的例子,如果您有一个人员对象来代表公司员工,那么您可以创建一个薪资对象来跟踪与薪资相关的值,一个评级对象来跟踪员工的评级,一个奖项对象来跟踪人员赢得的奖项,等等。
换句话说,如果您最初拥有一个包含以下方法的大类,其中每个方法都包含业务逻辑和许多其他方法:
...
public boolean isManagement() { ... }
public boolean isExecutive() { ... }
public int getYearsOfService() { ... }
public Date getHireDate() { ... }
public int getDepartment() { ... }
public BigDecimal getBasePay() { ... }
public BigDecimal getStockShares() { ... }
public boolean hasStockSharePlan() { ... }
...
那么这个大对象可以在它的构造函数中创建一个新创建的对象StaffType
,一个新创建的对象PayInformation
和一个新创建的对象StaffInformation
,最初这些方法在大对象中看起来像:
// Newly added variables, initialized in the constructor (or as appropriate)
private final StaffType staffType;
private final StaffInformation staffInformation;
private final PayInformation payInformation;
...
public boolean isManagement() { return staffType.isManagement(); }
public boolean isExecutive() { return staffType.isExecutive(); }
public int getYearsOfService() { return staffInformation.getYearsOfService(); }
public Date getHireDate() { return staffInformation.getHireDate(); }
public int getDepartment() { return staffInformation.getDepartment(); }
public BigDecimal getBasePay() { return payInformation.getBasePay(); }
public BigDecimal getStockShares() { return payInformation.getStockShares(); }
public boolean hasStockSharePlan() { return payInformation.hasStockSharePlan(); }
...
原来存放在大对象中的所有逻辑已经被移到这三个新的较小对象中。通过这种改变,您可以将大对象分解成更小的部分,而无需触及使用大对象的任何内容。然而,随着时间的推移,您会发现一些大对象的客户端只需要访问其中一个可分割组件。对于这些客户端,他们可以直接使用小对象而不是使用大对象并委托给特定对象。但即使这种重构从未发生过,将不相关项目的业务逻辑分离到不同的类中也会改进事情。
BigPart1 // all methods dealing with topic #1
BigPart2 extends BigPart1 // all methods dealing with topic #2
...
Big extends BigPart4 // all methods dealing with the last topic.
如果您真的可以将事物分层,使得故障有意义(即Part2实际上使用了Part1的东西,但反之则不然等),那么也许这样做有一些意义。
我看到过这种情况是在WebWorks中,一个类拥有大量的getter / setter方法--setter用于依赖注入(例如,在执行期间向对象传递URL参数)和getter用于使值可供各种页面模板访问(我认为是JSP)。
因此,逻辑上将Breakdown分组,例如假设类被称为MyAction,则存在MyActionBasicArgs(基本CGI参数的字段和setter),由MyActionAdvancedArgs(高级选项args)延伸,并由MyActionExposedValues(getter)延伸,再由MyActionDependencies(由Spring依赖注入使用的setter,非CGI args)延伸,最后是包含实际execute()方法的MyAction。
由于WebWorks中依赖注入的工作方式(或至少是当时的工作方式),必须是一个巨大的类,因此以这种方式进行分解可以使维护更加容易。但首先,请务必仔细考虑您的设计,尽可能避免单个巨大类的出现。
是的,C#提供了部分类。我猜这就是你所说的:
C#通过允许将类实现分解为多个源文件来解决这个问题:您可以按任意方式划分大型对象,并且它将正常工作。
这确实有助于使庞大的类更易管理。然而,我发现当需要扩展代码生成器创建的代码时,最好使用部分类。当一个类像你所说的那样大时,几乎总是可以通过适当的面向对象设计将其分解为较小的类。使用部分类绕过了更正确的面向对象设计,这有时是可以接受的,因为最终目标是稳定、可靠、易于维护的代码,而不是面向对象代码的教科书式范例。然而,很多时候,将大型对象的代码放入同一类的大量较小的部分类实例中并不是理想的解决方案。
如果可能找到“上帝”对象属性的子集彼此不相互作用,则每个子集都可以成为新对象类型的良好候选。但是,如果这个“上帝”对象的所有属性都相互依赖,那么您就没有太多的方法来分解该对象。
我不知道为什么你会有这样一个如此庞大的类。
我想,如果你正在使用GUI构建器代码生成并且懒得处理它,你可能会陷入这种情况,但是除非你自己掌控,否则codegen通常会变得很糟糕。
任意拆分单个类是可怕的解决方案,是一种可怕的人为问题。(例如,代码重用将变得几乎不可能)
如果必须使用GUI构建器,请让它构建较小的组件,然后使用这些小组件来构建更大的GUI。每个组件应该只做一件事,并且做得很好。
尽量避免编辑生成的代码。将业务逻辑放入生成的“框架”中是一种可怕的设计模式。大多数代码生成器在这方面并不是很有帮助,因此请尝试仅进行一次最小编辑以从外部类中获取所需内容(考虑MVC,其中生成的代码是您的视图,而您编辑的代码应该在您的模型和控制器中)。
有时候你可以从Frame对象中暴露getComponents方法,通过迭代容器获取所有组件,然后动态地将它们绑定到数据和代码上(通常绑定到名称属性上效果很好)。我一直都能够安全地使用表单编辑器,而且所有的绑定代码往往非常容易抽象和重用。