#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(...);
}
.
.
.
}
Это не очень хорошее решение, но оно делает свое дело.