Java Design Issue: Enforce method call sequence

后端 未结 12 561
青春惊慌失措
青春惊慌失措 2021-02-02 05:27

There is a question which was recently asked to me in an interview.

Problem: There is a class meant to profile the execution time of the code. The class

相关标签:
12条回答
  • 2021-02-02 05:46

    I know this already has been answered but couldn't find an answer invoking builder with interfaces for the control flow so here is my solution : (Name the interfaces in a better way than me :p)

    public interface StartingStopWatch {
        StoppingStopWatch start();
    }
    
    public interface StoppingStopWatch {
        ResultStopWatch stop();
    }
    
    public interface ResultStopWatch {
        long getTime();
    }
    
    public class StopWatch implements StartingStopWatch, StoppingStopWatch, ResultStopWatch {
    
        long startTime;
        long stopTime;
    
        private StopWatch() {
            //No instanciation this way
        }
    
        public static StoppingStopWatch createAndStart() {
            return new StopWatch().start();
        }
    
        public static StartingStopWatch create() {
            return new StopWatch();
        }
    
        @Override
        public StoppingStopWatch start() {
            startTime = System.currentTimeMillis();
            return this;
        }
    
        @Override
        public ResultStopWatch stop() {
            stopTime = System.currentTimeMillis();
            return this;
        }
    
        @Override
        public long getTime() {
            return stopTime - startTime;
        }
    
    }
    

    Usage :

    StoppingStopWatch sw = StopWatch.createAndStart();
    //Do stuff
    long time = sw.stop().getTime();
    
    0 讨论(0)
  • 2021-02-02 05:48

    Presumably the reason for using a stopwatch is that the entity that's interested in the time is distinct from the entity that's responsible for starting and stopping the timing intervals. If that is not the case, patterns using immutable objects and allowing code to query a stop watch at any time to see how much time has elapsed to date would likely be better than those using a mutable stopwatch object.

    If your purpose is to capture data about how much time is being spent doing various things, I would suggest that you might be best served by a class which builds a list of timing-related events. Such a class may provide a method to generate and add a new timing-related event, which would record a snapshot of its created time and provide a method to indicate its completion. The outer class would also provide a method to retrieve a list of all timing events registered to date.

    If the code which creates a new timing event supplies a parameter indicating its purpose, code at the end which examines the list could ascertain whether all events that were initiated have been properly completed, and identify any that had not; it could also identify if any events were contained entirely within others or overlapped others but were not contained within them. Because each event would have its own independent status, failure to close one event need not interfere with any subsequent events or cause any loss or corruption of timing data related to them (as might occur if e.g. a stopwatch had been accidentally left running when it should have been stopped).

    While it's certainly possible to have a mutable stopwatch class which uses start and stop methods, if the intention is that each "stop" action be associated with a particular "start" action, having the "start" action return an object which must be "stopped" will not only ensure such association, but it will allow sensible behavior to be achieved even if an action is started and abandoned.

    0 讨论(0)
  • 2021-02-02 05:49

    I suggest something like:

    interface WatchFactory {
        Watch startTimer();
    }
    
    interface Watch {
        long stopTimer();
    }
    

    It will be used like this

     Watch watch = watchFactory.startTimer();
    
     // Do something you want to measure
    
     long timeSpentInMillis = watch.stopTimer();
    

    You can't invoke anything in wrong order. And if you invoke stopTimer twice you get meaningful result both time (maybe it is better rename it to measure and return actual time each time it invoked)

    0 讨论(0)
  • 2021-02-02 05:50

    I'm going to suggest that enforcing the method call sequence is solving the wrong problem; the real problem is a unfriendly interface where the user must be aware of the state of the stopwatch. The solution is to remove any requirement to know the state of the StopWatch.

    public class StopWatch {
    
        private Logger log = Logger.getLogger(StopWatch.class);
    
        private boolean firstMark = true;
        private long lastMarkTime;
        private long thisMarkTime;
        private String lastMarkMsg;
        private String thisMarkMsg;
    
        public TimingResult mark(String msg) {
            lastMarkTime = thisMarkTime;
            thisMarkTime = System.currentTimeMillis();
    
            lastMarkMsg = thisMarkMsg;
            thisMarkMsg = msg;
    
            String timingMsg;
            long elapsed;
            if (firstMark) {
                elapsed = 0;
                timingMsg = "First mark: [" + thisMarkMsg + "] at time " + thisMarkTime;
            } else {
                elapsed = thisMarkTime - lastMarkTime;
                timingMsg = "Mark: [" + thisMarkMsg + "] " + elapsed + "ms since mark [" + lastMarkMsg + "]";
            }
    
            TimingResult result = new TimingResult(timingMsg, elapsed);
            log.debug(result.msg);
            firstMark = false;
            return result;
        }
    
    }
    

    This allows a simple use of the mark method with a result returned and logging included.

    StopWatch stopWatch = new StopWatch();
    
    TimingResult r;
    r = stopWatch.mark("before loop 1");
    System.out.println(r);
    
    for (int i=0; i<100; i++) {
        slowThing();
    }
    
    r = stopWatch.mark("after loop 1");
    System.out.println(r);
    
    for (int i=0; i<100; i++) {
        reallySlowThing();
    }
    
    r = stopWatch.mark("after loop 2");
    System.out.println(r);
    

    This gives the nice result of;

    First mark: [before loop 1] at time 1436537674704
    Mark: [after loop 1] 1037ms since mark [before loop 1]
    Mark: [after loop 2] 2008ms since mark [after loop 1]

    0 讨论(0)
  • 2021-02-02 05:54

    We commonly use StopWatch from Apache Commons StopWatch check the pattern how they've provided.

    IllegalStateException is thrown when the stop watch state is wrong.

    public void stop()
    
    Stop the stopwatch.
    
    This method ends a new timing session, allowing the time to be retrieved.
    
    Throws:
        IllegalStateException - if the StopWatch is not running.
    

    Straight forward.

    0 讨论(0)
  • 2021-02-02 05:56

    With minor changes to the interface, you can make the method sequence the only one that can be called - even at compile time!

    public class Stopwatch {
        public static RunningStopwatch createRunning() {
            return new RunningStopwatch();
        }
    }
    
    public class RunningStopwatch {
        private final long startTime;
    
        RunningStopwatch() {
            startTime = System.nanoTime();
        }
    
        public FinishedStopwatch stop() {
            return new FinishedStopwatch(startTime);
        }
    }
    
    public class FinishedStopwatch {
        private final long elapsedTime;
    
        FinishedStopwatch(long startTime) {
            elapsedTime = System.nanoTime() - startTime;
        }
    
        public long getElapsedNanos() {
            return elapsedTime;
        }
    }
    

    The usage is straightforward - every method returns a different class which only has the currently applicable methods. Basically, the state of the stopwatch is encapsuled in the type system.


    In comments, it was pointed out that even with the above design, you can call stop() twice. While I consider that to be added value, it is theoretically possible to screw oneself over. Then, the only way I can think of would be something like this:

    class Stopwatch {
        public static Stopwatch createRunning() {
            return new Stopwatch();
        }
    
        private final long startTime;
    
        private Stopwatch() {
            startTime = System.nanoTime();
        }
    
        public long getElapsedNanos() {
            return System.nanoTime() - startTime;
        }
    }
    

    That differs from the assignment by omitting the stop() method, but that's potentially good design, too. All would then depend on the precise requirements...

    0 讨论(0)
提交回复
热议问题