2

Ive done research on this pattern, and Im still not sure why it is 100% better than an alternative which I will describe shortly. I will discuss two ways to implement streams. As far as I am concerned, the decorator pattern and my approach are two sides of the same coin.

I wont describe the decorator pattern here since I will assume that people interested in this answer are already familiar with it.

The alternative is to use lists. Lists are inherently ordered, and seem to do the same thing. Lets say we have an output stream that we wrap in an encryption stream which we wrap in a zip stream.

OutputStream stream = new ZipStream(new EncryptStream(new OutputStream(outputLocation)));
stream.write(data);

The alternative to would be to create an output stream, and add a zipper and encrypter to it.

OutputStream stream = new OutputStream(outputLocation);
stream.add(new EncyrptStream());
stream.add(new ZipStream());
stream.write(data);

The code for OutputStream.write would look something like:

public void write(String data) {
  for(StreamDecorator d : decorators) {
    data = d.doMyThing(data);
  }
  //continue normal write operation
}

am I correct in saying these two approaches are two sides of the same coin? If not, where is the decorator pattern more useful than this approach?

yemista
  • 433
  • 5
  • 15
  • Decorators wrap a single object. Your second example seems like an implementation of the Composite pattern. See http://stackoverflow.com/q/2233952/1168342 – Fuhrmanator Dec 10 '15 at 17:14

5 Answers5

3

It's roughly the same to me. In your list pattern, you'll end up calling :

zipStream.doMyThing(encryptStream.doMyThing(outputStream.doMyThing(data))));

The difference is just that you'll be using a specific method rather than constructors, which might be better in some situations.

oailloud
  • 363
  • 1
  • 2
  • 7
3

Your example bypasses a lot of things. If you look at Java's streams, you can see that each of them has a single responsibility. FileOutputStream knows how to write bytes to file, SocketOutputStream knows how to write bytes to socket, etc. They could be used by themselves or with other streams, but they're "terminating", i.e. the bytes are gone outside of your handling.

Your Outputstream.write doesn't describe what the "normal write operation" is, but with your list approach, all the "terminating" streams would need to have a list of StreamDecorators and the same code to iterate them and process the data.

While it's not at all impossible to do this with a list (therefore becoming Chain Of Responsibility), it's a lot simpler to do it as a decorator, since the class needs to do only a single thing.

Kayaman
  • 72,141
  • 5
  • 83
  • 121
1

Kayaman mentioned single responsibility, but it's more than that to me.

Decorators nest, which means that at a given level of nesting, there is encapsulation (and subsequently information hiding). This means that code is more resistant to changes, etc.

It might help to look at the coupling in the two designs:

UML Object diagram of Stream Decorator

UML Object diagram of alternative with lists

In the Decorator example, the coupling only goes to a single component (the object that the stream wraps). The goal of the decorator is to enhance the object it wraps. It has no idea what that object is, apart from it respecting the type of objects it can wrap (e.g., OutputStream).

In the list (with add()) example, there is more coupling. The stream object needs to know more (and could have more reasons to change, re Single Responsibility). Stream has to worry about failures of any/all of the things it aggregates.

I think if you try to implement your alternative all the way, you'll see that it's not as clean as Decorator. As suggested by another answer, you need

zipStream.doMyThing(encryptStream.doMyThing(outputStream.doMyThing(data))));

which I don't think can be easily coded generally (for an arbitrary number of decorators add()ed to your Stream).

Another point involves types and nesting. Since types/subtypes can be used in the Decorator nesting process, it should prevent illogical nesting. It's not clear that add() respects these typing rules, or it would have to be more restricted.

If you start hard-coding add() rules, you wind up with combined class functions of decorator (see the counter example in https://stackoverflow.com/a/6366543/1168342).

Community
  • 1
  • 1
Fuhrmanator
  • 11,459
  • 6
  • 62
  • 111
0

Your first alternative is not a "list," it is nested constructors.

Anyways, I think that first alternative makes more sense because you want to encrypt the stream, then zip that.

The second alternative looks like you are adding two streams to a single stream. Where would the data be written in that case? I would think the original OutputSteam.

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
0

From a functional point of view, as others say, it isn't that different.

But, the Decorator Pattern is better because it is a known thing. So when you describe your design or document it for others, you can say "and I used the decorator pattern here". This is much more concise and easier for them to validate than "and I used a List here, where I add the streams to it that I want to get called, and I call them in this order. This allows me to blah blah blah..."

All of the concepts behind the patterns have been around for decades; by naming them, we can more efficiently communicate about the concepts and our intent.

Martin Serrano
  • 3,727
  • 1
  • 35
  • 48