Проблема проектирования Java: принудительное выполнение последовательности вызовов метода



647   12  

12 ответов:

мы обычно используем секундомер с Apache Commons Секундомер проверьте шаблон, как они предоставили.

IllegalStateException возникает, когда состояние секундомера неверно.

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.

прямо вперед.

во-первых, есть тот факт, что реализация собственного профилировщика Java является пустой тратой времени, так как хорошие из них доступны (возможно, это было намерение за вопросом).

если вы хотите обеспечить правильный порядок метода во время компиляции, вы должны вернуть что-то с каждым методом в цепи:

  1. start() должен вернуть a WatchStopper С помощью метода stop.
  2. затем WatchStopper.stop() должен вернуть a WatchResult С getResult() метод.

внешнее построение этих вспомогательных классов, а также другие способы доступа к их методам должны быть предотвращены, конечно.

с незначительными изменениями в интерфейсе вы можете сделать последовательность методов единственной, которую можно вызвать-даже во время компиляции!

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;
    }
}

использование является простым - каждый метод возвращает другой класс, который имеет только в настоящее время применимые методы. В основном, состояние секундомера инкапсулируется в системе типов.


в комментариях было указано, что даже с помощью данной конструкции, вы можете позвонить stop() два раза. Хотя я считаю, что будь добавленной стоимостью, теоретически можно себя накрутить. Тогда, единственный способ, который я могу придумать, будет что-то вроде этого:

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;
    }
}

это отличается от назначения, опуская stop() метод, но это тоже потенциально хороший дизайн. Тогда все будет зависеть от точных требований...

может быть, он ожидал этой "реконфигурации" и вопрос был вовсе не о последовательности метода:

class StopWatch {

   public static long runWithProfiling(Runnable action) {
      startTime = now;
      action.run();
      return now - startTime;
   }
}

еще раз подумал

задним числом кажется, что они искали выполнить вокруг шаблона. Они обычно используются для выполнения таких вещей, как принудительное закрытие потоков. Это еще более актуально из-за этой строки:

есть ли шаблон проектирования для обработки таких ситуаций?

идея заключается в том, что вы даете вещь, которая делает "выполнение вокруг" какой-то класс, чтобы сделать что-то. Вы, вероятно, будете используйте Runnable но это не обязательно. (Runnable имеет смысл, и вы скоро поймете, почему.) в своем StopWatch класс добавить какой-то метод, как это

public long measureAction(Runnable r) {
    start();
    r.run();
    stop();
    return getTime();
}

вы бы тогда назвали это так

StopWatch stopWatch = new StopWatch();
Runnable r = new Runnable() {
    @Override
    public void run() {
        // Put some tasks here you want to measure.
    }
};
long time = stopWatch.measureAction(r);

это делает его дурака. Вам не нужно беспокоиться о том, чтобы справиться с остановкой перед запуском, или люди забывают позвонить одному, а не другому и т. д. Причина Runnable хороший, потому что

  1. стандартный класс java, не ваш собственный или третья сторона
  2. конечные пользователи могут поставить все, что им нужно в Runnable сделать.

(если вы использовали его для принудительного закрытия потока, то вы можете поместить действия, которые необходимо выполнить с подключением к базе данных внутри, чтобы конечному пользователю не нужно было беспокоиться о том, как открыть и закрыть его, и вы одновременно заставляете их закрыть его правильно.)

если бы вы хотели, вы могли бы сделать некоторые StopWatchWrapper оставить StopWatch без изменений. Вы также можете сделай measureAction(Runnable) не возвращайте время и сделайте getTime() публика вместо этого.

способ вызова Java 8 еще проще

StopWatch stopWatch = new StopWatch();
long time = stopWatch.measureAction(() - > {/* Measure stuff here */});

третья (надеюсь, последняя) мысль: кажется, что интервьюер искал и то, что больше всего поддерживается, бросает исключения на основе состояния (например, если stop() вызывается перед start() или start() после stop()). Это прекрасная практика и на самом деле, в зависимости от методов в StopWatch имея видимость, отличную от частной/защищенной, вероятно, лучше иметь, чем не иметь. Моя единственная проблема с этим заключается в том, что бросать исключения в одиночку не будет исполнение последовательность вызовов метода.

