简体   繁体   中英

Design query - Using Java Generics for code duplication

Problem

I am trying to design two methods that filter a list

public List<TypeA> filter(List<TypeA> typeAList) { 
    //duplicated code
    //filter typeAList using some parameters in typeA objects
    //return filtered list
}

public List<TypeB> filter(List<TypeB> typeBList) { 
    //duplicated code
    //filter typeBList using some parameters in typeB objects 
    //return filtered list
}

The problem is both the methods have duplicate code except for the filtering part where I access different parameters inside TypeA and TypeB.

Things I tried so far

  • I tried making a generic method like this. But this does not support types other than TypeA and TypeB. Encourages someone to call this method with an unintended type.

public <T> List<T> filter(List<T> genericList) {
    //duplicated code 
    if (T instanceOf TypeA) 
        //filtering code for TypeA 
    if (T instanceOf TypeB) 
        //filtering code for TypeB 
    //return filtered list 
}
  • Overload with two methods calling a private generic filter method. I felt this ensures unintended calls to the public method, while still using generics to avoid code duplication.
public List<TypeA> filter(List<TypeA> typeAList) {     
    //call innerFilter(typeAList)
}

public List<TypeB> filter(List<TypeB> typeBList) { 
    //call innerFilter(typeBList)
}

private <T> List<T> innerFilter(List<T> genericList) {
   //duplicated code
    if (T instanceOf TypeA)
        //filtering code for TypeA
    if (T instanceOf TypeB)
        //filtering code for TypeB
    //return filtered list
}
  • Tried to make the two classes implement a common interface and use that interface as the input parameter to my method. But one of the class is third-party and not under my control.

Help needed

I'm really new to design. Want to understand if my reasoning is right behind the approaches. Also looking for suggestions on alternate best approaches to solve this problem. Thanks in advance.

The appropriate structure is not reflective, and does not use instanceof .

public List<TypeA> filter(List<TypeA> typeAList) {     
    innerFilter(typeAList, typeA -> isGoodA(typeA))
}
private boolean isGoodA(TypeA a) { ... }
public List<TypeB> filter(List<TypeB> typeBList) { 
    innerFilter(typeBList, typeB -> isGoodB(typeB))
}
private boolean isGoodB(TypeB a) { ... }

private <T> List<T> innerFilter(List<T> genericList, Predicate<T> pred) {
   //duplicated code
    //filter genericList using pred
    //return filtered list
}

This is exactly the type of problem that the Predicate<T> functional interface was meant to solve.

import java.util.List;
import java.util.function.Predicate;

public class SOQ_20220501
{

   public static void main(String[] args)
   {
   
      record TypeA(int a) {}
   
      record TypeB(boolean b) {}
      
      final List<TypeA> as = List.of(new TypeA(0), new TypeA(1), new TypeA(2), new TypeA(3), new TypeA(4));
      
      final List<TypeB> bs = List.of(new TypeB(true), new TypeB(false));
      
      var whateverA = filter(as, typeA -> typeA.a() % 2 == 1);
      
      System.out.println(whateverA);
   
      var whateverB = filter(bs, typeB -> typeB.b());
      
      System.out.println(whateverB);
   
   }

   public static <T> List<T> filter(List<T> typeAList, Predicate<T> predicate)
   {
   
      return
         typeAList.stream()
            .filter(predicate)
            .toList()
            ;
   
   }

}

Assume you have this 2 TypeX interface without inheritance link and with same methods signature.

interface TypeA {
    String methodFromA();
}

interface TypeB {
    String methodFromB();
}

You could declare an Enum who knows which method has to be called for each TypeX interface.


enum FilterType {
    TYPE_A(TypeA.class){
        @Override
        public <T> void callMethod(T typeX) {
            TypeA typeA = (TypeA) typeX;
            typeA.methodFromA();
        }
    },
    TYPE_B(TypeB.class){
        @Override
        public <T> void callMethod(T typeX) {
            TypeB typeB = (TypeB) typeX;
            typeB.methodFromB();
        }
    };

    Class typeClass;

    FilterType(Class typeClass) {
        this.typeClass = typeClass;
    }

    public static FilterType from(Class<?> typeClass) {
        return Arrays.stream(values())
                .filter(filterType -> filterType.typeClass.equals(typeClass))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("FilterType for class '" + typeClass + "' not exist")),
    }

    public abstract <T> void callMethod(T typeX);
}


Finally, in your filter method, you just have to recover the enum instance with the TypeX class and call the appropriated method on it.

class FilterService<T> {
    // The class type of TypeX interface
    private final Class<T> typeClass;

    public FilterService() {
        // Recover the class of the generic T
        this.typeClass = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass())
                .getActualTypeArguments()[0];
    }

    public List<T> filter(List<T> genericList) {
        FilterType filterType = FilterType.from(typeClass); // Will throw IllegalArgumentException if T class isn't handle
        genericList.forEach(typeX -> filterType.callMethod(typeX));
        //return filtered list
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM