Teaser Image

qnoid

Markos Charatzas - London, UK




/**
     * Emulates a method as described in the {@link Robot} which can
     * be executed.
     * 
     * Any arguments to the method are encapsulated by the implementation.
     */
    public interface RobotMethod
    {
        /**
         * Execute the method on the robot
         * @param robot the robot to execute the method on
         */
        public void execute(Robot robot);
    }

    /**
     * All available {@link RobotMethod}s
     */
    public class RobotMethods
    {
        private static final class LogMethod implements RobotMethod
        {
            private final String message;

            private LogMethod(String message)
            {
                this.message = message;
            }

            @Override
            public void execute(Robot robot) {
                robot.log(this.message);
            }
        }

        private static final class AlertMethod implements RobotMethod
        {
            @Override
            public void execute(Robot robot) {
                robot.alert();
            }
        }

        private static final class MoveMethod implements RobotMethod
        {
            private final int y;
            private final int x;

            private MoveMethod(int y, int x)
            {
                this.y = y;
                this.x = x;
            }

            @Override
            public void execute(Robot robot) {
                robot.move(this.x, this.y);
            }
        }

        public static RobotMethod newMove(int x, int y) {
        return new MoveMethod(y, x);
        }

        public static RobotMethod newAlert() {
        return new AlertMethod();
        }

        public static RobotMethod newMessage(String message) {
        return new LogMethod(message);
        }
    }

    /**
     * Tracks all the methods executed in a {@link Robot}
     */
    public final class Bug implements Robot
    {
        private final List<RobotMethod> history = Lists.newArrayList();
        private final Robot robot;

        /**
         * @param robot the robot to track
         */
        public Bug(Robot robot)
        {
            this.robot = robot;
        }

        @Override
        public void alert()
        {
            this.robot.alert();
            this.history.add( RobotMethods.newAlert() );
        }

        @Override
        public void log(String message)
        {
            this.robot.log(message);
            this.history.add( RobotMethods.newMessage(message) );
        }
        
        @Override
        public void move(int x, int y)
        {
            this.robot.move(x, y);
            this.history.add( RobotMethods.newMove(x, y) );
        }

        /**
         * Executes all the methods in the same order as were called
         * on this robot
         *  
         * @param robot the robot to execute all methods on
         */
        public void playbackOn(Robot robot)
        {
            for (RobotMethod method : this.history) {
                method.execute(robot);
            }
        }        
    }

    /**
     * Let's track some robots
     */
    public class BugTest
    {
        private final RobotTracker robotTracker = 
            new RobotTracker();
        
        private final RobotFactory robotFactory = 
            new RobotFactory(this.robotTracker);
        
        @Test
        public void playbackOn() throws Exception
        {
            final String message = "Hello";
            final int x = 1;
            final int y = 1;
            
            Robot buggedRobot = this.robotFactory.newRobot();
            
            buggedRobot.alert();
            buggedRobot.log(message);
            buggedRobot.move(x, y);
                    
            Bug bug = this.robotTracker.latestBug();
            
            RobotImpl mimic = new RobotImpl();        
            bug.playbackOn(mimic);
            
            Assert.assertEquals(message, mimic.log());
            Assert.assertEquals(x, mimic.x());
            Assert.assertEquals(y, mimic.y());
        }
    }

Imagine that you are a spy and have been assigned the task to infiltrate a robot factory to plant a tracking device which will allow you to place a bug in every robot produced by the factory and record every action it makes. That bug can then be used to replicate all the actions the robot made on another identical robot.

    /**
     * Let's infiltrate the factory
     */
    public class RobotFactory
    {
        private final RobotTracker robotTracker;
    
        RobotFactory(RobotTracker robotTracker)
        {
            this.robotTracker = robotTracker;
        }

        public Robot newRobot()
        {
            Robot robot = new RobotImpl();

            /* bug will be placed and go unnoticed  */         
        return this.robotTracker.wasCreated(robot);
        }
    }

The Robot is described as follows

    public interface Robot
    {
      public void alert();        
      public void log(String message);    
      public void move(int x, int y);
    }

There are a couple of things that need to be taken care off. First is how can you tell when a robot alerts, logs or moves? In other words, how do you trace a method call? The simplest way is to use composition.

    public final class Bug implements Robot
    {
      private final Robot robot;

      /**
       * @param robot the robot to track
       */
      public Bug(Robot robot)
      {
          this.robot = robot;
      }

      @Override
      public void alert()
      {
          /*
           * Alert was just called!
           */
          this.robot.alert();
      }

      /* cont.d */
    }

Notice how the Bug class must implement the Robot interface so that it can perform the same actions as the robot. (It also helps go unnoticed) The second challenge is how do you "save" a method call when a method is not a first class citizen?

For that we need an interface to describe a method that executes on a Robot

    public interface RobotMethod
    {
        public void execute(Robot robot);
    }

The reason there is a need for an interface is that each implementation differs on which method it calls on the Robot yet we want to treat them all the same when executed on a robot.

    public void playbackOn(Robot robot)
    {
        for (RobotMethod method : this.history) {
            method.execute(robot);
        }
    }

Notice how the implementation of the RobotMethod also keeps the parameters needed to execute a specific method.

Finally use a simple List to keep track of all the methods executed in order simply by adding them to the list.

In summary

  • create a class that implements the same interface as your Robot
  • have a reference to the actual robot
  • delegate each method call to the robot
  • on every method call create a new instance from the class describing the method that was executed
  • add that instance to the list

Challenge: Calling developers to write a post showing an implementation on another language (ruby/javascript)

The principle of the command pattern can be used to implement an undo/redo feature but you also need to think of any state changes

Source