Being able to write better is a achieveament that every programmer want to. SOLID is a great thing to start to and particularly one of the best things to be followed by TDD.
a class should have only a single responsibility (i.e. only one potential change in the software's specification should be able to affect the specification of the class)
We have our class Upload doing too many things at once
- Validates the file
- Move the file
- Renaming the file
- Lack of file object
A better approach to it is to create a new class to validate it, to move it and finally to handle it to a new place, let's see some code
software entities … should be open for extension, but closed for modification.
This principle is followed by many vendors to allow developers extend features without change the original source code. Lets first understand the problem we want to solve. lets imagine we have a class to send data through a webservice and at the time we have only the XML handler to send this but you client requested you to implement the feature to support JSON, what would you do ?
In the code above we have unfortunately only one choice, add a method sendJson, but that would not follow the Open/Close principle. With this action we would had to change the original source code to add a new feature (should be open for extension, but closed for modification). How can we fix this code ?To easily refactor this code accordinly with open/close principle we would first to create a new generic class called Type, this class will abstract the XMl and JSON type, which we can then give to the send date as a parameter. This approach is much cleaner and easier to create a new extension to it, we can extend the Type class and create as many types as we want.
Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program
This principle is connected with inheritance one of the most features used in the Object Oriented Programming. The principle says that the subtype should not alter the result of the program.
In the following example we have a class called Door, inside what we have is a simple method that tell us when the door is open or not.
A simply class and easy to use, then we have our client code, who uses that class
Now let' say we have other door but this time this door is a decorated one.
The code above is valid but if the client diced to use the DecoratedDoor his code will break. We are not following the Liskov principle "Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program".
In other words to fix this problem and follow the Liskov principle we need just provide for the client class the same result that he expects from the Door class, true or false.
Many client-specific interfaces are better than one general-purpose interface.
This I think is the easier one to understand, the quote above is really clear. So let' dive into the source code and get our hands dirty.
The interface above has two methods to our Bird, and we can implement this interface really easy and well, as the code below shows
This kind of generalization works fine and there is just one problem with it, what if I have a bird who doesn't fly or doesn't eat ? We do not have many options though, we have to implement all methods, therefore what we can do to identify when a bird can't fly or eat is thrown an exception.
For many of us its just ok to do this, but the true is that we are implementing a method that we don't need.
Then comes Interface Segregation to save us, "Many client-specific interfaces are better than one general-purpose interface.". To deal with that we simply create a new interface with the method eat.
With this approach we can finally implements the specific interface instead of jus throw exceptions if the class doesn't support the contract. The following code supports both Bird and EatableThis code is more flexible as well, we just implement what we are going to need.
One should “Depend upon Abstractions. Do not depend upon concretions
The following code just clarify the idea of abstraction and how to depend on it instead of a concrete instance.
The following approach allow us to implement as many drivers as we want and give it as a parameter to the run method.
You can complain that we could easy depend directly on Driver, but if we do that we will block the flexibility of the code.