например, рассмотрим это:

class StopWatch {
    boolean started = false;
    boolean stopped = false;

    // ...

    public void start() {
        if (started) {
            throw new IllegalStateException("Already started!");
        }
        started = true;
        // ...
    }

    public void stop() {
        if (!started) {
            throw new IllegalStateException("Not yet started!");
        }
        if (stopped) {
            throw new IllegalStateException("Already stopped!");
        }
        stopped = true;
        // ...
    }

    public long getTime() {
        if (!started) {
            throw new IllegalStateException("Not yet started!");
        }
        if (!stopped) {
            throw new IllegalStateException("Not yet stopped!");
        }
        stopped = true;
        // ...
    }
}

просто потому, что он бросает IllegalStateException не означает, что соблюдается правильная последовательность,это просто означает, что неправильные последовательности запрещены (и я думаю, мы все можем согласиться исключения раздражают, к счастью, это не проверенное исключение).

единственный способ, который я знаю, чтобы действительно обеспечить правильность вызова методов, - это сделать это самостоятельно с помощью шаблона execute around или других предложений, которые делают такие вещи, как return RunningStopWatch и StoppedStopWatch что я предполагаю, что есть только один метод, но это кажется слишком сложным (и OP упомянул, что интерфейс не может быть изменен, по общему признанию, предложение без оболочки, которое я сделал, делает это). Так что к лучшему моих знаний нет исполнение правильный порядок без изменения интерфейса или добавления дополнительных классов.

я думаю, это действительно зависит от того, что люди определяют как "принудительное применение последовательности вызовов метода". Если только исключения выбрасываются, то ниже компилируется

StopWatch stopWatch = new StopWatch();
stopWatch.getTime();
stopWatch.stop();
stopWatch.start();

правда, это не выполнить, но это просто кажется намного проще сдать Runnable и сделать эти методы частными, пусть другой расслабьтесь и справиться с досадными деталями самостоятельно. Тогда нет никакой догадки. С этим классом очевиден порядок, но если было больше методов или имена не были настолько очевидны, это может стать головной болью.


оригинальный ответ

более задним числом редактировать: OP упоминает в комментарии,

" три метода должны оставаться нетронутыми и являются только интерфейсом для программиста. Члены класса и реализация метода может измениться."

так что ниже-это неправильно, потому что он удаляет что-то из интерфейса. (Технически, вы можете реализовать его как пустой метод, но это кажется глупым и слишком запутанным.) Мне нравится этот ответ, если ограничения не было, и это, похоже, еще один способ "доказательства дурака", чтобы сделать это, поэтому я оставлю его.

мне что-то вроде этого кажется хороший.

class StopWatch {

    private final long startTime;

    public StopWatch() {
        startTime = ...
    }

    public long stop() {
        currentTime = ...
        return currentTime - startTime;
    }
}

причина, по которой я считаю, что это хорошо, - это запись во время создания объекта, поэтому ее нельзя забыть или сделать не по порядку (не могу вызвать stop() метод, если он не существует).

один недостаток-вероятно, именование stop(). Сначала я подумал, может быть lap() но это обычно подразумевает перезапуск или какой-то вид (или, по крайней мере, запись с последнего круга/начала). Возможно read() будет лучше? Это имитирует действие смотреть на время на a секундомер. Я выбрал stop() чтобы сохранить его похожим на исходный класс.

единственное, в чем я не уверен на 100%, это как получить время. Честно говоря, это кажется более незначительной деталью. Пока оба ... в приведенном выше коде получить текущее время так же, как это должно быть хорошо.

часто возникает исключение, когда методы не вызываются в правильном порядке. Например, Thread ' s start появится IllegalThreadStateException если звонил дважды.

