Java Generics - 接受不同通用接口的方法

时间:2017-12-04 13:31:37

标签: java generics design-patterns

说我有这段代码:

IOperation<?> parameter1 = null;
ITotallyDifferentOperation<?> parameter2 = null;

switch (OPERATION_TYPE) {
  case TYPE_1:
    parameter1 = new MyOperation<Type1>();
    parameter2 = new MyTotallyDifferentOperation<Type1>();
    break;
  case TYPE_2:
    parameter1 = new MyOperation<Type2>();
    parameter2 = new MyTotallyDifferentOperation<Type2>();
    break;

...

switch (DB_OPERATION_TYPE) {
  case DB_TYPE_1:
    myMethod(parameter1, parameter2);
    break;
  case DB_TYPE_2:
    myOtherMethod(parameter1, parameter2);
    break;

...

接受interfacesMyOperation实施的两个MyTotallyDifferentOperation的方法:

void myMethod(final IOperation<?> operation, final ITotallyDifferentOperation<?> totallyDifferentOperation) {
  operation.processList(totallyDifferentOperation.getList());
}

totallyDifferentOperation.getList()返回的List<T>List<T>接受的operation.processList()类型相同。

此代码显然无法编译。
是否有其他模式可以获得相同的结果,或者可以更正此代码?

根据要求,我发布了更多的方法。不幸的是,我不能再透露,这是一个模型。
我需要这种模式以避免重复代码。

5 个答案:

答案 0 :(得分:4)

没有既定的设计模式可以帮助你。

您的问题出在这些变量的声明中:

switch (OPERATION_TYPE) {
   case TYPE_1:
      typeOne();
   case TYPE_2:
      typeTwo();
}

private void typeOne()
{
    IOperation<Type1> parameter1 = new MyOperation<>();
    ITotallyDifferentOperation<Type1> parameter2 = new MyTotallyDifferentOperation<>();

    doTheChecks(parameter1, parameter2);
    databaseStuff(parameter1, parameter2);
}

private void typeTwo() /*identical to above, except the types*/
{
    IOperation<Type2> parameter1 = new MyOperation<>();
    ITotallyDifferentOperation<Type2> parameter2 = new MyTotallyDifferentOperation<>();

    doTheChecks(parameter1, parameter2);
    databaseStuff(parameter1, parameter2);
}

<T> void doTheChecks(IOperation<T> param1, ITotallyDifferentOperation<T> param2)
{
    ...
}

<T> void databaseStuff(IOperation<T> param1, ITotallyDifferentOperation<T> param2)
{
    switch (DB_OPERATION_TYPE) {
        case DB_TYPE_1:
            myMethod(param1, param2);
            break;
        case DB_TYPE_2:
            myOtherMethod(param1, param2);
            break;
    }
}

通过使用通配符,您实际上是在告诉编译器&#34;我不关心类型&#34;。那不是真的。最后你会关心这种类型。

从根本上说,你的方法试图做太多,而这是导致你出现问题的原因。将其分解为多种方法,它变得更加容易:

---
- hosts: local
  connection: local
  become: yes
  become_user: root
  tasks:
    - name: add docker's key
      apt_key:
        keyserver: hkp://p80.pool.sks-keyservers.net:80
        id: 58118E89F3A912897C070ADBF76221572C52609D

    - name: add deb repo
      file: path=/etc/apt/sources.list.d/docker.list state=touch

    - name: register apt sources
      lineinfile: dest="/etc/apt/sources.list.d/docker.list" line="{{item}}"
      with_items:
      - "deb https://apt.dockerproject.org/repo ubuntu-trusty main"

    - name: install docker-engine
      apt: name=docker-engine state=present update-cache=yes force=yes

答案 1 :(得分:2)

阅读this Java tutorial about generic method parameters。您可以向方法添加类型参数,以确保操作和fullyDifferentOperation适用于相同的类型:

<T> void myMethod(IOperation<T> operation,
                  ITotallyDifferentOperation<T> totallyDifferentOperation)
{
    operation.processList(totallyDifferentOperation.getList());
}

这样您就可以确保两个参数的输入方式与调用者相同:

// compiles
myMethod(new MyOperation<Type1>(), new MyTotallyDifferentOperation<Type1>())
// does not compile
myMethod(new MyOperation<Type1>(), new MyTotallyDifferentOperation<Type2>())

这是最简单的例子。您可以使用上限和下限来使限制更灵活。这可以使您只要兼容类型就可以混合类型(想象一下可以处理任何Number输入的IOperation,以及提供LongDouble,......的不同ITotallyDifferentOperations。 This tutorial解释了这一点。

添加代码后,也许您可​​以查看一下:

Operations<?> parameters = null;

switch (OPERATION_TYPE)
{
    case TYPE_1 :
        parameters = new Operations<>(new MyOperation<Type1>(), 
                                      new MyTotallyDifferentOperation<Type1>());
        break;
}
switch (DB_OPERATION_TYPE)
{
    case DB_TYPE_1 :
        myMethod(parameters);
        break;
}

static <T> void myMethod(final Operations<T> operations)
{
    operations.operation.processList(operations.totallyDifferentOperation.getList());
}

static class Operations<T>
{
    public final IOperation<T>                 operation;
    public final ITotallyDifferentOperation<T> totallyDifferentOperation;

    public Operations(IOperation<T> operation, ITotallyDifferentOperation<T> totallyDifferentOperation)
    {
        this.operation = operation;
        this.totallyDifferentOperation = totallyDifferentOperation;
    }
}

通过将两个操作包装到类型化参数对象中,可以确保它们属于同一类型。这当然意味着你的dbOperations需要单独接受param对象而不是两个参数。

答案 2 :(得分:1)

我假设您的Operation类和接口看起来像这样:

interface IOperation<T> {
    public List<T> processList(List<T> list);
    public List<T> getList();
}

interface ITotallyDifferentOperation<T> {
    public List<T> processList(List<T> list);
    public List<T> getList();
}

class MyOperation<T> implements IOperation<T> {
    public List<T> processList(List<T> list) {return null;}
    public List<T> getList() {return null;}
}

class MyTotallyDifferentOperation<T> implements ITotallyDifferentOperation<T> {
    public List<T> processList(List<T> list) {return null;}
    public List<T> getList() {return null;}
}

你得到的错误是这样的:

void myMethod(final IOperation<?> parameter1,
final ITotallyDifferentOperation<?> parameter2) {
    // COMPILE ERROR: processList() is not applicable for the arguments
    parameter1.processList(parameter2.getList());
}

这个编译错误的原因是myMethod()允许参数1和参数2的类型彼此不同。

最好的选择(在我看来)是不是通配符参数1和参数2,而是确保它们是相同的泛型类型,如下所示:

Operation<T> parameter1 = null;
ITotallyDifferentOperation<T> parameter2 = null;

基于IOperation接口,任何这些操作都需要myMethod()接受具有相同泛型类型的两个参数,如下所示:

void <T> myMethod(
final IOperation<T> operation,
final ITotallyDifferentOperation<T> totallyDifferentOperation) {
    operation.processList(totallyDifferentOperation.getList());
}

<强> BUT

如果您不想执行上述任何操作,则可以将两个IOperation接口processList方法更改为接受通配符,以便将所有输入保留为通配符,如下所示:

interface IOperation<T> {
    public List<T> processList(List<?> list);
    public List<T> getList();
}

interface ITotallyDifferentOperation<T> {
    public List<T> processList(List<?> list);
    public List<T> getList();
}

唯一的问题是你在这些方法中没有任何编译时类型保证,你必须在运行时检查它们......

编辑以回应Malte Hartwig:

  

不通配符参数将无法编译,因为T无法解析。这就是整个问题:你不知道它是哪种类型

您不需要知道它是什么类型,只是它们是相同的类型T。我在这里扩展了我的答案:

// remove wildcards, use a generic method, rather than deciding Type in a switch statment.
<T> void someMethod(final Class<T> klass, final int DB_OPERATION_TYPE) {
    IOperation<T> parameter1 = new MyOperation<T>();
    ITotallyDifferentOperation<T> parameter2 = new MyTotallyDifferentOperation<T>();

// ... OR ...

<T> void someMethod(final int DB_OPERATION_TYPE, IOperation<T> parameter1,
    ITotallyDifferentOperation<T> parameter2) {

    // ... Other conditions to evaluate before calling the myMethod ...

    switch(DB_OPERATION_TYPE) {
        case 1:
            myMethod(parameter1, parameter2);
            break;
        case 2:
            myOtherMethod(parameter1, parameter2);
            break;
    }
}

<T> void myMethod(final IOperation<T> operation, final ITotallyDifferentOperation<T> totallyDifferentOperation) {
    operation.processList(totallyDifferentOperation.getList());
}

<T> void myOtherMethod(final IOperation<T> operation, final ITotallyDifferentOperation<T> totallyDifferentOperation) {
    // something... ?
} 

并调用此方法:

final int DB_OPERATION_TYPE = 1;
someMethod(String.class, DB_OPERATION_TYPE);
// OR
someMethod(DB_OPERATION_TYPE, new MyOperation<String>(), MyTotallyDifferentOperation<String>());
  

至于将方法调用带入交换机并完全抛弃参数:Op澄清说这不是一个选项。

只有在我的回答发布后,才会对此作出回应澄清。我现在已经根据我的回答编辑了它。

答案 3 :(得分:0)

阐述迈克尔回答这是我的想法。 似乎几乎很好;)

switch (operation) {
  case TYPE_1:
    processDbOperation(
      dbOperation, 
      new MyOperation<Type1>(), 
      new MyTotallyDifferentOperation<Type1>());
    break;
  case TYPE_2:
    processDbOperation(
      dbOperation, 
      new MyOperation<Type2>(),
      new MyTotallyDifferentOperation<Type2>());
    break;

...

<T> void processDbOperation(final DbOperation dbOperation, final IOperation<T> operation, final ITotallyDifferentOperation<T> totallyDifferentOperation) {
  switch (dbOperation) {
    case DB_TYPE_1:
      myMethod(parameter1, parameter2);
      break;
    case DB_TYPE_2:
      myOtherMethod(parameter1, parameter2);
      break;
}

...

答案 4 :(得分:0)

如何另外传递描述类型参数的Class对象?那么你基本上就有了非擦除类型。