Наследование Java, ООП и АОП

На самом деле это тема полу-вопроса, полу-обсуждения. Я думаю, что в Java нужна особенность, чтобы метод (скажем, "calcTotalX") мог быть определен с помощью аннотации в классе (например, ParallelExecuter), который будет выполняться перед Start/AfterEnd другого метода (например, doJob) в том же классе. Так что мы можем быть уверены, что любой класс (скажем, SortingParallelExecuter), расширяющий ParallelExecuter и переопределяющий его метод doJob, не должен знать о X, рискуя забывать обработку X, обрабатывать события операций над X и т. Д.

Мой вопрос заключается в том, есть ли что-нибудь в Java, что я могу сделать так, кроме АОП. Я не выбираю АОП, потому что это делает код настолько распространенным и трудным для чтения. Также проблема здесь специфична для класса / метода / атрибута. Таким образом, репликация поведения не требуется для других классов / методов / атрибутов.

Кстати, если вы считаете это разумным, пожалуйста, проголосуйте за ветку. Thnx

Хорошо, для того, чтобы быть конкретным, я добавляю пример класса, который я использую для деления и паралелизации.

public abstract class PartitionedParallelExecutor<T> {

private ExecutorService executorService;

private final List<PartitionErrorDesc<T>> errorMap     = new ArrayList<PartitionErrorDesc<T>>();
private final AtomicInteger totalExecutedJobCount      = new AtomicInteger();

private boolean shutdownForced = false;


private final int workerCount;
private final int partitionCount;
protected final List<T> sourceList; 

//Must be implemented via Extender class
protected abstract PartitionErrorDesc<T> doWork(List<T> subList);



public PartitionedParallelExecutor(int workerCount, int partitionCount, List<T> sourceList) {
    super();
    this.workerCount = workerCount;
    this.partitionCount = partitionCount;
    this.sourceList = sourceList;
}



public Object onPerPartitionFail(List<T> subList, PartitionErrorDesc<T> ped){return null;};

public Object onPerPartitionSuccess(List<T> subList){return null;};

public Object onAnyFailDoOnce() {return null;}

public Object onTotalSuccess() {return null;}


public final void  fireAndWait() {

    if(workerCount <= 0 || partitionCount <= 0 ||
            sourceList == null || sourceList.size() == 0){
        throw new IllegalArgumentException();
    }

    ExecutorService executorService = Executors.newFixedThreadPool(workerCount);
    this.executorService = executorService;

    List<List<T>> partitions = partitionList(sourceList, partitionCount);

    for (final List<T> subList : partitions) {

        executorService.execute(new Runnable() {
                                    @Override
                                    public void run() {

                                        PartitionErrorDesc<T> errorDesc = null;

                                        try {
                                            errorDesc = doWork(subList);
                                        } catch (Throwable e) {

                                            errorDesc = new PartitionErrorDesc<T>(subList);
                                            errorDesc.setSuccess(false);
                                            errorDesc.setE(e);
                                            errorDesc.setFailedAtItem(0);
                                        }   

                                        errorMap.add(errorDesc);

                                        if(errorDesc.isSuccess == false) { //failure

                                            onPerPartitionFail(subList, errorDesc);
                                            setShutdownForced(true);

                                            totalExecutedJobCount.addAndGet(errorDesc.getFailedAtItem());
                                            Thread.currentThread().interrupt();
                                            return;
                                        } else { //success
                                            totalExecutedJobCount.addAndGet(subList.size());
                                            onPerPartitionSuccess(subList);
                                        }
                                    }
        });
    }

    executorService.shutdown();

    try {
        executorService.awaitTermination(60, TimeUnit.MINUTES);
    } catch (InterruptedException e) {

        setShutdownForced(true);
        Thread.currentThread().interrupt();
    }

    if (!isShutdownForced()) {
        onTotalSuccess();
    } else {
        onAnyFailDoOnce();
    }
}


private List<List<T>> partitionList(List<T> sourceList , int partitionCount) {
    List<List<T>> partitions = new ArrayList<List<T>>();
    int totalSize = sourceList.size();

    int pageCount = partitionCount;
    int pageSize  = totalSize / pageCount; 
    int remainder = totalSize % (pageSize * pageCount);

    int fromIndex  = 0;
    int toIndex  = 0;
    for(int i = 0;  i < pageCount; i++) {

        fromIndex = toIndex;

        if(toIndex >= totalSize){
            break;
        }

        if ( remainder > i) {
            toIndex = toIndex + pageSize + 1;
        } else {
            toIndex = toIndex + pageSize;
        }

        List<T> subList = sourceList.subList(fromIndex,toIndex);

        partitions.add(subList);
    }

    return partitions;
}

public final void shutdownNow() {
    setShutdownForced(true);
    List<Runnable> runnables = executorService.shutdownNow();

    try {
        if(!executorService.awaitTermination(60,TimeUnit.SECONDS)) {
            LOG.error("pool didnt terminate after 60 seconds in shutdownNow");
        }
    } catch (InterruptedException e) {
        executorService.shutdownNow();
        Thread.currentThread().interrupt();
    }
}

public final boolean isShutdownForced() {
    return shutdownForced;
}

private synchronized void setShutdownForced(boolean shutdownForced) {
    this.shutdownForced = shutdownForced;
}

}

