问题
I am debating the proper, OO-design to use another object's functionality (methods) from a java class, while both objects remain decoupled as much as possible.
For example, at some point in my class, to implement my logic, I need to call a method that belongs to another object, say a helper class. This helper class does not need to be in any way related to my original class, it just has a specific method which is visible to, and available for my class to use.
After the logic is implemented, the helper method (or the helper object) is not needed down the line.
Obviously, I would need a reference to this helper object in order to use its method. But to enforce encapsulation, I should not declare an instance variable in my original class to reference this helper object? Is that reasoning correct? Also, the helper class is not aware of any client class that might use it.
Would a local variable be more appropriate in this case? Declare and instantiate the helper object in the method which will make use of its functionality? Where is the best location in my original class to declare and instantiate such a helper object?
I was wondering if there is a high-level example, or if this is explained with a bit more elaboration in OO articles. I'd appreciate any encapsulation-focused input or hint on the above.
回答1:
So is this the scenario? Are these the questions?
Class MyClass {
private SomeState myState;
public voic oneMethod() {
// Q1 : what type is **ahelper** ?
// Q2 : where do we declare it ?
// Q3 : where do we initialise it?
aHelper.doSomeWork();
// update your state depending upon the results
}
}
Q1. I think you should declare aHelper as an Interface
HelperInterface aHelper;
Then we are not coupled to any specific implementation.
Q2. If you only use it in one place declare it in that function, otherwise as a member variable. You don't increase coupling by doing either.
HelperInterface aHelper = ? what here?
aHelper.soSomeWork();
Q3. Initialise either in a constructor or with a lazy getter using a factory.
public MyClass(HelperInterface injectedHelper) {
aHelper = injectedHelper;
}
This can make testing very easy, your tests can inject a Mocked Helper class.
Or you can use a lazy intialiser. This is quite handy if your helper is a local variable in your method(s). Again the factory can be injected or be static depending upon your preference.
private getHelper() {
if (aHelper == null ){ make the helper, perhaps using a factory }
return aHelper
}
回答2:
But to enforce encapsulation, I should not declare an instance variable in my original class to reference this helper object? Is that reasoning correct?
No, declaring an instance variable has nothing to do with breaking encapsulation.
The relevant considerations are:
- dependency: by default you depend on the utility class you use, and it doesn't depend on you. Various techniques (e.g. interfaces, strategy patterns, dependency injection) can be used to reverse or reduce that dependency, if required. But in the simple case, depending on it is probably ok.
- object lifetime: if it is an object, you need it to exist at the point you use it. It existing may mean something semantically (i.e. change the behaviour of other parts of your program), or may have performance implications (it is expensive to create, or ties up a lot of memory if left hanging around when not needed). So you need a way of handling it's lifetime that is compatible with both it's nature and your goals.
The basic choices are:
- local unshared variable in one or more of your functions - it is created when needed, goes away as soon as the function exits. Probably the default choice, everything else is an optimisation or special case.
- shared instance variable created in constructor - created only once, but last until your object itself gets garbage collected/destroyed.
- shared instance variable created first time used - as above, but delaying creation at the cost of complexity.
- external static function - no object, so no lifetime issues. Suitable for something with no internal state and simple interface, otherwise you end up having a implicit object lifetime managed only by the comments to the functions (as in C library functions like strcpy).
Advanced choices:
- external singleton - object manages it's own lifetime, guaranteeing one will be available to you. Works ok for some things, but very possible to overuse.
- dependency injection - someone else (typically a framework managed by configuration files) breaks your encapsulation and puts in the object you will need.
All the other ways of doing this (e.g. add the object to constructor or method arguments) add extra dependencies to the system and so shouldn't be done unless at least the basic choices above aren't suitable.
回答3:
The right answer depends on the nature of the relationship between the helper class (H) and the method you use on it (M), and the original object class (C). You mentioned a few key points:
- You don't want to put the logic needed into
C
. - You have placed it into
H
instead. H.M()
is used only once byC
.H
is client-agnostic.- Because you say "obviously, I would need a reference to this helper object in order to use its method", I assume that you can only work with instances of
H
and thatM()
is an instance method.
There are a couple of solutions:
Assess whether
M
wouldn't be better as a static method. This is a very compelling use for a static method if I've ever seen one. You don't mention anything about H maintaining state.Use the Strategy pattern. If
H.M()
represents a specific way of doing something, thenH
is the Strategy object in the pattern forC
. If there are otherH
-like classes with similarM()
methods, these are the different strategies you can pick from.
回答4:
Static methods are hard to test, or rather static methods beeing called in another method is hard to mock. I find it easy to do good design by thinking in terms of testing. If the method is a non-static member, you can easily unit-test code calling that method without also testing that method. Are you with me? Let's say a method m uses the network to do stuff. If that method is static, every time you test code that uses the method m, it will do stuff at the network. And if the network stuff fails, your test fails, but not the code you want to test. You see? If it is not static, you can mock the object with the method, and make it always return "OK".
That beeing said, I would send the helper as a parameter to the method, or rather a helper-interface. That way your class is totally oblivious of how to create a helper, or even what type. Ignorance is bliss. The class will only know how to use it, beautiful stuff. :)
回答5:
I am debating the proper, OO-design to use another object's functionality (methods) from a java class, while both objects remain decoupled as much as possible.
You are putting too much effort in this. Decoupling does not mean not having a connection at all. If you are going to use the helper object once, just pass it as a parameter to the code of block that uses it, or have your class get an instance of it from a factory.
But at some point you have to have a reference to it. Obviously you do not want to have an member instance referencing it (potential memory leak). So that would call some sort of factory or instance manager that gets you a reference to a helper.
But don't go overboard with this. The goal is to get a solution, not to play decoupling games and getting things fit into artificial, redundant class hierarchies. The later is the worst usage of OO concepts.
回答6:
Why don't you go for a static method, or have a singleton helper object?
来源:https://stackoverflow.com/questions/1351157/using-another-objects-functionality-following-a-proper-oo-design-encapsulatio