Henry Merriam
Henry Merriam

Reputation: 824

Can a Java interface in another package be refactored from my code?

I'm using an (open-source) API in my Java project and referencing interfaces from it. One interface (Foo) is used extensively both in the API and in projects that use it. For my project, I'd like to use an interface which only exposes a subset of the methods that Foo does, such that Foo would inherit methods from my new interface (Bar). I don't want to change the interface of Foo at all; rather, I'd like to be able to write classes which implement Bar, and then manipulate those along with classes which implement Foo as if they are all Bar.

Since the API is in a different package, is there any way to accomplish this such that Bar is a superclass (interface) of all Foo?

Upvotes: 4

Views: 1122

Answers (4)

Grundlefleck
Grundlefleck

Reputation: 129327

Because Java has nominal typing, such that org.theirs.Foo is in no way related to org.yours.Foo, even if they have the exact same method signatures, I don't think this is possible to do with inheritance in Java. Even with generics, AFAIK there is no way to say, "this method takes an instance of Bar<T extends Foo OR Bar>".

Instead, I think you want to use an Adapter interface for the library's Foo, with your own (i.e. a FooAdapter, having the exact same signatures as Foo) and have this extend Bar. The methods you want in Bar could then be extracted from FooAdapter. Unfortunately your code would have to be modified so that everywhere you previously referenced their Foo, you would instead reference either:

  • Bar if the only methods called are those you would define in your interface
  • FooAdapter if methods defined in Foo but NOT in Bar are called.

This method, although clean, and a good separation of concerns, can be painful and tedious to implement, I'm afraid.

An example of how this would work is shown in the following code snippets:

3rd Party Library

package org.theirs;
public interface Foo {
    void doSomething();
    void doSomethingExtra();
}

Your code

package org.mine;
public interface Bar {
    void doSomething();
}

public class BarImpl implements Bar{
     public void doSomething( /* implementation */ );
}

public class FooAdapter implements Bar{
    private final Foo adapted;
    public FooAdapter(Foo adapted) {
         this.adapted = adapted;
    }
    public void doSomething() {
        adapted.doSomething(); // delegate to adapted instance
    }
}

public class UsingThoseBars {
    public void doSomethingWithAllThoseBars(Collection<Bar> bars) {
         // each entry in bars could either be a BarImpl or a FooAdapter    
    }
}

You can see from this example that the method doSomethingExtra() is not accessible from your code, since the interface Bar does not specify it.

Other Suggestions

Note there may be useful tricks you could do with class rewriting, such as with AspectJ. I'm assuming you'd rather have the effect you desire at compile-time, in pure Java.

Another suggestion is to have an implementation of Bar which throws UnsupportedOperationException for the methods of Foo that you don't need. Although there is a precedent for this in major Java libraries (e.g. UnmodifiableList in the JDK) I would recommend against this practice in general. However, if the cost of replacing references to their Foo with your new FooAdapter is quite high, it may be a good trade off to use this strategy.

Upvotes: 1

Ryan Gross
Ryan Gross

Reputation: 6515

You could use the Adapter Pattern to accomplish this:

public class FooAdapter implements Bar {
   private Foo wrapped;
   public FooAdapter(Foo foo) {
      wrapped = foo;
   }
   public void op1() {
      foo.op1();
   }
   public string op2(int param) {
      return foo.op2(param);
   }
}

Upvotes: 2

duffymo
duffymo

Reputation: 308938

I'd like to be able to write classes which implement Bar, and then manipulate those along with classes which implement Foo as if they are all Bar.

You can create an abstract Bar that implements Foo. You'll leave off the methods shared by the two and implement the methods that Bar does not share so they throw an exception like UnsupportedOperationException.

But you can't manipulate Foo as if it were Bar; it's the other way 'round. You can't satisfy the Liskov Substitution Principle the way you're proposing.

Upvotes: 3

Nick Veys
Nick Veys

Reputation: 23949

You're probably going to need to use an Aspect to make this happen. It looks like AspectJ has a @DeclareParents that might do what you want.

Upvotes: 2

Related Questions