1 Encapsulating Routes
2 Handling Non Routes
3 Summary

Cleaning up FindRoute: Encapsulation and Types

Kathi Fisler

We left off last class with a version of findRoute that produces the right answer, but with some shortcomings, particularly regarding encapsulation and the representation of non-existent routes. As a reminder, here is the previous version of the code:

  class Node {

    private String cityname;          // name of city at this node

    private LinkedList<Node> getsTo;  // edges from this Node

  

    // produces a route from this node to the to Node, if one exists

    // The route should not include the same city more than once

    LinkedList<Node> findRoute(Node to, LinkedList<Node> visited) {

      // if have tried this node before, there is no route

      if (visited.contains(this)) {

        return new LinkedList<Node>();

      }

      // if this is the node we are searching for,

      //   return a route with one node

      else if (this.equals(to)) {

        LinkedList<Node> newRoute = new LinkedList<Node>();

        newRoute.addFirst(this);

        return newRoute;

      }

      // search for route through the getsTo nodes

      else {

        visited.add(this);

        return this.findRouteEdges(to,visited);

      }

    }

  

    // checks for a route using the edges out of getsTo

    private LinkedList<Node>

      findRouteEdges(Node to, LinkedList<Node> visited) {

        for (Node c : this.getsTo) {

          LinkedList<Node> cRoute = c.findRoute(to,visited);

          if (cRoute.size() > 0) {

            cRoute.addFirst(this);

            return cRoute;

          }

        }

        // no route from children, so return empty list

        return new LinkedList<Node>();

    }

  }

In this lecture, we will transform this code from working code to good, clean OO code.

1 Encapsulating Routes

What would it mean to encapsulate routes in this method? It would mean that we create an interface and a class(es) for routes, then move all the code that uses the specific LinkedList representation of routes into the new classes. If you are asked to do a problem like this, the first step is to identify all the lines of code that need to change once routes become a separate datatype. For this code, we identify the following pieces of code:

In terms of classes and interfaces, we need to add classes for routes and non-routes, as well as an interface that lets findRoute return either of these types. Initially, these classes appear as follows:

  interface IRouteResult {

  }

  

  class NonRoute implements IRouteResult {

    NonRoute(){}

  }

  

  class Route implements IRouteResult {

    private LinkedList<Node> path;

  }

Note that we have used the name IRouteResult rather than IRoute for the interface. The name IRoute would suggest that NonRoutes were a kind of route. They aren’t. They are a result of searching for a route, as the current name captures.

Now, we start migrating the findRoute code to use these new classes. Start by changing the return types on the methods and the locations where we return an empty route.

  class Node {

    private String cityname;          // name of city at this node

    private LinkedList<Node> getsTo;  // edges from this Node

  

    // produces a route from this node to the to Node, if one exists

    // The route should not include the same city more than once

    IRouteResult findRoute(Node to, LinkedList<Node> visited) {

      // if have tried this node before, there is no route

      if (visited.contains(this)) {

        return new NonRoute();

      }

      // if this is the node we are searching for,

      //   return a route with one node

      else if (this.equals(to)) {

        LinkedList<Node> newRoute = new LinkedList<Node>();

        newRoute.addFirst(this);

        return newRoute;

      }

      // search for route through the getsTo nodes

      else {

        visited.add(this);

        return this.findRouteEdges(to,visited);

      }

    }

  

    // checks for a route using the edges out of getsTo

    private IRouteResult

      findRouteEdges(Node to, LinkedList<Node> visited) {

        for (Node c : this.getsTo) {

          IRouteResult cRoute = c.findRoute(to,visited);

          if (cRoute.size() > 0) {

            cRoute.addFirst(this);

            return cRoute;

          }

        }

        // no route from children, so return empty list

        return new NonRoute();

    }

  }

Next, we have to fix the creation of a new route in the case that this.equals(to). Since a valid route requires at least one node, we should write the Route constructor to take a single Node, with which it initializes the list inside the route:

  class Route implements IRouteResult {

    private LinkedList<Node> path;

  

    Route(Node initnode) {

      this.path = new LinkedList<Node>();

      this.path.add(initnode);

    }

  }

