When designing code, you alaways have two options.
1. get it done, in case pretty much any solution will work for you
2. be pedantic and design a solution which exploits the quirks of the language an its ideology (OO languages in this case - the use of polymorphism as a mean to provide the decision)
I am not going to focus on the first of the two, because there is really nothing to be said. If you just wanted to get it to work, you could leave the code as it is.
But what would happend, if you chose to do it the pedantic way and actually solved the problem with design patterns, in the way you wanted it do?
###You could be looking at the following process:
When designing OO code, most of the `if`s which are in a code do not have to be there. Naturally, if you want to compare two scalar types, such as `int`s or `float`s, you are likely to have an `if`, but if you want to change procedures based on configuration, you can use [polymorphism][1] to achieve what you want, move the decisions (the `if`s) from your business logic to a place, where objects are instantiated - to [factories][2].
As of now, your process can go through 4 separate paths:
1. `data` is neither encrypted nor compressed (call nothing, return `data`)
2. `data` is compressed (call `compress(data)` and return it)
3. `data` is encrypted (call `encrypt(data)` and return it)
4. `data` is compressed and encrypted (call `encrypt(compress(data))` and return it)
Just by looking at the 4 paths, you find a problem.
You have one process which calls 3 (theoretically 4, if you count not calling anything as one) different methods that manipulate the data and then returns it. **The methods have different names**, different so called public API (the way through which the methods communicate their behaviour).
Using the [adapter][3] pattern, we can solve the name colision (we can unite the public API) that has occured. Simply said, adapter helps two incompatible interfaces work together. Also, adapter works by defining a new adapter interface, which classes trying to unite their API implement.
> *This is not a concrete language. It is a generic approach, the **any** keyword is there to represent it may be of any type, in a language like C# you can replace it with generics (`<T>`).*
I am going to assume, that right now you can have two classes responsible for compression and encryption.
class Compression
{
Compress(data : any) : any { ... }
}
class Encryption
{
Encrypt(data : any) : any { ... }
}
In an enterprise world, even these specific classes are very likely to be replaced by interfaces, such as the `class` keyword would be replaced with `interface` (should you be dealing with languages like C#, Java and/or PHP) or the `class` keyword would stay, but the `Compress` and `Encrypt` methods would be defined as a [pure virtual][4], should you code in C++.
To make an adapter, we define a common interface.
interface DataProcessing
{
Process(data : any) : any;
}
Then we have to provide implementations of the interface to make it useful.
// when neither encryption nor compression is enabled
class DoNothingAdapter : DataProcessing
{
public Process(data : any) : any
{
return data;
}
}
// when only compression is enabled
class CompressionAdapter : DataProcessing
{
private compression : Compression;
public Process(data : any) : any
{
return this.compression.Compress(data);
}
}
// when only encryption is enabled
class EncryptionAdapter : DataProcessing
{
private encryption : Encryption;
public Process(data : any) : any
{
return this.encryption.Encrypt(data);
}
}
// when both, compression and encryption are enabled
class CompressionEncryptionAdapter : DataProcessing
{
private compression : Compression;
private encryption : Encryption;
public Process(data : any) : any
{
return this.encryption.Encrypt(
this.compression.Compress(data)
);
}
}
By doing this, you end up with 4 classes, each doing something completely different, but each of them providing the same public API. The `Process` method.
In your business logic, where you deal with the none/encryption/compression/both decision, you will design your object to make it depend on the `DataProcessing` interface we designed before.
class DataService
{
private dataProcessing : DataProcessing;
public DataService(dataProcessing : DataProcessing)
{
this.dataProcessing = dataProcessing;
}
}
The process itself could then be as simple as this:
public ComplicatedProcess(data : any) : any
{
data = this.dataProcessing.Process(data);
// ... perhaps work with the data
return data;
}
No more conditionals. The class `DataService` has no idea what will really be done with the data when it is passed to the `dataProcessing` member, and it does not really care about it, it is not its responsibility.
Ideally, you would have unit tests testing the 4 adapter classes you created to make sure they work, you make your test pass. And if they pass, you can be pretty they will work no matter where you call them in your code.
###So doing it this way I will never have `if`s in my code anymore?
No. You are less likely to have conditionals in your business logic, but they still have to be somewhere. The place is your factories.
And this is good. You separate the concerns of creation and actually using the code. If you make your factories reliable (in Java you could even go as far as using something like the [Guice][5] framework by Google), in your business logic you are not worried about chosing the right class to be injected. Because you know your factories work and will deliver what is asked.
###Is it necessary to have all these classes, interfaces, etc.?
This brings us back to the begining.
In OOP, if you choose the path to use polymorphism, really want to use design patterns, want to exploit the features of the language and/or want to follow the everything is an object ideology, then it is. And even then, this example does not even show all the factories you are going to need and if you were to refactor the `Compression` and `Encryption` classes and make them interfaces instead, you have to include their implementations as well.
In the end you end up with hundreds of little classes and interfaces, focused on very specific things. Which is not necessarily bad, but might not be the best solution for you if all you want is to something as simple as adding two numbers.
If you want to get it done and quickly, you can grab [Ixrec's solution][6], who at least managed to eliminate the `else if` and `else` blocks, which, in my opinion, are even a tad worse than a plain `if`.
> <sub>Take into consideration this is **my** way of making good OO
> design. Coding to interfaces rather than implementations, this is how
> I have done it for the past few years and it is the approach I am the
> most comfortable with.</sub>
>
> <sub>I personally like the if-less programming a lot and would much
> more appreciate the longer solution over the 5 lines of code, not only
> but also because it is the way I am designing code and am very
> comfortable reading it.</sub>
---
**Update 2:** There has been a wild discussion about the first version of my solution. Discussion mostly caused by me, for which I apologize.
I decided to edit the answer in a way that it is one of the ways to look at the solution but not the only one. I also removed the decorator part, where I meant facade instead, which I in the end decided to leave out completely, because an adapter is a facade variation.
[1]: https://en.wikipedia.org/wiki/Polymorphism
[2]: https://en.wikipedia.org/wiki/Factory_%28object-oriented_programming%29
[3]: https://en.wikipedia.org/wiki/Adapter_pattern
[4]: https://en.wikipedia.org/wiki/Virtual_function
[5]: https://github.com/google/guice
[6]: http://programmers.stackexchange.com/a/306315/193669