Application Design Principles
Audience of the Software Design Topics - Application Architect and developers
What Will You Learn from the Topics
The following concepts will be covered in this topic:
- Stupid Design for bad practice
- Singleton
- Tight Coupling
- Un-testability
- Premature Optimization
- In-descriptive Naming
- Duplication
- Solid Design for good practice
- Single Responsibility Principle
- Open and Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
Let's Drilldown the Basic Concept
Wait a minute!!...why am I talking? No! Today, I'll not discuss the WAIT principle at-all. Software design and implementation should be quick and simple. If it can accomplish all of your requirements for now, then it is good enough to go. Yeah, I'm talking about the "Good Enough" principle. Even if, at the very first if we start and implement something poorly, then it's good enough. That's how we learn it. But what about the future? You should keep your one eye for the future of this system. So, if we practice some stupid stuff, then it's fine. But we should have to improve ourselves for the future.
STUPID Design
Apologize me, if I hurt your feelings; but I did the same thing like you. Anyway, what does the very word STUPID mean? STUPID means:
- Singleton Pattern
- Tight Coupling
- Un-testability
- Premature Optimization
- In-descriptive Naming
- Duplication
Singleton Pattern
You are already familiar with this pattern. It instantiates a single static
object and ensures to provide a global access point.
This is also one of the favorite patterns to many developers. So, I will not use any bad word for it. I just want to say, Singleton pattern itself is not the problem; problem, if you don't know - when, where and how you should use it.
Problems:
- Unit Testing: If you are used to the unit testing, then you know that it is very hard to test and generally, it is not possible to make a mock for it. Because, it is tightly coupled. It hides the dependency in the code of the application.
- Global Instance: It hides the dependency in the code of the application. Many people think that it could be an anti-pattern if you incorrectly implement and use it.
- It violates the Single-Responsibility principle and it controls the object creation and lifecycle by itself.
So if you need a single instance, then use it and make sure it is thread safe. But be careful and don't use it globally all over the place.
Tight Coupling
In the class diagram:
Customer
class needs classes 'CheckingAccount
' and 'SavingAccount
' classes to do its work. - "
Customer
" class can't do its work without classes 'CheckingAccount
' and 'SavingAccount
'.
In the example, CheckingAccount
and SavingAccount
are tightly coupled with the customer
class. This implementation violates the Opened-Closed principles where your classes should be open for extension but closed for modifications. Anyway, forget the principle. Just think that your checking-account class is fixed in this design and if you need another implantation of the checking-account or saving-account, then what should you do?
Finally, we can say that:
- Tight coupled design is very hard to reuse and doesn't allow to extend in the future.
- It is difficult to test. Most of the time, object mocking is not possible for unit testing.
So, the solution is that it should be loosely coupled.
Un-testability
According to the first Principle of the unit testing, we “Test the logic of the class only, nothing else”.
When you are going to test a class, you should not have dependency on database, file, registry, web services, etc. You should be able to test any class in complete "isolation" and it should be designed to support complete "isolation."
According to this principle, mock out all external services and state and remember unit test NEVER uses:
- configuration settings
- a database
- another Application/Service/network I/O or file system
- logging
Let's see the example - suppose, you have a method 'IsValidUser
' to verify the user name and password for login.
Now if you look at the line number 15 and 17, then you will see class 'UserLogIn
' has a dependency to the class 'UserDataAccess
' and it is tightly coupled. Now the problem is you need to test a method 'IsValidUser
' of a class class 'UserLogIn
' and you have to avoid the dependency. Because if you call 'IsValidUser
', then it will call the method 'GetUserInfoByUseeName(userName)
' and according to the unit test principle, if we have a dependency to a method of another class, then we have to mock that object class. It means that we have to inject some dummy data to that object. In this example, we need to inject dummy data for the GetUserInfoByUseName(userName)
. If we do so, then we will able to verify the logic of the method 'IsValidUser
' in the 'UserLogIn
' class.
So, to avoid the tight coupling code, we need to refactor the code for loose coupling.
Premature Optimization
Say, we are imagining that we are incrementing a counter value. To increment the value, we can use either pre-increment (++counter) or post-increment (counter++). Say, pre-increment is more efficient then post-increment. So, which one should we use. If we use the wrong one, then what happened?
Optimization has different labels from very high level to the low levels. Choosing good architecture for the application, abstraction of each layer using loose coupling. It also depends on choosing the right data structure and correct algorithm. Efficient coding and many more stuff are connected with it. So, I am avoiding it now to make it easy.
In-descriptive Naming
More or less, we all know about the clean code, naming convention and best practices. It can start from the solution name of your project to name of your component, class, method, attributes, property, variables and so on. Are you writing code for yourself only? If you are not there and in future another one comes, then how will he/she understand your code and design?
For example, I have a class and its name is DaEmp
. So, how do you know what it means? I'm sure, nothing. Because, you don't like to guess only.
But say, I mean that this class will be used for data accessing from employer
table of the database. So, if I re-name it like DataAccessForEmployer
, then it will make sense at-least and you don't need to ask me.
Again suppose, I have an int
variable c
then how do you know what it is for? But I mean counter. So, keep the variable name int
counter. Avoid the abbreviation like emp
. What's the problem if you write employer
instead of "emp
". Don't write code for yourself only, write code for another people and keep it readable. Apologies!
Duplication
In the below example, we are repeating some properties. Not only in your model classes but also it could happen to your methods or anywhere in the classes or in the project.
In this particular scenario, the solution will be - say, we could create a base class for the Address
and inherit it into your model classes like GeneralClient
and CorporateClient
classes. Note that if you use only Id
in your class, then best practice is that you use ClassName+Id
instead only ID
. I mean if class name is Address
, Employee
then use AddressId
, EmployeeId
instead of only Id
.
Always better to remember - "Don't Repeat Yourself" principle.
Solid Design
It's time to wash out the dust from the design. Yes, if you want to be a good architect, then you should have to follow some design pattern and principles. It could be your own pattern and principles, does not matter. You have to make sure that your design is flexible like:
- Easy to change
- Hard to break
- Easy to reuse
- Easy to extend
- Easy to read and understand
- Very simple, neat and clean design
For class designing purpose, now I will discuss only SOLID principles. What does the very word SOLID mean?
So, SOLID means:
- Single Responsibility Principle
- Open-Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
Single Responsibility Principle
A class should have only one reason to change.
I know you understand what it means. But I am not clear; so follow me and confirm whether I am right or wrong? Let's take a look at the example:
In the class diagram, I'm pointing to the SwitchForFanLight
class. It has two dependencies, light
and fan
.
Intentionally, I picked up this confusing example to give you an idea about the design. It is in the middle of the good and bad design. In this design scenarios, Fan
and Light
are loosely-coupled with the SwitchForFanLight
class. Do you want to see how? Alright, let's see the implementation of the SwitchForFanLight
class below and technically, it has no problem. We can inject any of the implantation of the ISwitch interface
.
Again, if you see the implementation of the Fan
and Light
class, then we are good for that.
Okay, no more drama! Come to the main point, I have one switch and if I turn on the switch, then it turns on both of the fan and light. Similarly, if I turn off the switch, then it turns off both of the light and fan. I mean one switch for both fan and light. I am saving my time and cost. One switch for both of them. I am a good engineer. How funny, huh!
But I did not realize that one day I would feel too cold and I wouldn't need fan; but I need light. Now what? If I turn off the fan, then I have no light. Another day, it was sun shine outside and it brightens my room. So, I didn't need light and I turned off the light and it stopped the fan too. But I need fan because of the temperature. It was too hot.
Realization
Now I am in problem with this design. Because one switch has two responsibilities at the same time. If we turn on the switch, then one method is called from the fan
object and another one is called from the light
. Again, if anything happens to the light
class, say, implementation bug or compile time bug, then both light
and fan
will be effected. So, I have violated the single-responsibility principle in the SwitchForFanLight
class.
So, now there we go. I'm bound to change my implementation. Now I have speared the switch and that is one switch for fan and another switch for light. Cool huh!
I have learned that one class should have only one responsibility and it should have only one reason to change. By the way, it is called 'Single Responsibility' principle.
Open-Closed Principle
It states that "Modules and Methods should be open for extension but closed for modifications."
Wait-Principle -
Disclaimer: I'm a lazy developer and always prefer short-cut way. So, I want to use my previous example. That's how, I don't need to implement another project as well as don't need to explain again. I don't want to keep pressure on your brain. Alright, now we are good to go.
In my previous example, we have been having a problem with SwitchForFanLight
class and I don't want to use this class anymore. But I don't have time to change that class and even I don't know where I used that reference. If I change it now, then I don't know where it can effect. So, better for me to just keep it alone and let it go.
Anyway, that time, when I have been having a problem, then I have extended my design and I have created another two classes and those are SwitchForFan
and SwitchForLight
.
It was easy for me, because, I had a base interface ISwitch
and in the SwitchForFan
and SwitchForLight
classes, I have inherited the ISwitch
interface as a base class.
It means that I have extended the SwitchForFan
and SwitchForLight
classes according to my current requirements and I didn't modify the old SwitchForFanLight
class at-all.
Now look at image which is given below. There is a SwitchBoardForBadDesign
class which contains two methods, TurnOn
and TurnOff
. Both of these methods have some if
and else
-If
statements which are used for turning on or turning off the light or fan or both according to the SwitchType
.
Now come to the main point, in future, if you need another implementation of the ISwitch
, then you need to add another If
-Else
statement into your TurnOn
and TurnOff
methods. So, there you go. This is the violation of the Open-Close principle. Because it is forcing you to modify your codes.
Now what is the solution for these issues??
Solution
Let’s look at the abstract switch class
which is given below:
So, I have fixed that issue into the SwitchBoardForGoodDesign
class. In this class, I have inherited the abstract Switch class
and overridden the TurnOn
and TurnOff
methods.
Now in future, if we add one more implementation of the ISwitch
say, GreenLight
or RedLight
, then we don’t need to touch the SwitchBoardForGoodDesign
class. So, now it is closed for the modification as well as we can extend our ISwitch
in future.
That's how the Open-Close principle works. It helped me to get rid of that problem.
Liskov Substitution Principle
It states that "Derived classes must be substitutable for their base classes".
Make sure that new derived classes are extending the base classes without changing their behavior.
I want to share one of my worse experiences with this principle. But again, I'm a lazy developer. So, don't worry, I'll not pressure you.
I am used to the British system in my daily life. But now I am in USA. So, I have got one project and my job was very easy. I just need to extend the functionality. During the development, they already told me that I will get one abstract
class and its name is 'Switch
'.
They have another class kSwitch
and my task is that I have to implement UsaSwitch
. Forgive me for kSwitch
. It is a bad naming convention. But I am using this just for the example.
I am including the class diagram:
The implementation of the K-switch was given. But I don't need to see that implementation; because I know how the switch works.
For me, it is very simple to implement and I have done that. So, after the unit testing, I sent it to the QA team. But when they started to test it, they got a bug. They told me that it was not working according to their expectations. They explained that when they turned up the switch, the electricity was not passing. Similarly, when they turned down the switch, the electricity was not stopping its flow.
Then, I read the document to know about the behavior of the switch and I understand that I have changed the behavior of the base class.
The UsaSwitch
class was implemented like this way:
Finally, I have learned that it was the violation of the Liskov Substitution Principle. Therefore, in USA, Turn-Up means start power supply. Turn-Down means stop power supply. In UK, Turn-Up means Stop Power supply. Turn-Down means Start Power.
So, don't change the meaning of the base class during the extension. You will not get any compilation error, but it can break your code during run-time.
Interface Segregation Principle
It states that - "Clients should not be forced to depend on interfaces that they don't use".
Say we have an IAnimal interface
. There is no doubt that Moose
, RedDeer
, Dog
and Cat
all are animals. Let's see the class diagram of these:
The IAnimal interface
has the following properties:
The Moose
and RedDeer
model classes are implemented from the IAnimal interface
.
Similarly, the Dog
and Cat
model classes are also implemented from the IAnimal interface
.
Now the problem is Moose
and RedDeer
have horns; but Dog
and Cat
have no horns. IAnimal interface
forces to implement the SizeOfHorns
property. So, this is the violation of the Interface Segregation Principle for the Dog
and Cat
classes. Although, we don't have any complaints for Moose
and RedDeer
classes.
So, according to the principle, we have to remove the SizeOfHorns
from the IAnimal interface
then Dog
and Cat
classes will be fine. On the other hand, we will add SizeOfHorns
properties into the implemented classes of the Moose
and RedDeer
or somewhere according to our design plan.
Dependency Inversion Principle
Real Life Scenario
Just for easy understanding, let's say, you have a different implementation of the Data Access Layer (DAL). For example, IDataAccess
has two implementations and these are DataAccessForSql
and DataAccessForOracle
. Depending on the customers, sometimes, you need DataAccessForSql
or sometimes you need DataAccessForOracle
. But if your classes are tightly coupled, then tight coupling design will not help you in this scenario.
Again, you have more than one payment system like PayPal, Visa and MasterCard. You need different implementation of your business logic depending on the payment system. For example, say, IPayment
has some implementation like PaymentForMasterCard
, PaymentForPayPal
and PaymentForVisa
. So, you need to switch from one to another depending on the preference of your customer. Tight coupling design will not give you this facilities.
So, the solution is Dependency Inversion.
Scenarios 2
Look at the below given example where we are considering a layer architecture style.
The Presentation Layer(PL) may contain User Interface Layer(UI) and Presentation Logic Layer(PLL). To make it easy, say, we have a web application and the UI means the web-form like *.aspx for ASP.NET or the view like *.cshtml for ASP.NET MVC. PLL means the controller class for the ASP.NET MVC or the *.aspx.cs for ASP.NET.
Business Layer (BL) could be your domain layer. The Data Access Layer (DAL). You can use any ORM like Entity-Frame-work as a DAL or whatever you want.
Say, you have bought some products from any web portal. Now you want to see your order items. So, when you click on a button to display your order, then View (UI) will communicate with the controller class (PLL). Controller
class will call the BL and then BL will call the DAL to verify the customer account information. Finally, DAL will send the order information to the BL and BL sends it to the PL. So, you will see your order list.
In short, the layers work as follows:
- PL depends on BL
- BL depends on DAL
So, high level layer depends on the low level layers. Say, these are tight coupled layers. So, we won't be able to change this dependency at run time or if we have more than one implementation of the DAL or BLL, then what happens next? Will we be able to change the tight-coupling of dependency from the layers?
Solution from DI
It states that "High-level layers should not depend on low-level layers. Both should depend on abstractions".
So, we can inject any of the implementations of the DAL to the BL and any of the implementations of BL to the PL. Design plan is cool, huh!
See the class diagram, where we have implemented DataAccessForSQL
, DataAccessForOracle
classes from the interface IDataAccess
. We have also implemented ProductBusinessLogic
class from the interface IProductBusinessLogic
.
Now we will inject either DataAccessForSQL
or DataAccessForOracle
into the ProductBusinessLogic
via constructor injection.
Finally, we have created dataAccess
object via either DataAccessForSQL
or DataAccessForOracle
according to the input during run-time, then the dataAccess
object is injected into productBl
object via constructor injection. See the code below:
OMG! You are adding more and more descriptions. Alright, I am stopping here. Even I will get time to review it. Let me know if you don't understand properly. I'm including the project and source codes with it. Find the attachment.
Remember one thing, these are all principles only, not hard coded rules like any holy books. So, relax and do your best, then design will find its way home.