Альтернатива для шаблона декоратора в Java?
предположим, что у вас есть следующая иерархия классов, связанных со статистикой, структурированная аналогично Шаблонный метод шаблон:
interface S {
// Method definitions up-to and including the S3 class
}
class S0 implements S {
// Code that counts samples
}
class S1 extends S0 {
// Code that calls the superclass methods and also computes the mean
}
class S2 extends S1 {
// Code that calls the superclass methods and also computes the variance
}
class S3 extends S2 {
// Code that calls the superclass methods and also computes the skewness
}
предположим теперь, что мы хотим расширить каждый из этих классов, например, проверить на сходимость метрики. Для моих целей мне не нужно делать это расширение во время выполнения. Я могу придумать следующие варианты:
-
создать подклассы
S0C
,S1C
,S2C
иS3C
СS0
,S1
,S2
иS3
соответственно, каждый с копией кода, который проверяет сходимость:- плюсы:
- концептуально прямо вперед
- результирующие объекты по-прежнему принадлежат к суперклассу
- исходный код подкласса содержит только дополнительный код проверки сходимости
- минусы:
- много-много дублирования кода - с результирующие изменения синхронизации накладных расходов в будущем
- главные минусы:
- что делать, если я хочу другой набор классов, например, предварительно обработать образцы? Мы говорим об экспоненциальной репликации одного и того же кода!
- плюсы:
-
использовать шаблон "декоратор":
- плюсы:
- код дублирование!
- минусы:
- объекты больше не принадлежат к исходному классу (легко обрабатываются)
- A очень slight (он существует! Я измерил!) производительность в Java, из-за использования виртуальных вызовов методов, в отличие от специальных вызовов методов. Это не очень важно, но все равно заметно.
- главные минусы:
- около мильона методов делегата необходимо синхронизировать с интерфейсом обернутого объекта. Использование интерфейсов гарантирует, что ни один метод не пропущен, но его все еще трудно поддерживать, даже с IDE, которые автоматизируют создание методов делегата.
- чтобы иметь правильно реализованный шаблон декоратора, все декораторы и обернутые классы должны реализовать точно такой же интерфейс. Это по существу означает, что мне придется добавить, например, методы проверки сходимости к
S
интерфейс, который полностью уничтожает любую чувство модульности. Единственный способ снять это требование - запретить вложенные декораторы в моем коде.
- плюсы:
если бы Java поддерживала множественное наследование, я, вероятно, смог бы справиться с этим, наследовав как от статистики, так и от базового класса проверки сходимости (или что-то еще). Увы, Java не поддерживает множественное наследование (нет, интерфейсы не в счет!).
есть ли лучший способ справиться с эта проблема в Java? Возможно, другой шаблон дизайна? Более техническое решение? Какой-то особый ритуальный танец?
PS: Если я что-то недопонимаю, не стесняйтесь (мягко) указать на это...
EDIT:
кажется, мне нужно немного прояснить свои цели:
мне не нужна композиция объекта времени выполнения. Я хочу расширить возможности
S*
занятия с новыми методами. Если бы я мог создавать подклассы как без дублирования кода, я бы, вероятно, сделать это таким образом. Если бы я мог сделать это в месте использования (маловероятно), еще лучше.я бы предпочел не писать один и тот же код снова и снова. Примечание: методы делегирования и конструкторы прекрасны, я полагаю, методы, реализующие алгоритмы, нет.
я хотел бы сохранить мои интерфейсы модульными. Это моя главная проблема с шаблоном декоратора-если только не очень конкретные ограничения вложенности помещенный, вы заканчиваете с супер-интерфейсом всех интерфейсов...
EDIT 2:
чтобы обратиться к нескольким комментариям:
-
на
S*
занятия построены с использованием методов шаблона:class S0 { int addSample(double x) { ...; } double getMean() { return Double.NaN; } } class S1 extends S0 { int addSample(double x) { super.addSample(x); ...; } double getMean() { return ...; } }
-
мой
S*C
расширенные классы из первого решения будут выглядеть следующим образом:interface S { int addSample(double x); double getMean(); } class S0C extends S0 implements S { int addSample(double x) { super.addSample(x); ...; } boolean hasConverged() { return ...; } } class S1C extends S1 { int addSample(double x) { super.addSample(x); ...; } boolean hasConverged() { return ...; } }
Примечание дублирования
hasConverged()
метод. -
A проверка конвергенции декоратора будет выглядеть так:
class CC<T extends S> implements S { T o = ...; int addSample(double x) { o.addSample(x); ...; } double getMean() { return o.getMean(); } boolean hasConverged() { return ...; } }
проблема: если я хочу объединить другое поведение разделителя помимо проверки сходимости, мне нужен отдельный декоратор, например
NB
- и для того, чтобы иметь доступ к, например,hasConverged()
метод, новый декоратор должен:- реализовать тот же интерфейс, что и
CC
- используйте тот же интерфейс, что и
CC
для своего обернутого типа объекта... - ...что заставляет меня используйте этот интерфейс для
S*
методы, если я хочу иметь возможность использоватьNB
СS*
объекты без использованияCC
- реализовать тот же интерфейс, что и
при продлении
S*
классы, мне все еще нужны оригиналы неповрежденными. Помещение, например, функциональности конвергенции в общий супер-класс означает, что связанное поведение (и его влияние на производительность) теперь будет существовать во всех подклассах, что определенно не чего я хочу.
2 ответов
на основе вашего последнего редактирования.
декоратор не подходит для этого, как вы могли бы понять. Это связано с тем, что он решает увеличение одной функциональности, а не увеличение целого дерева классов.
возможный способ, которым это может быть достигнуто, - это стратегия. Стратегия алгоритмически сфокусирована; она позволяет отделить поведенческий код (Извините, если немного C# проскальзывает здесь и там)
образец Класс!--4-->
public class S {
private List<Integer> Samples = new List<Integer>();
public void addSample(int x){
Samples.Add(new Integer(x));
}
public void Process(IOp[] operations){
for (Op operation : operations){
Process(operation);
}
}
public void Process(ICollection<IOp> operations){
for (Op operation : operations){
Process(operation);
}
}
public void Process(IOp operation){
operation.Compute(this.Samples);
}
}
операции
public interface IOp {
// Interface is optional. Just for flexibility.
public void Compute(List<Integer> data);
}
public class Op<T> implements IOp{
// Generics is also optional. I use this to standardise data type of Result, so that it can be polymorphically accessed.
// You can also put in some checks to make sure Result is initialised before it is accessed.
public T Result;
public void Compute(List<Integer> data);
}
class ComputeMeanOperation extends Op<double>{
public void Compute(List<Integer> data){
/* sum and divide to get mean */
this.Result = /* ... */
}
}
class CheckConvergenceOperation extends Op<boolean>{
public void Compute(List<Integer> data){
/* check convergence */
this.Result = /* ... */
}
}
использование
public static void main(String args[]) {
S s = new S();
s.addSample(1);
/* ... */
ComputeMeanOperation op1 = new ComputeMeanOperation();
CheckConvergenceOperation op2 = new CheckConvergenceOperation ();
// Anonymous Operation
Op<Integer> op3 = new Op<Integer>(){
public void Compute(List<Integer> samples){
this.Result = samples[0]; // Gets first value of samples
}
}
s.Process(op1); // Or use overloaded methods
s.Process(op2);
s.Process(op3);
System.out.println("Op1 result: " + op1.Result);
System.out.println("Op2 result: " + op2.Result);
System.out.println("Op3 result: " + op3.Result);
}
плюсы:
- вы можете произвольно добавлять и удалять операции, в зависимости от того, что вам нужно.
- никаких дополнительных изменений в образец класса.
- образец класса является когезионной структурой данных.
- модульность: каждый op является автономным. интерфейс предоставляет только то, что требуется. Общий процесс взаимодействия с каждым op.
- Если вам по какой-либо причине нужно делать это неоднократно, Вы можете хранить все ops в массиве и повторно использовать его в цикле. Гораздо чище, чем вызов 4-5 методов и сохранение результатов.
Минусы/Ограничения:
- если ваши операции требуют много данных, то вам придется подвергать эти данные вашим операциям, увеличивая сцепление (я могу редактировать сообщение, Если вам это нужно). В моем примере я просто передал один примерный список. Если требуется, возможно, вместо этого вам придется передать всю структуру данных.
- если у вас есть какие-либо операции, зависит от результата другой работы, это не будет работать из коробки. (Это может быть достигнуто с помощью Composite вместо-mega Op, который состоит из нескольких операций, результат которых передается на следующий.)
надеюсь, это соответствует вашим требованиям :)
Я в замешательстве. непонятно, зачем вам нужно первое дерево наследования. что-то вроде кода ниже может сделать работу для этого:
public class Statistics
{
void add(final double x)
{
sum += x;
sum2 += x * x;
sum3 += x * x * x;
n++;
}
double mean()
{
return n != 0 ? sum / n : 0;
}
double variance()
{
return n != 0 ? ( sum2 - sum * sum / n) / (n - 1) : 0;
}
// add method for skewness
double sum, sum2, sum3;
int n;
}