Как использовать 2 класса, имеющие одинаковые методы, но без родительского класса?

#java #sonarqube #code-duplication

#java #sonarqube #дублирование кода

Вопрос:

Давайте предположим, что у нас есть 2 класса в Java 8, ClassA и ClassB. ClassA — это класс зависимостей, который никоим образом не может быть изменен. ClassB является своего рода копией ClassA, но с некоторыми изменениями. ClassB не может расширять ClassA, но имеет почти те же функции, что и ClassA.

 public class ClassB {
    public ClassB(...) {}
    public int update(...) {}
    public String query(...) {}
    .
    .
    .
}
  

Таким образом, вышеупомянутая структура аналогично присутствует и в ClassA.

Допустим, я хочу закодировать 2 других класса ClassC, который использует экземпляр ClassA, и ClassD, который использует экземпляр ClassB. ClassC и ClassD имеют точно такой же код, за исключением экземпляров ClassA и ClassB.

 public class ClassC {
    ClassA tmp;
    public ClassC(...) {
        tmp = new ClassA(...);
    }
    public void doSomething(...) {
        tmp.update(...);
        tmp.query(...);
    }
    .
    .
    .
}
  
 public class ClassD {
    ClassB tmp;
    public ClassD(...) {
        tmp = new ClassB(...);
    }
    public void doSomething(...) {
        tmp.update(...);
        tmp.query(...);
    }
    .
    .
    .
}
  

Из примеров видно, что ClassC и ClassD имеют одинаковые функции, но используют другой класс для tmp

Есть ли какой-либо способ избежать дублирования кода? Могу ли я каким-то образом написать большинство функций ClassC и ClassD как общий код, а затем заставить ClassC и ClassD расширить этот общий код?

Попытки решения

Я попробовал способ, который в основном создает класс интерфейса странным образом. Я определяю класс, который создает абстракции функций ClassA и ClassB, а также реализует все функции ClassC и ClassD

 public abstract class ClassE {
    public ClassE(...) {}
    public abstract int update(...);
    public abstract String query(...);
    public void doSomething(...) {
        tmp.update(...);
        tmp.query(...);
    }
    .
    .
    .
}
  

Тогда, по сути, ClassC и ClassD расширяют ClassE

 public class ClassC extends ClassE {
    ClassA tmp;
    public ClassC(...) {
        tmp = new ClassA(...);
    }
    public int update(...) {
        return tmp.update(...);
    }
    public String query(...) {
        return tmp.query()
    }
    .
    .
    .
}
  
 public class ClassD extends ClassE {
    ClassB tmp;
    public ClassD(...) {
        tmp = new ClassB(...);
    }
    public int update(...) {
        return tmp.update(...);
    }
    public String query(...) {
        return tmp.query()
    }
    .
    .
    .
}
  

Это лучший подход к проблеме? Может ли быть более подходящий подход?

Комментарии:

1. Почему нельзя ClassB расширить ClassA ? Первой идеей был бы абстрактный родительский класс для ClassC и ClassD с универсальным экземпляром ( ClassA и ClassB совместимым)

2. Этот код является частью более крупной кодовой базы. ClassA обладает некоторой функциональностью для некоторых конкретных типов приложений и использует определенный аргумент в своем конструкторе, который нельзя использовать для других типов приложений. Этот аргумент не предоставляется ClassB и, следовательно, не может расширять ClassA

Ответ №1:

вы могли бы создать интерфейс, а не абстрактный класс. Универсальный интерфейс. Это было бы следующим:

 public interface class ClassE <G> {
     int update(G attr);
     String query(G attr);
     void doSomething(G attr);
    
 }

  

Вы должны создать класс implements:

 public class  ImplementClassA implements ClassE<ClassA> {

  public  int update(ClassA attr){
  .
  .
  .
  }
  public  String query(ClassA attr){
  .
  .
  .
  }
  public void doSomething(ClassA attr){
  .
  .
  .
  }
  
}  



public class  ImplementClassB implements ClassE<ClassB> {

  public  int update(ClassB attr){
  .
  .
  .
  }
  public  String query(ClassB attr){
  .
  .
  .
  }
  public void doSomething(ClassB attr){
  .
  .
  .
  }
  
}
  

В классе c могут использоваться интерфейсы. В ClassC вы могли бы использовать интерфейсы, чтобы передать экземпляр нужного вам типа, будь то реализация ClassA или ClassB

 public class ClassC <G> {

    private ClassE tmp;


    public ClassC(ClassE tmp) {
        this.tmp = tmp;
    }
    public int update(G attr) {
        return tmp.update(attr);
    }
    public String query(G attr) {
        return tmp.query(attr)
    }
    .
    .
    .

}

  

Пример,:

 ClassC<ClassA> teste = new ClassC<ClassA>(new ImplementClassA());
ClassA save = new ClassA();
teste.update(save);
ClassC<ClassB> teste2 = new ClassC<ClassB>(new ImplementClassB());
ClassB save2 = new ClassB();
teste2.update(save2);
  

Ответ №2:

Здесь на ум приходит то, что дизайн, управляемый доменом, называет антикоррупционным слоем.

Значение: вы создаете абстракцию вокруг частей, от которых хотите отделиться, и ваш код использует только ваши собственные абстракции

В вашем случае:

  • Вы создаете некоторый интерфейс, который содержит функциональность, которую захотят использовать ваши классы C и D
  • вы создаете две реализации этого интерфейса, одна из которых использует объект класса A, другая получает свою функциональность от объекта класса B.

Теперь вашим классам C и D просто нужно передать соответствующие классы impl. Они полагаются только на ваш интерфейс, поэтому вы можете реорганизовать их по своему усмотрению.

И обратите внимание: лучшим решением здесь является создание собственного интерфейса. Ваши классы A и B не имеют никакого отношения «тип». Поэтому обобщения здесь не помогут.

Я думаю, вы, вероятно, надеетесь на то, что называется «утиным вводом». Это концепция из других языков, где вы говорите, что «быть уткой означает: она может крякать () и она может ходить ()», например. Это означало бы: когда два разных класса совместно используют определенные методы, у вас может быть «представление», которое определяет тип, который имеет эти методы, и оба эти класса относятся к этому «типу представления». Но java не поддерживает это (scala, кстати, поддерживает), и дженерики в этом не помогают.

Комментарии:

1. Это подход, который я действительно пытался. Но я не был уверен, что это лучший способ сделать это. Я надеялся на другой способ, возможно, используя дженерики? Пожалуйста, просмотрите вопрос еще раз. Я также добавил свою попытку

Ответ №3:

Вы можете создать класс делегата для ClassA и ClassB:

 public class delegateAB {
  private ClassA a;
  private ClassB b;

  public delegateAB(ClassA a) {
    this.a = a;
  }

  public delegateAB(ClassB b) {
    this.b = b;
  }

  public int update(...) {
    return a!=null?a.update(...):b.update(...)
  }

  // ... other common delegated methods.
}
  

И затем вы можете мгновенно создать этот класс либо с помощью ClassA, либо ClassB, так что с этого момента вы можете использовать класс делегата в своем коде, например, так:

 public class ClassCD {
    DelegateAB tmp;

    public ClassCD(ClassA a, ...) {
        tmp = new DelegateAB(a);
    }

    public ClassCD(ClassB b, ...) {
        tmp = new DelegateAB(b);
    }

    public void doSomething(...) {
        tmp.update(...);
        tmp.query(...);
    }
    .
    .
    .
}
  

Это не очень хорошее решение, но оно делает свое дело.