Java中的大类分解

时间:2009-02-23 21:33:06

标签: java architecture oop refactoring

我刚刚开始学习Java,并且很好奇Java中是否有良好的对象分解方法?让我来描述一个问题。在大型软件项目中,它总是像“核心”或“ui”这样的大类,往往有很多方法,并且打算作为较小类之间的调解者。例如,如果用户单击某个窗口上的按钮,则此窗口的类会向“ui”类发送消息。这个'ui'类捕获此消息,并通过使用应用程序用户界面(通过其中一个成员对象的调用方法)执行某些操作或通过将消息发布到应用程序'core',如果它类似于“退出应用程序”或“启动网络”连接”。

这些对象很难分开,因为它们只是许多小应用程序对象之间的调解器。但是,如果这样的方法是从一个对象到另一个对象的简单任务委托,那么在具有成百上千种方法的应用程序中使用类就不是很方便。 C#通过允许将类实现分解为多个源文件来解决此类问题:您可以按照您选择的方式划分上帝对象,并且它将起作用。

通过在Java中划分这些对象的任何做法?

5 个答案:

答案 0 :(得分:15)

开始分解如此大的对象的一种方法是首先找到由大对象管理的字段或属性的良好子集,这些字段或属性彼此相关并且不与对象的其他字段或属性交互。然后,仅使用这些字段创建一个新的较小的对象。也就是说,将所有逻辑从大类移到新的较小类。在原始的大类中,创建一个简单地传递请求的委托方法。这是一个很好的第一步,只涉及改变大对象。它不会减少方法的数量,但它可以大大减少大类中所需的逻辑量。

经过几轮这样做之后,您可以通过将其他对象直接指向较新的较小对象来开始删除某些委托,而不是通过位于所有内容中的前一个巨大对象。 / p>

例如,请参阅Wikipedia's Delegation pattern讨论。

作为一个简单的例子,如果您有一个人员对象来代表公司的员工,那么您可以创建一个工资单对象来跟踪与工资单相关的值,一个用于跟踪员工评级的评级对象,一个奖励对象跟踪此人赢得的奖励等等。

也就是说,如果你开始使用一个包含以下方法的大类,每个方法都包含业务逻辑,以及许多其他方法:

...
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(); }
...

以前在大对象中的完整逻辑已被移动到这三个新的较小对象。通过此更改,您可以将大对象分解为更小的部分,而无需触摸任何使用大对象的内容。但是,随着时间的推移,您会发​​现大对象的某些客户端可能只需要访问其中一个可分割组件。对于这些客户端,他们可以直接使用小对象,而不是使用大对象并委托给特定对象。但即使这种重构永远不会发生,你也可以通过将不相关项的业务逻辑分成不同的类来改进。

答案 1 :(得分:1)

我已经看到了一些通过继承解决这个问题的案例:让我们说Big类会处理5种不同的事情,并且(由于各种原因)它们都必须在同一个类中。所以你选择一个任意的继承顺序,然后定义:

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 args)和getter使各种页面模板可以访问值(我认为它是JSP)。

因此,故障按逻辑分组,例如,假设该类被称为MyAction,有MyActionBasicArgs(基本CGI参数的字段和设置器),由MyActionAdvancedArgs(高级选项args)扩展,由MyActionExposedValues(getters)扩展,由MyActionDependencies(Spring依赖注入使用的setter,非CGI args)扩展,由MyAction扩展(包含实际的execute()方法)。

由于WebWorks中依赖注入的工作方式(或至少用于工作,当时),它必须是一个巨大的类,因此以这种方式分解使事情更易于维护。但首先,拜托,请看看你是否可以简单地避免单一的大班;仔细考虑你的设计。

答案 2 :(得分:1)

下一个逻辑步骤可能是将BigClass更改为java包。接下来为每组相关功能创建新对象(在每个类中注意该对象是新包的一部分)。

这样做的好处是依赖性降低和性能。

  1. 无需导入整个 package / BigClass只是为了得到一些 方法。
  2. 代码更改为相关 功能不需要 重新编译/重新部署整个 包/大类。
  3. 使用的内存较少 用于分配/解除分配对象, 因为你使用的是较小的课程。

答案 3 :(得分:0)

是的,C#提供了部分类。当你说:

时,我认为这就是你所指的
  

C#通过允许将类实现分解为多个源来解决此类问题   文件:你可以按照你选择的方式划分上帝对象,它会起作用。

这确实有助于使大型课程更易于管理。但是,当需要扩展代码生成器创建的代码时,我会发现最常用的部分类。当一个类与你所说的一样大时,它几乎总是可以通过适当的面向对象设计分成更小的类。使用部分类可以避开更正确的面向对象设计,这有时可以,因为最终目标是稳定,可靠,可维护的代码,而不是OO代码的教科书示例。但是,很多时候,将大对象的代码放入同一类的大量较小的部分类实例中并不是理想的解决方案。

如果您可以找到不相互交互的“god”对象属性的子集,那么这些集合中的每一个都将在逻辑上成为新对象类型的良好候选者。但是,如果这个“神”对象的所有属性彼此依赖,那么你可以做很多事情来分解对象。

答案 4 :(得分:0)

我不知道为什么你会有这么大的课程。

我想如果你使用gui构建器代码生成并且对它很懒,那么你可能会遇到这种情况,但是除非你自己控制,否则codegen通常会变得很讨厌。

任意分裂一个类是解决一个可怕的制造问题的可怕方法。 (代码重用,一方面几乎不可能)

如果必须使用GUI构建器,让它构建更小的组件,然后使用小组件构建更大的GUI。每个组件应该完成一项工作并且做得很好。

如果可以避免,请尽量不要编辑生成的代码。将业务逻辑置于一个绅士的“框架”中只是一种可怕的设计模式。大多数代码生成器对此都没有多大帮助,所以尽量做一个简单的编辑,以便从外部类中获取所需内容(想想MVC,其中genne代码是您的View,您编辑的代码应该在您的模型中和控制器)。

有时您可以从Frame对象公开getComponents方法,通过遍历容器获取所有组件,然后将它们动态绑定到数据和代码(通常绑定到name属性很好),我一直能够以这种方式安全地使用表单编辑器,并且所有绑定代码都很容易被抽象和重用。

如果你不是在谈论生成的代码 - 那么在你的“上帝”课程中,它是完成一项小工作并且做得好吗?如果没有,请拉出“作业”,将其放入自己的班级,并委托给它。

你的上帝课程是否完全考虑了?当我看到像这样的大类时,我经常看到很多复制/粘贴/编辑行。如果有足够的相似性来复制和过去并编辑某个部分,那么就足以将这些行分解为一个代码块。

如果您的大类是GUI类,请考虑装饰器 - 可重用并将其移出主类。双赢。

我想你的问题的答案是,在Java中,我们只是使用优秀的OO来确保问题不会首先出现(或者我们不会 - Java肯定不会对你正在谈论的问题免疫关于任何其他语言)