1

I am designing a validation module. It has 100 error codes(i.e. errcd_01, errcd_02,..,errcd_100) to be validated. In input I am getting a specific error code(i.e. errcd_01) out of above 100. Module should perform validation for that specific error code.

I am using factory pattern.

/* Interface */
public interface validateErrCd {
   void check_errcd();
}

/* Concrete classes implementing the same interface */
public class validateErrCd_01 implements validateErrCd {

   @Override
   public void check_errcd() {
      //business logic related to errcd_01
   }
}

public class validateErrCd_02 implements validateErrCd {

   @Override
   public void check_errcd() {
      //business logic related to errcd_02
   }
}
.
.
.
public class validateErrCd_100 implements validateErrCd {

   @Override
   public void check_errcd() {
      //business logic related to errcd_100
   }
}

/* Factory */
public class ErrorValidationFactory {
    
   //use check_errcd method to get object of type shape 
   public validateErrCd getValidation(String errorCode){
      if(errorCode == null){
         return null;
      }     
      if(errorCode.equalsIgnoreCase("errcd_01")){
         return new validateErrCd_01();
         
      } else if(errorCode.equalsIgnoreCase("errcd_02")){
         return new validateErrCd_02();
         
      } ..
       .......
      else if(errorCode.equalsIgnoreCase("errcd_100")){
         return new validateErrCd_100();
      }
      else {
           return null;
      }
   }
}

/* I am using the Factory to get object of concrete class by passing an specific error code to be validated (i.e. "errcd_01"). */
public class FactoryPatternDemo {

   public static void main(String[] args) {
      ErrorValidationFactory errorFactory = new ErrorValidationFactory();

      //get an object of validateErrCd_01 and call its check_errcd method.
      validateErrCd errcd01 = errorFactory.getValidation("errcd_01");

      //call check_errcd method of validateErrCd_01
      errcd01.check_errcd();
   }
} 

Now due to multiple if/else inside Factory class ErrorValidationFactory, I am getting couple of CI/CD errors while performing mvn clean install. e.g. [MethodLength] - checkstyle, Rule:CyclomaticComplexity - PMD.

So is there a way I can replace if/else, switch case kind of decision making inside factory which does not trigger above CI/CD errors in Java?

Note : If possible I would like to avoid reflection

Data_Geek
  • 11
  • 4
  • 1
    Create a `Map` and populate it with entries like `validators.put("errcd_100", new validateErrCd_100())` then `.get()` back your validator by the code. – VLAZ Jun 02 '21 at 18:45
  • @VLAZ on second thought, that wouldn't work because 2 calls with the same key would return the same instance, which may not be what OP needs, judging from the code they've shown (unless, that is, you create the map inside `getValidation`) – Federico klez Culloca Jun 02 '21 at 18:53
  • @FedericoklezCulloca then it's just a `Map(String, Supplier)` with `validators.put("errcd_100", validateErrCd_100::new)` right? – VLAZ Jun 02 '21 at 18:54
  • @VLAZ yep, that's what dan1st answer (now) says :) – Federico klez Culloca Jun 02 '21 at 18:55

1 Answers1

3

You could use a Map:

public class ErrorValidationFactory {
    private Map<String,Supplier<validateErrCd>> creators=new HashMap<>();
    public ErrorValidationFactory(){
        creators.put("errcd_100",validateErrCd_100::new);
        //Same for others
    }
   //use check_errcd method to get object of type shape 
   public validateErrCd getValidation(String errorCode){
        if(errorCode == null){
           return null;
        }
        return creators.getOrDefault(errorCode,()->null);
   }
}

Supplier is a functional interface that contains a method returning an object. SomeClass::new or ()->new SomeClass() means that the constructor of the class will be used for that.

This allows to to create the instances later.

If you want to create the Map only once, you can make it static and populate it in a static initializer.

However, if you really want to dynamically get the constructors, you would need to use reflection.

dan1st
  • 12,568
  • 8
  • 34
  • 67
  • 1
    Since the original code used `equalsIgnoreCase`, the equivalent solution would use `new TreeMap<>(String.CASE_INSENSITIVE_ORDER)` instead of `new HashMap<>()`. Though, it’s debatable whether a case insensitive matching makes sense here. – Holger Jun 03 '21 at 07:59
  • 1
    You can also simplify the lookup to `return creators.getOrDefault(errorCode, () -> null).get();` – Holger Jun 03 '21 at 08:05
  • Thanks @dan1st It worked to avoid Rule:CyclomaticComplexity - PMD. However, as I will have to still put entries for each errcd in `creators.put("errcd_100",validateErrCd_100::new);`, it still gives [MethodLength] - checkstyle failure. Is there a way we can create it via loop if I have all errcd(i.e. 1 to 100 in this case) present in some list? – Data_Geek Jun 03 '21 at 14:22
  • @Holger, we can ignore case sensitivity for this case currently. – Data_Geek Jun 03 '21 at 14:24
  • 2
    @Data_Geek it looks like your concrete classes only exist to implement the `validateErrCd` interface method and carry no (mutable) state. So do you actually need to instantiate them in `getValidation` or would having a singleton per type feasible? If that’s the case, just implement all of them as an `enum` type and you get the possibility to enumerate them (and even looking them up by name) for free. – Holger Jun 03 '21 at 14:27
  • In that case, you would need to use reflection. Or a lot of methods. – dan1st Jun 03 '21 at 14:48