The else if case in findRoute accordingly appears as follows:

    IRouteResult findRoute(Node to, LinkedList<Node> visited) {

      // if this is the node we are searching for,

      //   return a route with one node

      else if (this.equals(to)) {

        return new Route(this);

      }

  }

Finally, we have to fix findRouteEdges to not perform the size computation (which assumes the LinkedList). The size check is asking whether we have a Route, as opposed to a NonRoute. That suggests that a call to instanceof would do the trick. In addition, we need to add a method to the Route class for adding a Node to a Route. The code is as follows:

  // checks for a route using the edges out of getsTo

  private IRouteResult

    findRouteEdges(Node to, LinkedList<Node> visited) {

      for (Node c : this.getsTo) {

        IRouteResult cRoute = c.findRoute(to,visited);

        if (cRoute instanceof Route) {

          cRoute.addFront(this);

          return cRoute;

        }

      }

      // no route from children, so return empty list

      return new NonRoute();

  }

}

The addFront method must be added to the Node class:

  class Route implements IRouteResult, ExtendableRoute {

    private LinkedList<Node> path;

  

    Route(Node initnode) {

      this.path = new LinkedList<Node>();

      this.path.add(initnode);

    }

  

    public Route addFront(Node newnode) {

      this.path.addFirst(newnode);

      return this;

    }

  }

With this, routes are cleanly encapsulated in their own data structure. The fact that routes are based on LinkedList is hidden from the Node class (as encapsulation would demand).

Of course, we still have a use of instanceof in the code, which we know shouldn’t be there ...

2 Handling NonRoutes

Getting rid of the instanceof is tricky. Previously, we eliminated instanceof by adding a method to each class that could correspond to the object whose type we are checking. Here, that would mean adding an addFront method to NonRoute. We could do it technically: just have addFront in NonRoute return a NonRoute. But this isn’t good style. Classes should only have methods that make sense for those methods. An addFront method makes no sense in a NonRoute class, and hence shouldn’t be there.

The alternative, and current conventional wisdom about effective Java programming, is to report NonRoute via exceptions. This is a different use of exceptions from what we have seen before. A NonRoute is not an unusual or unexpected answer: it is an entirely reasonable result from this method. However, it is the answer that requires special handling (the one that the if statement doesn’t know how to handle). This justifies the use of an exception here.

Concretely, we replace the NonRoute class with a NoRouteExn exception. Rather than return a NonRoute, we throw a NoRouteExn object. The code is as follows:

  class Node {

    private String cityname;          // name of city at this node

    private LinkedList<Node> getsTo;  // edges from this Node

  

    // produces a route from this node to the to Node, if one exists

    // The route should not include the same city more than once

    IRouteResult findRoute(Node to, LinkedList<Node> visited)

    throws NoRouteExn {

      // if have tried this node before, there is no route

      if (visited.contains(this)) {

        throw new NoRouteExn();

      }

      // if this is the node we are searching for,

      //   return a route with one node

      else if (this.equals(to)) {

        return new Route(this);

      }

      // search for route through the getsTo nodes

      else {

        visited.add(this);

        return this.findRouteEdges(to,visited);

      }

    }

  

    // checks for a route using the edges out of getsTo

    private IRouteResult

      findRouteEdges(Node to, LinkedList<Node> visited)

      throws NoRouteExn {

        for (Node c : this.getsTo) {

          try {

            return c.findRoute(to,visited).addFront(this);

          } catch (NoRouteExn) {

          }

        }

        // no route from children, so throw non-route exception

        throw new NoRouteExn();

    }

  }

The interesting part of this code lies in the try-catch block in findRouteEdges. We try to find a route from one of the edges. If it succeeds, we add this node onto the route and return it. If it fails, a NoRouteExn will be caught. We don’t really want to handle this exception though: we simply want the for loop to continue. The catch statement therefore does nothing, which allows the for loop to resume normally.

3 Summary

The encapsulated and exception-based code presents a clean OO solution to this problem. Everyone in the class should be able to handle the encapsulation section. This use of exceptions is a bit subtle for those new to Java. It’s here for your education only, not something that we will test on the exam.