Does Java 8 provide an alternative to the visitor pattern?

后端 未结 3 1883
栀梦
栀梦 2021-02-07 14:22

This popular answer on Stack Overflow has this to say about the difference between functional programming and object-oriented programming:

Object-oriented

3条回答
  •  故里飘歌
    2021-02-07 14:39

    What you're trying to accomplish, while admirable, isn't a good fit for Java in most cases. But before I get into that...

    Java 8 adds default methods to interfaces! You can define default methods based on other methods in the interface. This was already available to abstract classes.

    public interface Animal {
        public void speak();
        public default void jump() {
            speak();
            System.out.println("...but higher!");
        }
    }
    

    But in the end, you're going to have to provide functionality for each type. I don't see a huge difference between adding a new method and creating a visitor class or partial functions. It's just a question of location. Do you want to organize your code by action or object? (functional or object oriented, verb or noun, etc.)

    I suppose the point I'm trying to make is that Java code is organized by 'noun' for reasons that aren't changing any time soon.

    The visitor pattern along with static methods are probably your best bet for organizing things by action. However, I think visitors make the most sense when they don't really depend on the exact type of the object they're visiting. For instance, an Animal visitor might be used to make the animal speak and then jump, because both of those things are supported by all animals. A jump visitor doesn't make as much sense to me because that behavior is inherently specific to each animal.

    Java makes a true "verb" approach a little difficult because it chooses which overloaded method to run based on the compile time type of the arguments (see below and Overloaded method selection based on the parameter's real type). Methods are only dynamically dispatched based on the type of this. That's one of the reasons inheritance is the preferred method of handling these types of situations.

    public class AnimalActions {
        public static void jump(Animal a) {
            a.speak();
            System.out.println("...but higher!");
        }
        public static void jump(Bird b) { ... }
        public static void jump(Cat c) { ... }
        // ...
    }
    // ...
    Animal a = new Cat();
    AnimalActions.jump(a); // this will call AnimalActions.jump(Animal)
                           // because the type of `a` is just Animal at
                           // compile time.
    

    You can get around this by using instanceof and other forms of reflection.

    public class AnimalActions {
        public static void jump(Animal a) {
            if (a instanceof Bird) {
                Bird b = (Bird)a;
                // ...
            } else if (a instanceof Cat) {
                Cat c = (Cat)a;
                // ...
            }
            // ...
        }
    }
    

    But now you're just doing work the JVM was designed to do for you.

    Animal a = new Cat();
    a.jump(); // jumps as a cat should
    

    Java has a few tools that make adding methods to a broad set of classes easier. Namely abstract classes and default interface methods. Java is focused on dispatching methods based on the object invoking the method. If you want to write flexible and performant Java, I think this is one idiom you have to adopt.

    P.S. Because I'm That Guy™ I'm going to bring up Lisp, specifically the Common Lisp Object System (CLOS). It provides multimethods that dispatch based on all arguments. The book Practical Common Lisp even provides an example of how this differs from Java.

提交回复
热议问题