Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Francis Terrero
Francis Terrero

Posted on

     

Mastering SOLID Principles in C#

As a backend developer, there are some concepts that you must know. If you want a job in this industry, the SOLID principles are a set of guidelines that have stood the test of time and are crucial to understand. They are also common interview topics that you must answer correctly if you want to pass.

I'm going to explain all the principles to you in the easiest way I found, using anime-related examples.

notLikeUs

Single Responsibility Principle

A class should have only one reason to change

Not only your class; your functions and modules should have one responsibility. This means that if you have a big function that does a lot of things, you should probably separate it into smaller functions.

publicclassAnime{publicstringTitle{get;set;}publicstringGenre{get;set;}publicintNumberOfEpisodes{get;privateset;}publicstringStudio{get;set;}publicstringDirector{get;set;}publicList<string>MainCharacters{get;privateset;}publicList<string>Episodes{get;privateset;}publicAnime(stringtitle,stringgenre,stringstudio,stringdirector){Title=title;Genre=genre;Studio=studio;Director=director;MainCharacters=newList<string>();Episodes=newList<string>();NumberOfEpisodes=0;}publicvoidAddCharacter(stringcharacter){MainCharacters.Add(character);}publicvoidAddEpisode(stringepisode){Episodes.Add(episode);NumberOfEpisodes++;}}
Enter fullscreen modeExit fullscreen mode

Look at this class. This class managesEpisodes andCharacters. These fields can be turned into classes easily.

Now let's split these properties by creating their own classes:

publicclassCharacter{publicstringName{get;set;}publicCharacter(stringname){Name=name;}publicvoidDisplayInfo(){Console.WriteLine($"Character:{Name}");}}publicclassEpisode{publicstringTitle{get;set;}publicEpisode(stringtitle){Title=title;}publicvoidDisplayInfo(){Console.WriteLine($"Episode:{Title}");}}publicclassAnime{publicstringTitle{get;set;}publicstringGenre{get;set;}publicintNumberOfEpisodes{get{returnEpisodes.Count;}}publicstringStudio{get;set;}publicstringDirector{get;set;}publicList<Character>MainCharacters{get;privateset;}publicList<Episode>Episodes{get;privateset;}publicAnime(stringtitle,stringgenre,stringstudio,stringdirector){Title=title;Genre=genre;Studio=studio;Director=director;MainCharacters=newList<Character>();Episodes=newList<Episode>();}publicvoidAddCharacter(Charactercharacter){MainCharacters.Add(character);}publicvoidAddEpisode(Episodeepisode){Episodes.Add(episode);}}
Enter fullscreen modeExit fullscreen mode

Here, there is a newCharacter andEpisode class, separating the responsibility of theAnime class.

If you want to split the class more, you can createStudio,Director, and evenGenre classes too!


Open-Closed Principle

Software entities (classes, modules, functions) should be open for extension, but closed for modification

You should not modify the class if you want to add new features!

