I'm a hobby coder trying to improve my code. I tend to create monolithic classes and want to start being more the S in SOLID. I've done some reading on here and elsewhere, but I'm struggling to get my head around what the best approach to doing this is. I can think of three scenarios:
- Static Methods
- Through instantiation
- Mixture of above but passing full parent class to dependency class (does this have memory implications or not due to it just being a pointer?)
namespace SingleResponsabilityTest
{
internal class Program
{
static void Main(string[] args)
{
Factoriser factoriser = new Factoriser();
factoriser.DoFactorTen();
}
}
internal class Factoriser
{
public int classSpecificInt = 10;
public void DoFactorTen()
{
SingleResponsabiltyApproach1 sra1 = new SingleResponsabiltyApproach1(classSpecificInt);
Console.WriteLine(sra1.FactorTen());
Console.WriteLine(SingleResponsabiltyApproach2.FactorTen(classSpecificInt));
Console.WriteLine(SingleResponsabiltyApproach3.FactorTen(this));
Console.ReadLine();
}
}
internal class SingleResponsabiltyApproach1
{
int passedInt = 0;
public SingleResponsabiltyApproach1(int passedInt)
{
this.passedInt = passedInt;
}
public int FactorTen()
{
return passedInt * 10;
}
}
internal class SingleResponsabiltyApproach2
{
public static int FactorTen(int passedInt)
{
return passedInt * 10;
}
}
internal class SingleResponsabiltyApproach3
{
public static int FactorTen(Factoriser factoriser)
{
return factoriser.classSpecificInt * 10;
}
}
}
What is the best approach?
Also, where does dependency injection and interfaces come into all this? Thanks.
You are abstracting over the value
passedInt. This is not the right approach. You must split the functional responsibilities. Here I can detect 3 responsibilities:Therefore I declare 3 interfaces describing these 3 requirements:
Here is a possible implementation:
Note that the Factoriser does not need to know the details about calculations and logging. Therefore these responsibilities are injected in the Factoriser through constructor injection. Note that we are injecting the responsibilities, not the values like
classSpecificInt = 10in your example. The implementations should be flexible enough to deal with all possible values.Now, we can write the Main method like this:
Now, you could easily write this result to a file by providing a file logger instead of a console logger. You could inject the file name into the logger through the constructor. In this case it makes sense to inject a value, because the logger will have to log into the same file during its whole lifetime.
This would not have an impact on the
Factoriser, since an abstractILoggeris injected.This approach implements these SOLID principles:
Factoriserdepends on concrete implementations because it calls, e.g.:new SingleResponsabiltyApproach1(..).Note also that
IFactoriserdoes not depend on the other interfaces. This gives us a high degree of flexibility in implementation.