вы, вероятно, должны были лучше объяснить, как экземпляр будет знать, если методы вызываются в правильном порядке. Это можно сделать путем введения переменной состояния и проверки состояния в начале каждого метода (и обновления его при необходимости).

Я предлагаю что-то вроде:

interface WatchFactory {
    Watch startTimer();
}

interface Watch {
    long stopTimer();
}

Он будет использоваться такой

 Watch watch = watchFactory.startTimer();

 // Do something you want to measure

 long timeSpentInMillis = watch.stopTimer();

вы не можете вызвать ничего в неправильном порядке. И если вы вызываете stopTimer дважды вы получаете значимый результат оба раза (может быть, лучше переименовать его в measure и возвращения фактическое время каждый раз, когда он вызывается)

Это также может быть сделано с лямбдами в Java 8. В этом случае вы передаете свою функцию в StopWatch класс, а потом сказать StopWatch для выполнения этого кода.

Class StopWatch {

    long startTime;
    long stopTime;

    private void start() {// set startTime}
    private void stop() { // set stopTime}
    void execute(Runnable r){
        start();
        r.run();
        stop();
    }
    long getTime() {// return difference}
}

Если ваша цель собрать данные о том, сколько времени будет потраченные на различные вещи, я бы предположил, что вам лучше всего подходит класс, который строит список событий, связанных с синхронизацией. Такой класс может предоставить метод для создания и добавления нового события, связанного с синхронизацией, которое будет записывать моментальный снимок его созданного времени и предоставлять метод для указания его завершения. Внешний класс также предоставляет метод для получения списка всех событий синхронизации, зарегистрированных на сегодняшний день.

Если код, который создает новое мероприятие сроки поставки параметр, указывающий его назначение, код в конце, который исследует список, может определить, были ли все события, которые были инициированы, должным образом завершены, и идентифицировать любые, которые не были; он также может определить, содержались ли какие-либо события полностью в других или перекрывали другие, но не содержались в них. Поскольку каждое событие будет иметь свой собственный независимый статус, неспособность закрыть одно событие не должна мешать любым последующим событиям или вызывать потерю или повреждение времени данные, связанные с ними (как это может произойти, например, если секундомер был случайно оставлен работать, когда он должен был быть остановлен).

хотя, конечно, можно иметь изменяемый класс секундомера, который использует start и stop методы, если намерение состоит в том, чтобы каждое действие "stop" было связано с конкретным действием "start", если действие "start" возвращает объект, который должен быть "остановлен", не только обеспечит такую ассоциацию, но и позволит достичь разумного поведения даже если действие начато и прекращено.

Я знаю, что на это уже был дан ответ, но не смог найти ответ, вызывающий builder с интерфейсы для потока управления так вот мое решение : (Назовите интерфейсы лучше, чем я :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;
    }

}

использование :

StoppingStopWatch sw = StopWatch.createAndStart();
//Do stuff
long time = sw.stop().getTime();

в соответствии с вопросом интервью, похоже, это нравится

Class StopWatch {

    long startTime;
    long stopTime;
    public StopWatch() {
    start();
    }

    void start() {// set startTime}
    void stop() { // set stopTime}
    long getTime() {
stop();
// return difference

}

}

Так что теперь всем пользователям нужно создать объект класса секундомера в начале и getTime () нужно позвонить в конце

для электронной.г

StopWatch stopWatch=new StopWatch();
//do Some stuff
 stopWatch.getTime()

Я собираюсь предложить, что применение последовательности вызовов метода решает неправильную проблему; реальная проблема-это недружественный интерфейс, где пользователь должен знать о состоянии секундомера. Решение состоит в том, чтобы удалить любое требование знать состояние секундомера.

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;
    }

}

Это позволяет просто использовать mark метод с возвращенным результатом и включенным журналированием.

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);

Это дает хороший результат;

первый отметка: [перед петлей 1] в момент времени 1436537674704
Отметка: [после цикла 1] 1037 МС с момента отметки [до цикла 1]
Mark: [после цикла 2] 2008ms с момента mark [после цикла 1]

Comments

    Ничего не найдено.