简体   繁体   English

如何处理单一职责原则?

[英]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:我可以想到三种情况:

  1. Static Methods静态方法
  2. Through instantiation通过实例化
  3. 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 .您正在抽象值passedInt This is not the right approach.这不是正确的方法。 You must split the functional responsibilities.您必须拆分职能职责。 Here I can detect 3 responsibilities:在这里我可以检测到 3 个职责:

  • Multiply (ie, calculate)乘法(即计算)
  • Writing to the console (ie, logging in the broadest sense)写入控制台(即广义上的日志记录)
  • Organizing and combining calculations and logging.组织和组合计算和记录。

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 原则:

  • Single-responsibility principle (SRP).单一职责原则 (SRP)。
  • Open–closed principle:开闭原则:
    • We can extend the behavior of our interfaces and implementations by deriving new interfaces and classes from them, ie, without modifying the existing ones.我们可以通过从中派生新的接口和类来扩展接口和实现的行为,即无需修改现有接口和类。
  • Liskov substitution principle (LSP):里氏代换原则(LSP):
    • We can inject a class derived from our calculator or logger or inject completely different implementations to the Factoriser without the Factoriser knowing it.我们可以注入一个派生自我们的计算器或记录器的类,或者在 Factoriser 不知道的情况下向 Factoriser 注入完全不同的实现。
  • The Interface segregation principle (ISP):接口隔离原则(ISP):
    • Our interfaces declare a minimal API and thus our implementations do not depend on methods they do not use.我们的接口声明了一个最小的 API,因此我们的实现不依赖于它们不使用的方法。
  • The Dependency inversion principle (DI):依赖倒置原则(DI):
    • Factoriser depends upon abstractions (ie interfaces), not concretions (ie, not specific classes). Factoriser 依赖于抽象(即接口),而不是具体化(即不是特定的类)。 Your implementation of 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

  • TurnOn()打开()
  • TurnOff()关掉()

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

  • TurnEngineOn()开启引擎()
  • TurnEngineOff()关闭引擎()
  • Accelerate()加速()
  • Brake()制动()
  • TurnLightsOn()开灯()
  • TurnLightsOff()关灯()

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM