可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have just started to learn Java and is curious is it any good practice in Java for good object decomposition? Let me describe a problem. In big software project it's always a big classes like 'core' or 'ui' that tends to have a lot of methods and are intended as a mediators between smaller classes. For example, if user clicks a button on some window, this window's class sends a message to 'ui' class. This 'ui' class catches this message and acts accordingly by doing something with application user interface ( via calling method of one of it's member objects ) or by posting message to application 'core' if it's something like 'exit application' or 'start network connection'.
Such objects is very hard to break apart since they are a mere mediators between a lots of small application objects. But having a classes in application with hundreds and thousands of methods is not very handy, event if such methods are trivial task delegation from one object to another. C# solves such problem by allowing to break class implementation into multiple source files: you can divide god object any way you choose, and it will work.
Any practices by dividing such objects in Java?
回答1:
One way to begin breaking such a large object apart is to first find a good subset of fields or properties managed by the large object that are related to each other and that don't interact with other fields or properties of the object. Then, create a new, smaller object using only those fields. That is, move all logic from the large class to the new smaller class. In the original large class, create a delegation method that simply passes the request along. This is a good first step that only involves changing the big object. It doesn't reduce the number of methods, but it can greatly reduce the amount of logic needed in the large class.
After a few rounds of doing this, you can begin to remove some of the delegation by pointing other objects directly at the newer, smaller objects, rather than going through the previously-huge object that was in the middle of everything.
See Wikipedia's Delegation pattern discussion for example.
As a simple example, if you have a personnel object to represent staff at a company, then you could create a payroll object to keep track of payroll-related values, a ratings object to keep track of employee ratings, an awards object to keep track of awards that the person has won, and so on.
To wit, if you started out with one big class containing the following methods, each containing business logic, among many other methods:
...
public boolean isManagement() { ... }
public boolean isExecutive() { ... }
public int getYearsOfService() { ... }
public Date getHireDate() { ... }
public int getDepartment() { ... }
public BigDecimal getBasePay() { ... }
public BigDecimal getStockShares() { ... }
public boolean hasStockSharePlan() { ... }
...
then this big object could, in its constructor, create a newly created object StaffType
and a newly created object PayInformation
and a newly created object StaffInformation
, and initially these methods in the big object would look like:
// 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(); }
...
where the full logic that used to be in the big object has been moved to these three new smaller objects. With this change, you can break the big object into smaller parts without having to touch anything that makes use of the big object. However, as you do this over time, you'll find that some clients of the big object may only need access to one of the divisible components. For these clients, instead of them using the big object and delegating to the specific object, they can make direct use of the small object. But even if this refactoring never occurs, you've improved things by separating the business logic of unrelated items into different classes.
回答2:
I've seen some cases where this is solved by inheritance: let's say class Big takes care of 5 different things, and (for various reasons) they all have to be in the same class. So you pick an arbitrary inheritance order, and define:
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.
If you can really layer things up, so that the breakage makes sense (Part2 actually uses stuff from Part1, but not vice versa, etc.) then maybe it makes some sense.
The place where I've seen this is in WebWorks, where a single class had tons of getter/setter methods -- the setters used for dependency injection (e.g., URL args passed to the object upon execution) and the getters for making values accessible to various page templates (I think it was JSPs).
So, the breakdown grouped stuff logically, e.g., assuming the class was called MyAction, there was MyActionBasicArgs (fields and setters for basic CGI arguments), extended by MyActionAdvancedArgs (advanced-option args), extended by MyActionExposedValues (getters), extended by MyActionDependencies (setters used by Spring dependency injection, non-CGI args), extended by MyAction (which contained the actual execute() method).
Because of the way dependency injection in WebWorks works (or at least, used to work, back then), it had to be one huge class, so breaking it down this way made things more maintainable. But first, please, please, see if you can simply avoid having a single huge class; think carefully about your design.
回答3:
The next logical step may be to change the BigClass into a java package. Next create new objects for each group of related functionality (noting in each class that the object is part of the new package).
The benefits of doing this are dependency reduction and performance.
- No need to import the entire
package/BigClass just to get a few
methods.
- Code changes to related
functionality don't require a
recompile/redeploy of the entire
package/BigClass.
- Less memory used
for allocating/deallocating objects,
since you are using smaller classes.
回答4:
I don't know why you would ever have such a large class.
I suppose if you were using a gui builder code generation and being lazy about it, you might end up in such a situation, but codegen usually ends up nasty unless you take control yourself.
Splitting a single class arbitrarily is a terrible solution to a terrible manufactured problem. (Code reuse, for one thing will become virtually impossible)
If you have to use a GUI builder, have it build smaller components, then use the small components to build up a bigger GUI. Each component should do exactly one job and do it well.
Try not to EVER edit generated code if you can avoid it. Putting business logic into a genned "frame" is just a horrid design pattern. Most code generators aren't very helpful with this, so try to just make a single, minimal edit to get at what you need from external classes (think MVC where the genned code is your View and the code you edit should be in your Model and Controller).
Sometimes you can just expose the getComponents method from the Frame object, get all the components out by iterating through the containers and then dynamically bind them to data and code (often binding to the name property works well), I've been able to safely use form editors this way, and all the binding code tends to be very easily abstracted and reused.
If you're not talking about generated code--Well in your "God" class, does it do exactly one small job and do it well? If not, pull out a "Job", put it in it's own class, and delegate to it.
Is your GOD class fully factored? When I've seen huge classes like this, I've usually seen a lot of copy/paste/edit lines. If there is enough of a similarity to copy and past and edit some section, then there is enough to factor these lines into a single chunk of code.
If your big class is a GUI class, consider decorators--reusable and moves stuff out of your main class. A double win.
I guess the answer to your question is that in Java we just use good OO to ensure that the problem doesn't arise in the first place (or we don't--Java's certainly not immune to the problems you are talking about any more than any other language)
回答5:
Yes, C# provides partial classes. I assume this is what you are referring to when you say:
C# solves such problem by allowing to break class implementation into multiple source
files: you can divide god object any way you choose, and it will work.
This does help make huge classes more manageable. However, I find partial classes best used when one needs to extend code created by a code generator. When a class is as large as you're talking about, it can almost always be divided into smaller classes by proper object oriented design. Using a partial class sidesteps the more correct object oriented design, which is sometimes OK as the end goal is stable, reliable, maintainable code, and not a textbook example of OO code. However, many times, putting the code of a large object into a large number of smaller partial class instances of the same class is not the ideal solution.
If you can possibly find subsets of the properties of the "god" object that do not interact with one another, then each one of those sets would logically make a good candidate for a new object type. However, if all properties of this "god" object depend on one another, then there is not much you can do to decompose the object.