Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Application Architecture - What's The Matter With Being STUPID Before Being SOLID

4.40/5 (46 votes)
24 Apr 2017CPOL16 min read 51.8K   222  
This topic will cover the bad design practice using STUPID and good design practice using SOLID. Detailed explanation for Single Single Responsibility Principle, Open and Closed Principle, Liskov Substitution Principle, Interface Segregation Principle and Dependency Inversion (DI) Principle.

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: 

  1. Stupid Design for bad practice
    • Singleton
    • Tight Coupling
    • Un-testability
    • Premature Optimization
    • In-descriptive Naming
    • Duplication
  2. 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.

Image 1

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.

Image 2

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

Image 3

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'.

Image 4

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.

Image 5

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.

Image 6

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.

Image 7

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.

Image 8

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 AddressEmployee then use AddressId, EmployeeId instead of only Id.

Image 9

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:

Image 10

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.

Image 11

Again, if you see the implementation of the Fan and Light class, then we are good for that.

Image 12

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!

Image 13

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.

Image 14

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.

Image 15

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.

Image 16

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.

Image 17

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:

Image 18

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. 

Image 19

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'.

Image 20

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:

Image 21

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.

Image 22

The UsaSwitch class was implemented like this way:

Image 23

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.

Image 24

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:

Image 25

The IAnimal interface has the following properties:

Image 26

The Moose and RedDeer model classes are implemented from the IAnimal interface.

Image 27

Similarly, the Dog and Cat model classes are also implemented from the IAnimal interface.

Image 28

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.

Image 29

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.

Image 30

So, the solution is Dependency Inversion.

Scenarios 2

Look at the below given example where we are considering a layer architecture style.

Image 31

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:

  1. PL depends on BL
  2. 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".

Image 32

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!

Image 33

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.

Image 34

Now we will inject either DataAccessForSQL or DataAccessForOracle into the ProductBusinessLogic via constructor injection.

Image 35

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:

Image 36

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)