如何在接口中重载方法?

11

如果我有这个接口

public interface someInterface {
  // method 1
  public String getValue(String arg1);
  // method 2
  public String getValue(String arg1, String arg2);
}

我希望能够在不必为每个实现类都覆盖两个字符串的情况下,将1或2个字符串传递给getValue方法。

public class SomeClass1 impelments someInterface 
{
 @Override
 public String getValue(String arg1);
}

public class SomeClass2 implements someInterface 
{
 @Override
 public String getValue(String arg1, String arg2);
}

这不起作用是因为SomeClass1需要实现方法2,而SomeClass2需要实现方法1。

我是否被困住了?

public interface someInterface2 {
  public String getValue(String... args);
}

public class SomeClass3 implements someInterface2 
{
  @Override
  public String getValue(String... args) {
    if (args.length != 1) {
      throw IllegalArgumentException();
    }
    // code
  }
}

public class SomeClass4 implements someInterface2
{
  @Override
  public String getValue(String... args) {
    if (args.length != 2) {
      throw IllegalArgumentException();
     }
    // code
  }
}

someInterface2 someClass3 = new SomeClass3();
someInterface2 someClass4 = new SomeClass4();
String test1 = someClass3.getValue("String 1");
String test2 = someClass4.getValue("String 1, "String 2");

有没有更好的方法来做这件事?


4
你应该使用两个接口。 - Sotirios Delimanolis
6
如果你传入一个字符串,并且该类只实现了两个参数的方法,或者反过来,你会期望发生什么? - Jon Skeet
1
你实际上想要做什么?你所描述的情况似乎是一个代码设计问题。 - Sam Dufel
2
你的两个类应该有不同的行为,因此它们不应该实现相同的接口(即使它们的方法名称可能相同,但它们的契约并不相同)。像Sotirios建议的那样使用两个接口。 - Vincent van der Weele
1
如果您需要知道您拥有哪个实现,以便正确调用方法,您也可以在每个类中添加正确的方法,并完全从接口中删除它。 - Vincent van der Weele
显示剩余3条评论
5个回答

14

接口作为用户使用该接口的契约:您指定了可用的方法(在所有实现中)以及它们的调用方式。如果接口的两个实现需要不同的方法,则该方法不应该是接口的一部分:

public interface Lookup {
}

public class MapLookup implements Lookup {
    public String getValue(String key) {
        //...
    }
}

public class GuavaLookup implements Lookup {
    public String getValue(String row, String column) {
        // ...
    }
}

在您的程序中,您将知道使用哪个实现,因此您可以简单地调用正确的函数:

public class Program {
    private Lookup lookup = new MapLookup();

    public void printLookup(String key) {
        // I hardcoded lookup to be of type MapLookup, so I can cast:
        System.out.println(((MapLookup)lookup).getValue(key));
    }
}

替代方法

如果你的类 Program 更为通用且使用了依赖注入,那么你可能不知道有哪些实现。此时,我会创建一个新的接口 Key,可以是任何类型的键:

public interface Lookup {
    // ...

    public String getValue(Key key);
}

public interface Key {
}

public MapKey implements Key {
    private String key;
    // ...
}

public GuavaKey implements Key {
    private String row, column;
    // ...
}

您的程序中的依赖注入可能来自于某个工厂实现。由于您无法知道使用了哪种类型的查找,因此需要一个单一的getValue契约。

public interface Factory {
    public Lookup getLookup();
    public Key getKey();
}

public class Program {
    private Lookup lookup;

    public Program(Factory factory) {
        lookup = factory.getLookup();
    }

    public void printLookup(Factory factory) {      
        System.out.println((lookup.getValue(factory.getKey()));
    }
}

谢谢,两个解决方案都很有帮助。 - cvue
难道你不觉得访问者模式违背了这个原则吗? - stdout
@zgulser,你能详细说明一下吗? - Vincent van der Weele
@VincentvanderWeele 我完全误解了你的意思,不好意思浪费了你的时间。 - stdout

12

从Java 8开始,您可以通过使用 default 关键字使接口提供方法的实现。因此,一个新的解决方案是提供两种方法的默认实现,可能会抛出异常,然后从默认接口派生实际实现。

无论如何,以下是您可以执行此操作的方法:

public interface SomeInterface {
    // method 1
    default String getValue(String arg1) {
        // you decide what happens with this default implementation
    }

    // method 2
    default String getValue(String arg1, String arg2) {
        // you decide what happens with this default implementation
    }
}

最后,确保类覆盖了正确的方法。
public class SomeClass1 implements SomeInterface {
    @Override
    public String getValue(String arg1) {
        return arg1;
    }
}

public class SomeClass2 implements SomeInterface {
    @Override
    public String getValue(String arg1, String arg2) {
        return arg1 + " " + arg2;
    }
}

2
一种(并不十分优雅的)解决方案可能是这样的:
public abstract class SomeClass {
   public String getValue(String arg1) {
      throw new IllegalArgumentException();
   }
   public String getValue(String arg1, String arg2) {
      throw new IllegalArgumentException();
   }
}

public class SomeClass1 extends SomeClass {
   public String getValue(String arg1) {
      // return sth
   }
}

public class SomeClass2 extends SomeClass {
   public String getValue(String arg1, String arg2) {
      // return sth
   }
}

然而,这也有一个缺点 - SomeClass1 和 SomeClass2 无法直接继承其他类。

这实际上非常接近我发布的解决方案。就像你能看到未来一样... - smac89
看起来很合理,但是抛出异常的空方法违反了 LSP(里氏替换原则)。 它们几乎像是合同违规行为,并且会让使用您 API 的用户感到惊讶。 尝试添加一个项到从 Arrays.asList 获取的列表中,并感受一下它的感觉。 - Gonen I

0
public interface SomeInterface {
    default void print(String s) {
        System.out.println(s);
    }
}

public class SomeClass implements SomeInterface {
    /**
     * Note the this overloads {@link SomeInterface#print(String)}, 
     * not overrides it!
     */
    public void print(int i) {
        System.out.println(i);
    }
}

0

如果第二个值在某种意义上可以被视为可选的,并且在调用时始终具有2个参数,则可以创建一个包装器类,该类实现了2个参数接口,将1个参数实现作为构造函数参数传递并在方法中调用它,例如:

interface A{
  method1(P1)
}

interface B{
  method2(P1, P2)
}

class Wrap implements B{
  Wrap(A impl)

  override method2(P1, P2){
    call impl.method1(P1)
  }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接