publicclassAnimeCharacter{// Create a goal for each characterpublicstringGokuGoal(){return"Save the planet";}publicstringGojoGoal(){return"Defeat Sukuna";}publicstringGokuSpecialAttack(){return"Kamehameha!!";}publicstringGojoSpecialAttack(){return"Domain expansion";}}
Enter fullscreen modeExit fullscreen mode

Here, this class has aGoal() and aSpecialAttack() for each character, meaning that if we want to add anotherGoal() orSpecialAttack(), you'll have to modify the class each time.

publicabstractclassAnimeCharacter{//SpecialAttack() is now an abstract methodpublicabstractstringSpecialAttack();publicvirtualstringGoal(){return"Save the planet";}}publicclassGoku:AnimeCharacter{// Can now override SpecialAttack() from AnimeCharacterpublicoverridestringSpecialAttack(){return"Kamehameha!!";}}publicclassGojo:AnimeCharacter{publicoverridestringSpecialAttack(){return"Domain expansion";}publicoverridestringGoal(){return"Defeat Sukuna";}}
Enter fullscreen modeExit fullscreen mode

Apply inheritance and extend its functionality. Now, there are newGojo andGoku classes that inherit fromAnimeCharacter, overriding each implementation in its child class!

If you want to know more about abstraction and inheritance, I have apost prepared for you!


Liskov Substitution Principle

Derived or child classes must be substitutable for their base or parent classes

The methods of the parent class should be replaceable by methods of the child class.

publicclassStrawHat{publicvirtualvoidAttack(){Console.WriteLine("StrawHat attacks.");}publicvirtualvoidHeal(){Console.WriteLine("StrawHat heals.");}}publicclassChopper:StrawHat{publicoverridevoidAttack(){Console.WriteLine("Chopper attacks with his devil fruit power.");}publicoverridevoidHeal(){Console.WriteLine("Chopper heals with his medical skills.");}}publicclassUsopp:StrawHat{publicoverridevoidAttack(){Console.WriteLine("Usopp attacks with his slingshot.");}publicoverridevoidHeal(){// This will break the principle of LSP. Usopp cannot heal.thrownewNotImplementedException("Usopp doesn't heal.");}}
Enter fullscreen modeExit fullscreen mode

All StrawHats butChopper know nothing about medicine and healing, likeUsopp, so you shouldn't add the methodHeal in the parent classStrawHat.

Instead, you should split the skills by interfaces, like this:

publicinterfaceIAttacker{voidAttack();}publicinterfaceIHealer{voidHeal();}publicclassChopper:IHealer,IAttacker{publicvoidHeal(){Console.WriteLine("Chopper heals with his medical skills.");}publicvoidAttack(){Console.WriteLine("Chopper attacks with his devil fruit power.");}}publicclassUsopp:IAttacker{publicvoidAttack(){Console.WriteLine("Usopp attacks with his slingshot.");}}
Enter fullscreen modeExit fullscreen mode

Now, the methods are separated by interfaces where you can implement the contract wherever you need it. This comes along with the nextInterface Segregation Principle.


Interface Segregation Principle

Do not force any client to implement an interface which is irrelevant to them

In simple words: do not create a "super" interface. This principle comes along with theSingle Responsibility Principle.

publicinterfaceIAnimeCharacter{stringName{get;set;}stringPower{get;set;}voidDisplay();voidAttack();voidDefend();// Not all characters may need this method}publicclassHero:IAnimeCharacter{publicstringName{get;set;}publicstringPower{get;set;}publicstringHeroicTitle{get;set;}publicvoidDisplay(){Console.WriteLine($"Hero:{Name}, Title:{HeroicTitle}, Power:{Power}");}publicvoidAttack(){Console.WriteLine($"Hero{Name} attacks heroically with{Power}!");}publicvoidDefend(){Console.WriteLine($"Hero{Name} defends bravely!");}}publicclassVillain:IAnimeCharacter{publicstringName{get;set;}publicstringPower{get;set;}publicstringEvilPlan{get;set;}publicvoidDisplay(){Console.WriteLine($"Villain:{Name}, Plan:{EvilPlan}, Power:{Power}");}publicvoidAttack(){Console.WriteLine($"Villain{Name} attacks maliciously with{Power}!");}publicvoidDefend(){// Villain may not have a defend actionthrownewNotImplementedException($"{Name} does not defend!");}}
Enter fullscreen modeExit fullscreen mode

TheVillain class implements the interfaceIAnimeCharacter even when the class does not need theDefend() function.

publicinterfaceICharacter{stringName{get;set;}stringPower{get;set;}voidDisplay();}publicinterfaceIAttackable{voidAttack();}publicinterfaceIDefendable{voidDefend();}publicclassHero:ICharacter,IAttackable,IDefendable{publicstringName{get;set;}publicstringPower{get;set;}publicstringHeroicTitle{get;set;}publicvoidDisplay(){Console.WriteLine($"Hero:{Name}, Title:{HeroicTitle}, Power:{Power}");}publicvoidAttack(){Console.WriteLine($"Hero{Name} attacks heroically with{Power}!");}publicvoidDefend(){Console.WriteLine($"Hero{Name} defends bravely!");}}publicclassVillain:ICharacter,IAttackable{publicstringName{get;set;}publicstringPower{get;set;}publicstringEvilPlan{get;set;}publicvoidDisplay(){Console.WriteLine($"Villain:{Name}, Plan:{EvilPlan}, Power:{Power}");}publicvoidAttack(){Console.WriteLine($"Villain{Name} attacks maliciously with{Power}!");}}
Enter fullscreen modeExit fullscreen mode

Now, there are three new interfaces:ICharacter,IAttackable, andIDefendable, where you can use them where you really need them.

If you don't apply theSingle Responsibility Principle, theInterface Segregation Principle won't be effective either.


Dependency Inversion Principle

High-level modules should not depend on low-level modules. Both should depend on abstractions

Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

This is very useful because you will be able to change low-level implementations without affecting high-level modules, making high-level modules independent of low-level modules.

publicclassHero{publicstringName{get;set;}publicstringPower{get;set;}publicstringHeroicTitle{get;set;}publicvoidDisplay(){Console.WriteLine($"Hero:{Name}, Title:{HeroicTitle}, Power:{Power}");}publicvoidAttack(){Console.WriteLine($"Hero{Name} attacks heroically with{Power}!");}publicvoidDefend(){Console.WriteLine($"Hero{Name} defends bravely!");}}publicclassVillain{publicstringName{get;set;}publicstringPower{get;set;}publicstringEvilPlan{get;set;}publicvoidDisplay(){Console.WriteLine($"Villain:{Name}, Plan:{EvilPlan}, Power:{Power}");}publicvoidAttack(){Console.WriteLine($"Villain{Name} attacks maliciously with{Power}!");}}publicclassAnimeBattle{privatereadonlyHero_hero;privatereadonlyVillain_villain;publicAnimeBattle(Herohero,Villainvillain){_hero=hero;_villain=villain;}publicvoidStartBattle(){_hero.Display();_villain.Display();_hero.Attack();_villain.Attack();_hero.Defend();}}
Enter fullscreen modeExit fullscreen mode

Here, I'm implementingHero andVillain classes directly. Any change in either theHero class or theVillain class (low-level) can break theAnimeBattle class (high-level).

publicinterfaceIAnimeCharacter{stringName{get;set;}stringPower{get;set;}voidDisplay();voidAttack();}publicinterfaceIHero:IAnimeCharacter{stringHeroicTitle{get;set;}voidDefend();}publicinterfaceIVillain:IAnimeCharacter{stringEvilPlan{get;set;}}publicclassHero:IHero{publicstringName{get;set;}publicstringPower{get;set;}publicstringHeroicTitle{get;set;}publicvoidDisplay(){Console.WriteLine($"Hero:{Name}, Title:{HeroicTitle}, Power:{Power}");}publicvoidAttack(){Console.WriteLine($"Hero{Name} attacks heroically with{Power}!");}publicvoidDefend(){Console.WriteLine($"Hero{Name} defends bravely!");}}publicclassVillain:IVillain{publicstringName{get;set;}publicstringPower{get;set;}publicstringEvilPlan{get;set;}publicvoidDisplay(){Console.WriteLine($"Villain:{Name}, Plan:{EvilPlan}, Power:{Power}");}publicvoidAttack(){Console.WriteLine($"Villain{Name} attacks maliciously with{Power}!");}}publicclassAnimeBattle{privatereadonlyIHero_hero;privatereadonlyIVillain_villain;publicAnimeBattle(IHerohero,IVillainvillain){_hero=hero;_villain=villain;}publicvoidStartBattle(){_hero.Display();_villain.Display();_hero.Attack();_villain.Attack();_hero.Defend();}}
Enter fullscreen modeExit fullscreen mode

Now, any class that implementsIHero orIVillain can be used in theAnimeBattle class, providing a flexible way to have different implementations and taking advantage of polymorphism.

Here's thesource code if you want to play with it!

Happy coding!

notLikeUs

Top comments(2)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
der_gopher profile image
Alex Pliutau
SWE | Passionate about Backend, Go, Cloud, DevOps, Kubernetes | Writing https://packagemain.tech Newsletter | 🇩🇪
  • Location
    Berlin
  • Work
    Software Engineering Lead at Solsten
  • Joined

Great write-up! Also wrote some thoughts about it but in context of Go. Although Golang is not a purely object-oriented language, we can still apply SOLID principles to improve our Go code -packagemain.tech/p/mastering-solid...

CollapseExpand
 
terrerox profile image
Francis Terrero
Software Developer | .NET 🖥️ | React.js 🌐 | AWS ⛈️
  • Location
    Domincan Republic
  • Pronouns
    He/Him
  • Joined

Good article! I'm looking forward to learning Go. Your article will be a great help!

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Software Developer | .NET 🖥️ | React.js 🌐 | AWS ⛈️
  • Location
    Domincan Republic
  • Pronouns
    He/Him
  • Joined

Trending onDEV CommunityHot

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp