In one class, I have to call a constructor of another class that needs two parameters, a IHelloServiceConnectionObserver
and a C开发者_Python百科ontextWrapper
. The problem is they're both this
.
Note: ContextWrapper
is a framework class that I have no control over (android.content.ContextWrapper
, actually). My class (an Android Activity
) is-a ContextWrapper
already, and I want to mix-in a little IHelloServiceConnectionObserver
ness to it.
Also note, my class is one of several classes that all inherit from ContextWrapper
, so combining ContextWrapper
and IHelloServiceConnectionObserer
won't work.
I could do this:
HelloServiceConnection svc = HelloServiceConnection(this,this);
calls
public HelloServiceConnection(IHelloServiceConnectionObserver observer, ContextWrapper contextWrapper){
this.observer = observer;
this.contextWrapper = contextWrapper;
}
But that looks silly. Or I could do this:
HelloServiceConnection svc = HelloServiceConnection(this);
calls
public HelloServiceConnection(IHelloServiceConnectionObserver observer){
this.observer = observer;
this.contextWrapper = (ContextWrapper) observer;
}
But now I move a nice compile time error to a runtime error.
What's the best practice here?
EDIT: Well, I can't say it's a "best practice", but for my special set of circumstances, Jon Skeet has the right answer. Here's what the code ends up looking like:
helloServiceConnection = HelloServiceConnection.create(this);
calls
public static <T extends ContextWrapper & IHelloServiceConnectionObserver> HelloServiceConnection create(T value){
return new HelloServiceConnection(value, value);
}
which in turn calls
private HelloServiceConnection(IHelloServiceConnectionObserver observer, ContextWrapper contextWrapper){
this.observer = observer;
this.contextWrapper = contextWrapper;
}
So let me give a bit more context as to why this is the right answer for this special situation. Since ContextWrapper
is part of a framework that I don't control, I can't change it. Because it's also an ancestor of several classes, any one of which I might want to use HelloServiceConnection
in, it doesn't really make sense to extend all the decendants of ContextWrapper
to add in IHelloServiceConnectionObserver
.
So I thought I was left will little choice but the this,this
idom. Jon's answer, once I understood it, saves the day!
Thanks, Jon -- and thanks to all who participated.
Well, you could make the call generic:
public <T extends ContextWrapper & IHelloServiceConnectionObserver> void method
(T item)
and let type inference sort it out. It's not terribly pleasant though.
EDIT: It looks like you're trying to use a constructor, which is going to make it harder. You can use a static method to create the instance though:
public static <T extends ContextWrapper & IHelloServiceConnectionObserver>
HelloServiceConnection createConnection(T value)
{
return new HelloServiceConnection(value, value);
}
private HelloServiceConnection(ContextWrapper wrapper,
IHelloServiceConnectionObserver observer)
{
this.wrapper = wrapper;
this.observer = observer;
}
Okay, so the constructor and the type itself will end up with two separate fields - but we know that they will both refer to the same object. You could even assert that in the constructor if you like.
As others have said though, it's worth considering whether you really, really need this coupling. What bad things would happen if the wrapper and the observer weren't the same object?
If you want to make a point of the fact that the object has the type ContextWrapper AND implements the interface then you could cast it in the method call?
method((ContextWrapper)this, (IHelloServiceConnectionHelper)this)
The casts are obviously redundant but it makes your point more explicit.
I believe you should stay with your current design, or have ContextWrapper
extend IHelloServiceConnectionObserver
. Your argument for merging the two is that new HelloServiceConnection(this, this)
looks awkward. But why is this scenario even present?
It's because you just so happen to have an object that is both a ContextWrapper
and IHelloServiceConnectionObserver
. If they should always be together (which you're forcing on the programmer by merging the two constructor parameters together), then make one type extend the other. Or, have HelloServiceConnection
take one parameter that is whatever type this
is.
If they should be separate, then your current code is fine. Or, instead of having your current class (the type of this
) extend/implement both ContextWrapper
and IHelloServiceConnectionObserver
, you could have it extend/implement only one of them and have a member variable that points to an instance of the other. Remember, when in doubt, use composition over inheritance.
I wouldn't be afraid to write this code:
IHelloServiceConnectionObserver observer = this;
ContextWrapper contextWrapper = this;
HelloServiceConnection svc = new HelloServiceConnection(observer, contextWrapper);
But in fact, I would reconsider having the this
's class be both a IHelloServiceConnectionObserver
and a ContextWrapper
.
Wherever possible, I have my classes either extend just 1 class, or implement just 1 interface. I like the clarity and separation afforded by that approach. This philosophy prefers a 'has-a' relationship instead of an 'is-a' relationship. Inner member classes in Java make a "one inheritance per class" approach fairly practical.
Ideally you do this:
HelloServiceConnection svc = HelloServiceConnection((IHelloServiceConnectionObserver)this,(ContextWrapper)this);
Same thing as you tried before, but more verbose!
Another (not pleasant IMO) solution would be to create an interface Combined
that extends your IHelloServiceConnectionObserver
and ContextWrapper
interfaces, change the class of this
to implement ICombined
, and change HelloServiceConnection
to take a single argument of type ICombined
.
Does this work?
Conceptually, it declares a generic type Combined
which creates HelloServiceConnection
objects using themselves as both the observer and and context wrapper.
class ContextWrapper {}
interface IHelloServiceConnectionObserver {}
class HelloServiceConnection
{
HelloServiceConnection(ContextWrapper cw, IHelloServiceConnectionObserver o)
{
System.out.println("HelloServiceConnection(" + cw + " ," + o + ")");
}
}
abstract class Combined<T> extends ContextWrapper
implements IHelloServiceConnectionObserver
{
public HelloServiceConnection svc()
{
return new HelloServiceConnection(this, this);
}
}
class MyClass extends Combined<MyClass>
{
public static void main(String args[])
{
MyClass mc = new MyClass();
System.out.println(mc);
mc.svc();
}
}
精彩评论