[英]How to approach Single Responsibility Principle?
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.
我倾向于创建整体类,并希望开始成为 SOLID 中的 S。 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:
我可以想到三种情况:
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
.您正在抽象值
passedInt
。 This is not the right approach.这不是正确的方法。 You must split the functional responsibilities.
您必须拆分职能职责。 Here I can detect 3 responsibilities:
在这里我可以检测到 3 个职责:
Therefore I declare 3 interfaces describing these 3 requirements:因此,我声明了 3 个接口来描述这 3 个要求:
public interface ICalculator
{
int Multiply(int x, int y);
}
public interface ILogger
{
void Log(string message);
void Close();
}
public interface IFactoriser
{
void DoFactorTen(int value);
}
Here is a possible implementation:这是一个可能的实现:
public class Calculator : ICalculator
{
public int Multiply(int x, int y)
{
return x * y;
}
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
public void Close()
{
Console.ReadKey();
}
}
public class Factoriser : IFactoriser
{
private ICalculator _calculator;
private ILogger _logger;
public Factoriser(ICalculator calculator, ILogger logger)
{
_calculator = calculator;
_logger = logger;
}
public void DoFactorTen(int value)
{
int result = _calculator.Multiply(value, 10);
_logger.Log($"The result is {result}");
_logger.Close();
}
}
Note that the Factoriser does not need to know the details about calculations and logging.请注意,Factoriser 不需要知道有关计算和日志记录的详细信息。 Therefore these responsibilities are injected in the Factoriser through constructor injection.
因此,这些职责通过构造函数注入注入到 Factoriser 中。 Note that we are injecting the responsibilities, not the values like
classSpecificInt = 10
in your example.请注意,我们正在注入责任,而不是您示例中的
classSpecificInt = 10
之类的值。 The implementations should be flexible enough to deal with all possible values.实现应该足够灵活以处理所有可能的值。
Now, we can write the Main method like this:现在,我们可以这样编写 Main 方法:
static void Main(string[] args)
{
var calculator = new Calculator();
var logger = new ConsoleLogger();
var factoriser = new Factoriser(calculator, logger);
factoriser.DoFactorTen(15);
}
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 abstract ILogger
is injected.这不会对
Factoriser
产生影响,因为注入了抽象ILogger
。
This approach implements these SOLID principles:这种方法实现了这些 SOLID 原则:
Factoriser
depends on concrete implementations because it calls, eg: new SingleResponsabiltyApproach1(..)
.Factoriser
的实施取决于具体实施,因为它会调用,例如: new SingleResponsabiltyApproach1(..)
。 Note also that IFactoriser
does not depend on the other interfaces.另请注意,
IFactoriser
不依赖于其他接口。 This gives us a high degree of flexibility in implementation.这使我们在实施中具有高度的灵活性。
The Single Responsibility Principle (SRP) basically says:单一职责原则 (SRP) 基本上说:
A Class/method must have only one responsibility
一个类/方法必须只有一个责任
So, to go over this principle, think about a class Car with a god method called TurnOn().所以,为了复习这个原则,考虑一个类 Car,它有一个名为 TurnOn() 的神方法。 Inside this method, you start the car, turn the lights on, accelerate, and brake.
在此方法中,您启动汽车、打开灯、加速和刹车。 And another method called TurnOff(), turning off engine, lights, and braking.
另一种方法称为 TurnOff(),用于关闭引擎、灯和刹车。
Car车
If you need to use this class, you may think the method TurnOn() only turns the car on, which is not valid, and it breaks the SRP.如果您需要使用此类,您可能会认为 TurnOn() 方法只是打开汽车,这是无效的,它会破坏 SRP。 The same applies for the TurnOff()
这同样适用于 TurnOff()
Applying the SRP, the class Car must have the methods:应用 SRP,类 Car 必须具有以下方法:
Car车
So now, if you need to use the Car class, you know exactly how to use each part of the car independently.所以现在,如果你需要使用 Car 类,你就知道如何独立使用汽车的每个部分。
You can notice every method with a specific responsibility.您可以注意到每个方法都有特定的职责。
I changed a few things in your example to apply the SRP:我在您的示例中更改了一些内容以应用 SRP:
namespace SingleResponsibility
{
internal class Program
{
// All the UI interaction (read, write) happens in the main method (UI layer with the user)
// So the single responsibility of the UI is only calling methods and interacting with users
static void Main(string[] args)
{
Console.Write("Tell me a number to factorise: ");
int myNumber = Convert.ToInt32( Console.ReadLine() );
SingleResponsabiltyApproach1 sra1 = new SingleResponsabiltyApproach1(myNumber);
Console.WriteLine( $"My first approach {sra1.DoFactorTen()}" );
SingleResponsabiltyApproach2 sra2 = new SingleResponsabiltyApproach2();
Console.WriteLine($"My second approach {sra2.DoFactorTen(myNumber)}");
Console.ReadLine();
}
}
// The single responsibility of this class is to do an approach using a parametered constructor
internal class SingleResponsabiltyApproach1
{
// using property as private (encapsulated)
private int PassedInt { get; set; }
// starting the constructor with a parameter
public SingleResponsabiltyApproach1(int passedInt)
{
this.PassedInt = passedInt;
}
// doing the factor
// The single responsibility of this method is to do a factor ten, and its name really means this
public int DoFactorTen()
{
return PassedInt * 10;
}
}
// The single responsibility of this class is to do an approach using a default constructor
internal class SingleResponsabiltyApproach2
{
// no custom constructor
// doing the factor passing number as parameter
// The single responsibility of this method is to do a factor ten with a number
// provided, and its name and signature really means this
public int DoFactorTen(int passedInt)
{
return passedInt * 10;
}
}
}
If you want to go over interfaces and dependency injections, maybe you can go over the other more complex principles, such as Liskov Substitution Principle (LSK), Interface Segregation Principle (ISP), Dependency Inversion Principle (DIP).如果你想复习接口和依赖注入,也许你可以复习其他更复杂的原则,比如里氏代换原则(LSK)、接口隔离原则(ISP)、依赖倒置原则(DIP)。
Cheers!干杯!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.