views:

86

answers:

2

I don't seem to find this in usage scenarios for the visitor pattern (or maybe I don't get it). It's also not hierarchical.

Let's use an authentication example. A UserAuthenticator authenticates credentials given by a user. It returns a result object. The result object contains the result of the authentication: authentication succeeded, not succeeded because username was not found, not succeeded because illegal characters were used etc. Client code may resort to conditionals to handle this. In pseudocode:

AuthResult = Userauthenticator.authenticate(Username, Password)
if AuthResult.isAuthenticated: do something
else if AuthResult.AuthFailedBecauseUsernameNotFound: do something else
else if etc...

Would a visitor pattern fit here? :

Authresult.acceptVisitor(AuthVisitor)

Authresult then calls a method on AuthVisitor depending on the result :

AuthVisitor.handleNotAuthenticatedBecauseUsernameNotFound
+1  A: 

I would not recommend using patterns for intent they were not made for.

The intents of the visitor patterns are:

  • Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
  • The classic technique for recovering lost type information.
  • Do the right thing based on the type of two objects.
  • Double dispatch

This solution would be useful if you had planned to do various authentification methods, but if you plan on only doing one, you'll have to use conditionals anyway.

tomzx
I don't agree with not using what they weren't made for. Something may solve a problem without it being intended to do that. If the visitor pattern solves my problem, in a good way, why shouldn't I use it? The question then becomes: is the solution a good one. Noone having it used this way doesn't mean it is a bad solution, though it may hint in that direction. More important is why it is a good or bad solution.If McGyver took your advice he would be out of job.
koen
Another thing is: the way the authentication is handled is agnostic to the example. I don't see why my example limits UserAuthenticator to only one way of authentication (eg only LDAPUserAuthentication, OpenIdUserAuthentication etc)
koen
+1  A: 

Visitor is a valuable design when your data doesn't change fast as your behaviour. A typical example is with a parse tree:

  • your class hierarchy (your data) is frozen
  • your behaviour varies too much, you don't want to break your classes adding another virtual method

I don't think that a Visitor is a valuable solution here, since each time you add a subclass of AuthResult you break your visitor.

Visitor is about trading encapsulation with double dispatch.

You can try a similar approach:

interface Handler {

    void onUsernameNotFound();

    void onWrongPassword();

    void authOk();
}

interface Authenticator {
    void authenticate(String username, String password, Handler handler);  
}   

class SimpleAuthenticator implements Authetnciator {

    void authenticate(String username, String password, Handler handler) {
        if (username.equals("dfa")) {
            if (password.equals("I'm1337")) {
                handler.authOk();
            } else {
                handler.onWrongPassword();
            }
        } else {
            handler.onUsernameNotFound();
        }
    }
}

some Handler stategies:

class FatalHandler implements Handler {

    void onUsernameNotFound() {
        throw new AuthError("auth failed");
    }

    void onWrongPassword() {
        throw new AuthError("auth failed");
    }

    void authOk() {
        /* do something */
    }   
}

and:

class DebugHandler implements Handler {

    void onUsernameNotFound() {
        System.out.println("wrong username");
    }

    void onWrongPassword() {
        System.out.println("wrong password");
    }

    void authOk() {
        System.out.println("ok");
    }   
}

now you can encapsulate error handling and operatorion in your Handlers that is much less code than Visitor since you don't really need double dispatch here.

dfa
I would use this design. However, one observation: you comment that Visitor is not appropriate because each new subclass of AuthResult requires changes to the visitor. Here the methods on Handler correspond to different outcomes, effectively waht would have been different subclasses of AuthResult. The same coupling pertains ... new possible outcome Handler interface and all implementations much change. Given we have two dimensions of variability, Auth + Handler, how far are we actually from Double Dispatch requriement?
djna
I must be missing something. Why would a Auth subclass fail for visitor? There could be an abstract base class that implements the acceptVisitor.I see how a strategy would work here but in one way a strategy is related to visitor IMHO, except that it doesn't do hierarchies.
koen
@djna: with visitor you duplicated logic since you need one if-elseif chain to build the correct concrete AuthResponse class... then you need another if-elseif chain for dispatching
dfa
@koen: try to write the actual code and let me about your implementation; each time you want to add another "case class" for your authresponse, you must change the dispatching code for your visitable
dfa