thkala
thkala

Reputation: 86403

An alternative for the Decorator pattern in Java?

Suppose that you have the following hierarchy of statistics-related classes, structured in a manner similar to the Template method pattern:

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
}

Suppose now that we want to extend each of these classes to e.g. check for the convergence of a metric. For my purposes, I do not need to do this extension at runtime. I can think of the following alternatives:

If Java supported multiple inheritance, I would have probably been able to handle this by inheriting from both the statistics and a base convergence-checking (or whatever) class. Alas, Java does not support multiple inheritance (no, interfaces don't count!).

Is there a better way to handle this issue in Java? Perhaps a different design pattern? A more technical solution? Some sort of special ritual dance?

PS: If I misunderstand something, feel free to (gently) point it out...

EDIT:

It seems I need to clarify my goals a bit:

EDIT 2:

To address a few of the comments:

Upvotes: 8

Views: 4502

Answers (2)

Daryl Teo
Daryl Teo

Reputation: 5495

Based on your recent edit.

Decorator isn't suitable for this as you might have realised. This is because what it solves is the augmenting of a single functionality, not the augmentation of a whole class tree.

A possible way this might be accomplished is with strategy instead. Strategy is algorithmically focused; it allows you to decouple behavioral code (Sorry if a little C# slips in here and there)


Sample Class

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

Operations

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 = /* ... */
   }
}

Usage

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

Pros:

  • you can arbitrarily add and remove operations depending on what you need.
  • no extra changes to sample class.
  • sample class is cohesive data structure.
  • modularity: each op is self contained. interface only exposes what is required. Common process for interaction with each op.
  • If you, for whatever reason, need to do this repeatedly, you can store all the ops in an array, and reuse that in a loop. Much cleaner than calling 4-5 methods and storing results.

Cons/Limitations:

  • if your operations require lots of data, then you will have to expose that data to your operations, increasing coupling (I can edit post if you need it). In my example, I simply passed a single sample list. If required, you may have to pass in the entire data structure instead.
  • if you have any operations that are dependent on the result of another operation, this won't work out of the box. (This may be accomplished using Composite instead - a mega Op that is made up of several Ops, the result of which is passed on to the next.)

Hope this fits your requirements :)

Upvotes: 4

Ray Tayek
Ray Tayek

Reputation: 10003

i'm confused. it's not clear why you need the first inheritance tree. something like the code below can can do the work for that:

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

Upvotes: 0

Related Questions