Alternative to decorator pattern in Java?

Suppose you have the following hierarchy of statistics-related classes structured according to a template template template :

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, for example, check the convergence of the metric. For my purposes, I do not need to do this extension at runtime. I can think of the following alternatives:

  • Subclass S0C , S1C , S2C and S3C from S0 , S1 , S2 and S3 respectively, each with a copy of the code that checks for convergence:

    • Pros:
      • conceptually straightforward
      • the resulting objects still belong to the superclass
      • subclass source code contains only additional convergence verification code
    • Minuses:
      • lots and lots of code duplication - with consequent costs of changing synchronization in the future
    • The main disadvantages:
      • What if I want a different set of classes, for example, preprocess the samples? We are talking about exponential replication of the same code!
  • Use the Decorator pattern :

    • Pros:
      • No code duplication!
    • Minuses:
      • Objects no longer belong to the source class (easy to process)
      • Very insignificant (it exists! I measured it!) Performance got into Java due to the use of virtual method calls, unlike special method calls. This is not very important, but it is still noticeable.
    • The main disadvantages:
      • About zillion delegate methods that should be kept in sync with the wrapped object's interface. Using interfaces ensures that no method is skipped, but it is still difficult to maintain even with IDEs that automate the generation of delegate methods.
      • In order to have a properly executed decorator template, all decorators and wrapped classes must implement exactly the same interface. This essentially means that I have to add, for example, convergence checking methods to the S interface, which completely destroys any sense of modularity. The only way to raise this requirement is to prohibit embed decorators in my code.

If Java supports multiple inheritance, I would probably be able to handle this by inheriting both the statistics and the base convergence class (or any other). Alas, Java does not support multiple inheritance (no, interfaces are not counted!).

Is there a better way to handle this problem in Java? Perhaps a different design? More technical solution? Any special ritual dance?

PS: If I misunderstand something, do not hesitate (carefully) to indicate this ...

EDIT:

It seems I need to clarify my goals a bit:

  • I don't need runtime composition. I want to expand the capabilities of S* classes with new methods. If I could create subclasses as needed without duplicating code, I would probably do it that way. If I could do it at the place of use (unlikely), even better.

  • I would rather not write the same code again and again. Note: delegate methods and constructors are in order, I suppose, methods that implement the algorithms are not.

  • I want my interfaces to be modular. This is my main problem with the Decorator pattern - if no special nesting restrictions are set, you will get a super-interface of all interfaces ...

EDIT 2:

To address a few comments:

  • S* classes are structured using template methods:

     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 ...; } } 
  • My S*C extended classes from the first solution would be like this:

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

    Note the duplication of the hasConverged() method.

  • The convergence check decorator will look like this:

     class CC<T extends S> implements S { T o = ...; int addSample(double x) { o.addSample(x); ...; } double getMean() { return o.getMean(); } boolean hasConverged() { return ...; } } 

    Problem: if I want to combine separator behavior other than convergence checking, I need a separate decorator, for example. NB - and to access, for example, hasConverged() , the new decorator must:

    • Deploy the same interface as CC
    • Use the same interface as CC for its object with a wrapped object ...
    • ... which forces me to use this interface for S* methods if I want to be able to use NB with S* objects without using CC
  • My choice of Decorator speaker was only due to the lack of a better alternative. This is simply the cleanest solution I have found so far.

  • When extending S* classes, I still need undamaged originals. Entering, for example, convergence functionality in a general superclass would mean that the associated behavior (and its impact on performance) would now exist in all subclasses, which is definitely not what I want.

+6
source share
2 answers

Based on your recent edit.

The decorator is not suitable for this, as you might understand. This is due to the fact that what he decides is an addition to one functionality, and not an addition to a whole tree of classes.

Perhaps this can be achieved with strategy. The strategy is algorithmically focused; it allows you to separate the behavioral code (sorry if a little C # slips here and there)


Class example

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

Using

 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 optionally add and remove operations depending on what you need.
  • no additional changes to the class of samples.
  • The class of the sample is the structure of the whole structure.
  • modularity: each op is autonomous. the interface provides only what is required. The general process of interaction with each operator.
  • If for some reason you need to do this several times, you can save all op in an array and reuse it in a loop. Much cleaner than calling 4-5 methods and saving results.

Cons / Limitations:

  • If your operations require a large amount of data, then you will have to expose this data to your actions, increasing communication (I can edit the message if you need it). In my example, I just passed one list of examples. If necessary, you may need to transfer the entire data structure.
  • If you have any operations that depend on the result of another operation, this will not work. (This can be done using Composite instead - a mega-Op, which consists of several Ops, the result of which is passed to the next.)

Hope this meets your requirements :)

+3
source

I'm confused. it is not clear why you need the first inheritance tree. something like the code below can do for this:

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

Source: https://habr.com/ru/post/898942/


All Articles