开发者

How to require a parameter be of two types when passed to a method in Java

开发者 https://www.devze.com 2023-02-13 03:37 出处:网络
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.

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

精彩评论

暂无评论...
验证码 换一张
取 消