Альтернатива для шаблона декоратора в 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;
}