Search code examples
javaspringgenericsdesign-patternsinterface

How to simplify dependency injection of generic interfaces with multiple implementations in a Java application?


I have an interface that looks like this:

public interface InputsValidator<T> {
    List<MessageEnum> validator(T input);
}

I have multiple implementations for example:

@Component
class AddressInputsValidator implements InputsValidator<Address> {

    @Override
    public List<MessageEnum> validator(Address input) {
        List<MessageEnum> messages = new ArrayList<>();
        // Some actions
        return messages;
    }
}

and

@Component
class ContactInputsValidator implements InputsValidator<Contact> {

    @Override
    public List<MessageEnum> validator(Contact input) {
        List<MessageEnum> messages = new ArrayList<>();
        // Some actions
        return messages;
    }
}

Now, in my Spring Boot service, I need to call multiple validators. Today, if I need 10 validators I need to declare all of them:

@Component
@RequiredArgsConstructor
class MyServiceApiImpl extends DelegateHelper implements UserApiDelegate {

    private final InputsValidator<Address> addressInputsValidator;
    private final InputsValidator<Contact> contactInputsValidator;
    ....
}

Is there an elegant way or design pattern to replace all these InputsValidator declarations of each type, with only generic one? For example

@Component
@RequiredArgsConstructor
class MyServiceApiImpl extends DelegateHelper implements UserApiDelegate {

    private final InputsValidator<T> inputsValidator; // Wrong solution
    ....
}

Solution

  • I'm not sure I understand your question fully, but here are a few ideas.

    You can group multiple validators on the same type with a class like this:

    public class MultiInputsValidator<T> implements InputsValidator<T> {
        private final InputsValidator<T>[] validators;
    
        public MultiInputsValidator(InputsValidator<T>... validators) {
            this.validators = validators;
        }
    
        @Override
        public List<MessageEnum> validator(T input) {
            List<MessageEnum> result = new ArrayList<>();
            for (InputsValidator<T> tor: validators) {
                result.addAll(tor.validator(input));
            }
            return result;
        }
    }
    

    You can write a validator class that validates a property of an object as follows:

    public class PropertyValidator<T,F> implements InputsValidator<T> {
        private final Function<T,F> getter;
        private final InputsValidator<F> fieldValidator;
    
        public PropertyValidator(
                Function<T,F> getter, InputsValidator<F> fieldValidator) {
            this.getter = getter;
            this.fieldValidator = fieldValidator;
        }
    
        @Override
        public List<MessageEnum> validator(T input) {
            return fieldValidator.validator(getter.apply(input));
        }
    }
    

    And you can combine the above for a particular class, like say:

    public class Compound {
        private Contact contact;
        private Address address;
    
        public Contact getContact() {
            return contact;
        }
    
        public Address getAddress() {
            return address;
        }
    
        ...
    }
    

    As follows:

    @Component
    public class CompoundInputsValidator extends MultiInputsValidator<Compound> {
        @Autowired
        public CompoundInputsValidator(
                AddressInputsValidator addressValidator,
                ContactInputsValidator contactValidator) {
            super(new PropertyValidator<>(Compound::getAddress,addressValidator),
                    new PropertyValidator<>(Compound::getContact,contactValidator));
        }
        
    }
    

    I hope this will help.