Наследование 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().