1
\$\begingroup\$

I have a use case of template design pattern with generics as mentioned below. I have AbstractVerificationHandler that provides template for verification business logic which is used by different domain verification handlers e.g UserVerifier depending on the db type and file type POJOs. Can the below code be simplified using simpler constructs or by reducing the scope of generics?

class VerificationJob {
   public void execute() {
     String domainType = "user";
     VerificationHandler verifier = getVerifierInstance(domainType);
     boolean result = verifier.verify();
   }

   VerificationHandler getVerifierInstance() {
     return domainType.equals("user") ? new UserVerifier() : null // or a switch case etc.
   }
}

interface VerificationHandler {
   boolean verify(); // omitted common inputs for verification
}

interface Identify {
   String identity();
}

abstract class AbstractVerificationHandler<F extends Identity, D extends Identity>
   implements VerificationHandler {
   
   boolean verify() {
      List<D> dbRecords = fetchAllDBRecords();
      Map<String, F> fileRecords = identityMap(fetchAllFileRecords());
      for(D record: dbRecords) {
         if(!verify(record, fileRecords.get(record.identity()))) {
            return false;
         }
      }
      return true;
   }

   private Map<String, F> identityMap(List<F> fileRecords) {
      // map creation using F.identity()
   }

   abstract List<D> fetchAllDBRecords();
   abstract List<F> fetchAllFileRecords();
   abstract boolean verify(D dbRec, F fileRec);
}

class UserVerifier extends AbstractVerificationHandler<DBUser, FileUser> {
   List<DBUser> fetchAllDBRecords() {
      // code omitted
   } 

   List<FileUser> fetchAllFileRecords() {
      // code omitted
   }

   boolean verify(DBUser dbuser, FileUser fileuser) {
     // code omitted
     // fields specific comparison with different names etc. 

   }
}

class DBUser implements Identity { 
// code omitted 
}

class FileUser implements Identity { 
// code omitted 
}
\$\endgroup\$
3
  • 1
    \$\begingroup\$ I've been looking at the code for a few minutes and I don't think it works at all. The AbstractVerificationHandler calls the verify(DBUser, FileUser) method but it does not know anything about it because it is defined in the subclass. \$\endgroup\$ Commented Sep 26, 2023 at 5:52
  • 1
    \$\begingroup\$ Anyway, instead of implementing the reusability via inheritance and abstract factory methods, you should strive for using composition: en.wikipedia.org/wiki/Composition_over_inheritance \$\endgroup\$ Commented Sep 26, 2023 at 5:55
  • \$\begingroup\$ @TorbenPutkonen my bad. I have added the missing abstract method. \$\endgroup\$ Commented Sep 26, 2023 at 8:07

1 Answer 1

4
\$\begingroup\$

As I mentioned in the comment, you should strive to using composition instead of inheritance. The AbstractVerificationHandler exists only as an extension point for the user to provide access to the data. Instead of implementing fetching and verifing as abstract methods, use suppliers and predicates. For example:

public class VerificationTask<T extends Identifiable, U extends Identifiable> {
    // Provide these in a constructor.
    final Supplier<Collection<T>> supplierT;
    final Supplier<Collection<U>> supplierU;
    final BiPredicate<T, U> verifier;

    public boolean verify() {
        Collection<T> tRecords = supplierT.get();
        Map<String, U> uRecords = identityMap(supplierU.get());
        for (T record: tRecords) {
            if (!verifier.test(record, uRecords.get(record.identity()))) {
                return false;
            }
        }
        return true;
    }
}

The users are loaded with one purpose suppliers:

public class DBUserLoader implements Supplier<Collection<DBUser>> {
    public Collection<DBUser> get() {
        // Load users from DB.
    }
}

UserVerifier becomes a simple one purpose predicate:

public class UserVerifier extends BiPredicate<DBUser, FileUser> {
    public boolean test(DBUser dbuser, FileUser fileuser) {
        // fields specific comparison with different names etc. 
    }
}

Following this your code doesn't violate the single responsibility principle. In your code the UserVerifier class is responsible for three things: loading data from DB, loading data from file and comparing the entries.

I renamed the Identify interface as Identifiable as the word "identify" describes a command while "identifiable" describes a capability. It also follows the custom used in the Java standard libraries (e.g. Comparable, Iterable).

You should use as generic collection classes as you canget away with. Requiring a List when the code does not require any list specific operations unnecessarily limits the programmer who implements your API. Maybe they are forced to use another API that provides a Set, which would require them to repack their data to a List before handing it over to you.

\$\endgroup\$
2
  • \$\begingroup\$ thanks. I am assuming UserVerifier will still be be supplying two thing 1. loading data from db, loading data from file as part of verification? Also, is it fine to repeat the template in concrete classes e.g EmployeeVerifier will also follow the same. \$\endgroup\$ Commented Sep 26, 2023 at 10:37
  • \$\begingroup\$ @user3239193 I have improved the answer. Hopefully it clears up your question. \$\endgroup\$ Commented Sep 26, 2023 at 10:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.