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:
Create subclasses
S0C
,S1C
,S2C
andS3C
fromS0
,S1
,S2
andS3
respectively, each with a copy of the code that checks for convergence:- Pros:
- conceptually straight-forward
- the resulting objects still belong to the superclass
- the subclass source code only contains the additional convergence-checking code
- Cons:
- lots and lots of code duplication - with the resulting change synchronization overhead in the future
- Major cons:
- what if I want another set of classes that e.g. pre-process the samples? We are talking about exponential replication of the same code!
- Pros:
Use the Decorator pattern:
- Pros:
- No code duplication!
- Cons:
- The objects no longer belong to the original class (easily worked around)
- A very slight (it exis开发者_开发技巧ts! I measured it!) performance hit in Java, due to the use of virtual method calls, as opposed to special method invocations. It's not very important, but it is still noticeable.
- Major cons:
- About a zillion of delegate methods that must be kept in sync with the wrapped object interface. Using interfaces does ensure that no method is missed, but it's still hard to maintain, even with IDEs that automate generating delegate methods.
- To have a properly implemented decorator pattern, all decorators and wrapped classes need to implement exactly the same interface. This essentially means that I would have to add e.g. the convergence checking methods to the
S
interface, which completely destroys any sense of modularity. The only way for this requirement to be lifted, would be to forbid nested decorators in my code.
- Pros:
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:
I do not need runtime object composition. What I want is to extend the capabilities of the
S*
classes with new methods. If I could create subclasses as needed without code duplication, I'd probably do it that way. If I could do it in the location of use (unlikely), even better.I'd rather not write the same code over and over again. Note: delegate methods and constructors are fine, I suppose, methods implementing algorithms are not.
I'd like to keep my interfaces modular. This is my main issue with the Decorator pattern - unless very specific nesting constraints are placed, you end up with a super-interface of all interfaces...
EDIT 2:
To address a few of the comments:
The
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.A convergence checking decorator would be 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 ...; } }
The problem: If I want to combine another separator behavior besides convergence checking, I need a separate decorator e.g.
NB
- and in order to have access to e.g. thehasConverged()
method, the new decorator needs to:- Implement the same interface as
CC
- Use the same interface as
CC
for its wrapped object type... - ...which forces me to use that interface for the
S*
methods if I want to be able to useNB
withS*
objects without usingCC
- Implement the same interface as
My selection of the Decorator patter was only for lack of a better alternative. It's just the cleanest solution I have found thus far.
When extending the
S*
classes, I still need the originals intact. Putting e.g. the convergence functionality in a common super-class would mean that the associated behavior (and its performance impact) would now exist in all subclasses, which is definitely not what I want.
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 :)
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;
}
精彩评论