As a Team Leader and sometimes as an Instructor I see a lot of people who thinks programming using Object Oriented Paradigm is using classes and create a hierarchy. They think its about organizing code and not thinking in a Object Oriented way. During the learning path as professional developer in Object Oriented languages you start to observe that’s Object Oriented it’s a good thing but creating good software products using this paradigm its not so easy as thought at first. Why? There are a lot of reasons, but the main ones are always with architecture and ironically organizing the code! Then you start to dig into loosely coupled concepts, patterns, cohesion, etc., just to try to give some predictability, conceptual and physical organization to your application in terms of design, layers and tiers.
Single responsibility principle
A class should have a single responsibility.
This principle its very related with the concept of cohesion. You want a class with only a single responsibility and all the methods and properties are always related with that responsibility. The advantages are obvious, you can ask the right questions all the time and know the answer very quickly! What questions? “In what class its implemented some ?” / “I found a problem related with the Shopping Cart” (guess where to search for the cause)
If you have more than one responsibility for a class, as your application grows so the complexity of the class and at some point you will start to see that it’s a better idea to split the class in two, but probably it will not be an easy task, considering possible dependencies and cross logic inside the methods of the class. It brings you ambiguity which is always a bad thing.
You can detect this kind of problems if you have methods in your class that don’t call any others methods or properties of the class, they are isolated from the rest. This applies to cluster of methods too. This clusters, probably they should be classes by them selves.
All the entities should be open for extension, but closed for modification.
The idea is that you shouldn’t change the behavior of a class when you extends it with other class. The subclass should only extends the behavior not changing it. If you are creating an hierarchy of classes and you change somehow the behavior probably you should create another method instead of overriding one, or you are talking about a class that shouldn’t be a subclass but probably a sibling, because the logic its different.
Consider this example, if you try to create an Object Oriented structure for cats and dogs you will find that almost 95% of the method names and properties are the same, and you will be tempted to create a class that models both. For instance, both will have properties like: number of lets, eyes, color, mouth, head, and methods like: walk, run, eat, play, talk, rest, sleep, .… If you implement for instance the talk method, probably it will be just a change in the MP3 clip. One naive implementation will be to try to create a way to take advantage of one implementation to implement the other, and probably using hierarchy or the same class with some conditional statements. If you try to use subclass you will violate this principle, if you try to use the same class you are removing cohesion from the class, putting more than one responsibility in it. This is just a simple example just to try to show the idea and dangers of not applying this principle. You should have at least 2 classes because they are completely different entities.
Liskov substitution principle
If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering the correctness of the program.
If you implement a subclass its because you are extending the parent one and not changing its behavior (see the Open/Close principle). If you are extending a class the core logic it’s the same, syntactically and in a semantic way you are talking about the same entity, you just improved the previous class. For instance if you create classes for Accounts in a bank you will have a class Account with methods such as: deposit, withdraw and getBalance. You will expect that any subclass will have these methods to do the same thing as this with just a few extensions like an interest rate for special accounts, but the core logic it will always be the same. You will not put the method deposit change others things than the balance, or probably just adding a transaction to a list.
Interface segregation principle
Its better to have several specific Interfaces than a generic one.
This principle is simple to understand. If you have an interface with several methods that are not related between them in some context, it will be very probably that you will implement this interface in class that need only some of the methods, letting some of the methods implementation empty. I have seen a lot of this in some frameworks.
For instance, instead of having an Interface named CustomerOfBank, with methods for manage the customers and his transactions, you should have 2 Interfaces at least: Customer and Account. Now it is easy to change the interface related with customers and the interface related with accounts!
So, its better to have a lot of several tiny interfaces for a specific context than one generic one!
Dependency inversion principle
One should “Depend upon Abstractions. Do not depend upon concretions.
This principle have produced complete books and frameworks but it states that:
– High-level modules should not depend on low-level modules. Both should depend on abstractions.
– Abstractions should not depend upon details. Details should depend upon abstractions.
For instance, the business tier/layer of an application shouldn’t depend about the implementation of a database module. He shouldn’t be dependent of if I will use an Oracle or Sql Server, or Web Services, etc.. (If we try to be very perfectionist and using the Single Responsibility Principle, the business layers shouldn’t be responsible for creating the Database/Integration tier/layer too.)
How is this possible? Using interfaces and others classes with the responsibility to create the instances of the dependent classes (database), and using only interfaces to “coupling”” the layers and tiers.
– IDatabaseRepository (interface with the signature of the methods and public properties related with database)
– DependencyManagerClass – responsible to create instances and managing their lifetime, so the business layer will call something like:
IDatabaseRepository rep = DependencyManagerClass.GetRepository();
PS: When using “real” frameworks probably when creating the Business Layer the repository was injected in the constructor, so this call will be in another class and the business class will only use the already created reference to the repository.
You cant find any dependency in business layer about the DatabaseRepository, and neither any responsibility to create the repository and so the name:dependency inversion, because its not the business class that its creating the repository but the logic was inverted, some other class already created the class and gave it to the business layer. This inversion (plus the interface) removed the dependency and knowledge about the details, place, etc. of the repository. You can now exchange one repository to another with changing anything in the Business Layer! Great! Isn’t it?
There are a lot of others principles, but these five are the most common and “solid” in the object oriented paradigm. If you study more deeply these concepts you will observe how related they are, and so the name SOLID!