简体   繁体   中英

How to avoid empty visit functions in visitor pattern?

I have the following use case. I have a restriction interface that needs to fill its members from dependencies, do the validations. These methods are applicable for all implementations and hence it is fine till now. Some restrictions require some other validation later. In the main function, I want to loop over each of the restriction and call the methods in a general way instead of using instanceOf and then calling. I think this might be a use case of visitor pattern as mentioned here . Now I have the following classes.

interface Restriction() {
    void fillFields();
    void firstRoundValidation();
    void accept(SecondRoundValidationVisitor secondRoundValidationVisitor);
}
class RestrictionBasic implements Restriction {
    Field field;

    // Inject dependencies

    @Override
    void fillFields() {
        // Get field from dependencies
    }

    void firstRoundValidation() {
        // Implement
    }

    @void accept(SecondRoundValidationVisitor secondRoundValidationVisitor) {
        secondRoundValidationVisitor.visitRestrictionBasic(this);
    }
}
class RestrictionAdvanced implements Restriction {

    // Same as above except below function.

    @void accept(SecondRoundValidationVisitor secondRoundValidationVisitor) {
        secondRoundValidationVisitor.visitRestrictionAdvanced(this);
    }
}
interface ValidationVisitor {
    void visitRestriction(RestrictionBasic restrictionBasic);
    void visitRestriction(RestrictionAdvanced restrictionAdvanced);
}
class SecondRoundValidationVisitor implements ValidationVisitor {
    @Override
    void visitRestriction(RestrictionBasic restrictionBasic) {
        // Empty function
    }

    @Override
    void visitRestriction(RestrictionAdvanced restrictionAdvanced) {
        // Perform second level of validation
    }
}
class Main() {
    List<Restriction> restrictionList = new ArrayList();
    ValidationVisitor validationVisitor = new SecondRoundValidationVisitor();
    for (restriction : restrictionList) {
        restriction.accept(validationVisitor)
    }
}

Could you please tell if there is any issue with this approach? There is also another approach where getSecondValidationNeeded() could be added to the interface and based on that, call secondValidation with default value of empty body. But this is not following interface segregation principle. My doubt is how does visitor pattern solve this issue? Even in visitor pattern, there is only one interface and accept is being added in base interface even when only some visitors have non empty visit functions.

Pattern-wise I don't think there is a problem. visitRestrictionBasic is only empty because apparently you don't have second round validation for basic restrictions. This is a business rule, not a flaw of the design. If you later decide that you DO want second round validation for basic restrictions, you know where you can add it.

Apart from that, the whole set up might be overkill. It's usually good to start off simple. But I don't know your complete domain and use case, so cannot judge if this is the case here.

EDIT: To evaluate your approach we should get more context and take a step back to understand the problem. So far what I understand is there are several restriction types which have the following characteristics:

  • each restriction has a fixed groups of dependencies
  • a restriction extracts values from these dependencies into its fields
  • each restriction performs two rounds of validation on these fields
  • the first round validation is implemented in each restriction type
  • the second round validation is also specific per restriction type, but implemented in the form of a separate visitor

The fundamental difference between the first round and second round validation is not clear to me. Both of them have specific validation code for each restriction type if I understand it correctly. If not, and the basic validator is only used in the first round, and the advanced validator only in the second round, then the model could probably be simplified. In that case first round = basic and second round = advanced...

Visitor pattern uses overloading of methods to choose appropriate implementation. It can be seen in a wiki example :

interface CarElementVisitor {
    void visit(Body body);
    void visit(Car car);
    void visit(Engine engine);
    void visit(Wheel wheel);
}

So I would edit interface ValidationVisitor :

interface ValidationVisitor {
    void visitRestrictionBasic(RestrictionBasic restrictionBasic);
    void visitRestrictionAdvanced(RestrictionAdvanced restrictionAdvanced);
}

to this:

public interface ValidationVisitor
{
    void VisitRestriction(RestrictionBasic restrictionBasic);
    
    void VisitRestriction(RestrictionAdvanced restrictionAdvanced);
}

So we have created VisitRestriction() with different overloads.

Why? If you don't know the type of the object? You probably would need to find out the real type of Restriction and then call VisitRestrictionBasic or VisitRestrictionAdvanced . I highly recommend you to read this very nice answer about What's the point of the accept method ?

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