В этом примере программист, который хочет использовать класс выше для выполнения своей работы многопоточным способом, он должен реализовать "doJob()", но вызвать "fireAndWait()". Какой будет более изящный способ - реализовать doJob и вызвать doJob. Остальные вещи, такие как вычисление "totalExecutedJobCount", onPerPartitionFail() должны быть реализованы в виде AOP, перекрестном с методом "doJob". Да, мы можем реализовать эту функциональность в другом классе, и все же любой класс, расширяющий PartitionedParallelExecutor, может также расширить это поведение AOP, насколько я знаю. На данный момент я спрашиваю, почему эти вещи (вычисляя "totalExecutedJobCount", onPerPartitionFail()) должны быть в другом классе. Они связаны с этим классом, его атрибутами и методом. Глядя в объектно-ориентированном виде, они должны несколько методов в одном классе и вызываться всякий раз, когда заканчивается "doJob". Вот в чем вопрос. Надеюсь, теперь все ясно. Спасибо за ваше время.

2 ответа

Решение

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

Ваш метод fireAndWait() на самом деле это шаблонный метод, который является проверенным шаблоном проектирования ООП. Я думаю, что это совершенно нормально для реализации doWork(List<T>) как часть алгоритма, но проинструктировать пользователя (через JavaDoc) не вызывать его самому, а полагаться на то, что он вызывается косвенно. Например, вы часто реализуете Runnable.run() (даже в вашем примере кода!), но не жалуйтесь, что он вызывается не вами, а косвенно через Thread.start() или же ExecutorService.execute(), Разве это не та же самая картина? Почему один "метод Бога" должен делать все?

Если вам не нравится ваш собственный метод с использованием шаблонов, напишите PartitionedParallelExecutorStatisticsAspect (извините за длинное имя) заботиться об этом аспекте (потому что это то, что он есть). Поместите его в тот же пакет, если хотите, чтобы он был рядом с абстрактным классом, и позвольте ему делать свою работу. Бог-классы так же плохи, как и Бог-методы, поэтому использование АОП также является жизнеспособной альтернативой. Выполнение работы (с параллельным разделением) является основной задачей, ведение статистики - вторичной задачей. Я был бы хорошо с обоими подходами, если реализовано чисто.

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

Может быть, вы должны использовать такой код:

public final void doJob() {
    before();
    inside();
    after();
}

    protected void inside() {
}

private void before() {
}

private void after() {
}

Теперь вы не можете перегрузить doJob(), но только внутри метода (), и у вас есть перехватчики: before() и after().

Другие вопросы по тегам