3

In one class, I have to call a constructor of another class that needs two parameters, a IHelloServiceConnectionObserver and a ContextWrapper. 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.

JohnnyLambada
  • 12,700
  • 11
  • 57
  • 61

7 Answers7

5

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?

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 4
    Agreed. The method doesn't know, or want to know, that the two interfaces are implemented by the same instance. Your embarrassment at writing `this, this` will quickly pass. – Vance Maverick Feb 25 '11 at 06:28
  • In the context of my app, I fully expect that they will always be the same object and don't mind encoding that into the interface. – JohnnyLambada Feb 25 '11 at 06:40
  • If they'll always be the same object then you should declare a new type that extends/implements both of them, and use that type in your methods/constructors. – ide Feb 25 '11 at 07:03
  • @ide -- can't combine because `ContextWrapper` is high up in a framework hierarchy and I actually instantiate lower level classes. I added a note to the question to explain this. – JohnnyLambada Feb 25 '11 at 07:09
  • @Jon -- You answered very early, before I had time to fix my question (accidentally posted it too early), so I don't actually understand your answer, can you elaborate? – JohnnyLambada Feb 25 '11 at 07:13
  • @Jon -- because of the way the app works, they will always be the same object. I'd combine them, but one is a framework object that's high up in the hierarchy -- so combination makes little sense. Your edited answer is perfect, however and I've updated the question with your answer. Thanks! – JohnnyLambada Feb 25 '11 at 07:41
1

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.

mmccomb
  • 13,516
  • 5
  • 39
  • 46
  • Problem here is I still end up with runtime type checking instead of compile time type checking. – JohnnyLambada Feb 25 '11 at 06:38
  • 1
    @JohnnyLambada - no. There's no runtime checking here. The compiler should realizes that the typecasts are redundant and doesn't generate bytecodes to do it. (Or if for some reason it does, the runtime check will always succeed.) – Stephen C Feb 25 '11 at 06:47
1

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.

Community
  • 1
  • 1
ide
  • 19,942
  • 5
  • 64
  • 106
  • Thanks -- I added a Note that explains that `ContentWrapper` is part of the Android framework, so I can't have it extend `IHelloServiceConnectionObserver`. – JohnnyLambada Feb 25 '11 at 06:50
  • Hmm. Unless you strongly feel that your `Activity` absolutely is an `IHelloServiceConnectionObserver`, I would not implement that interface, and instead add a member variable that is an `IHelloServiceConnectionObserver`. What's important to note is that your `HelloServiceConnection` constructor wants an `IHelloServiceConnectionObserver` and `ContextWrapper` (which is the basis of this question), and a very clear way of stating that is with two parameters of those types, which you already have. It just so happens that your `Activity` is both of those. – ide Feb 25 '11 at 06:56
0

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.

Mike Clark
  • 10,027
  • 3
  • 40
  • 54
  • I agree with the has-a over is-a, but is-a relationships, especially with interfaces really help to de-couple code. Because my class implements `IHelloServiceConnectionObserver`, the `HelloServiceConnection` can call back into it without knowing what it is. – JohnnyLambada Feb 25 '11 at 06:53
0

Ideally you do this:

HelloServiceConnection svc = HelloServiceConnection((IHelloServiceConnectionObserver)this,(ContextWrapper)this);

Same thing as you tried before, but more verbose!

Suraj Chandran
  • 24,433
  • 12
  • 63
  • 94
0

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.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • That almost sounds perfect, except for one thing not stated in the question. There are several other framework classes that inherit from `ContextWrapper`, any of which could use a `HelloServiceConnection`. To implement your approach I'd have to create several `Combined` classes (`CombinedActivity`, `CombinedApplication`) which leaves the whole system fragile. Not pleasant -- agreed. – JohnnyLambada Feb 25 '11 at 07:06
0

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();
    }
}
Miserable Variable
  • 28,432
  • 15
  • 72
  • 133