zoukankan      html  css  js  c++  java
  • 单一职责原则(C#)

    翻译、参考:https://www.dotnetcurry.com/software-gardening/1148/solid-single-responsibility-principle


    在用面向对象编程很多年后,我发现很多程序员总是违反这个原则。

    是的,我们写类和方法,企图以面向过程的方式将逻辑写到同一个方法内,而不是把它们拆分成很多单一的类,每个只负责做一件事。

    这里有一些典型的示例代码

    public class CsvFileProcessor
    {
        public void Process(string filename)
        {
            TextReader tr = new StreamReader(filename);
            tr.ReadToEnd();
            tr.Close();
    
            var conn = new SqlConnection("server=(local);integrated security=sspi;database=SRP");
            conn.Open();
    
            string[] lines = tr.ToString().Split(new string[] {@"
    l"}, StringSplitOptions.RemoveEmptyEntries);
            foreach( string line in lines)
            {
                string[] columns = line.Split(new string[] {","}, StringSplitOptions.RemoveEmptyEntries);
                var command = conn.CreateCommand();
                command.CommandText = "INSERT INTO People (FirstName, LastName, Email) VALUES (@FirstName, @LastName, @Email)";
                command.Parameters.AddWithValue("@FirstName", columns[0]);
                command.Parameters.AddWithValue("@LastName", columns[1]);
                command.Parameters.AddWithValue("@Email", columns[2]);
                command.ExecuteNonQuery();
            }
            conn.Close();
        }
    }

    这个类做了几件事?一件?两件?三件?或者更多?

    你可能会忍不住说一个。也就是说,这个类处理一个CSV文件。

    从另一个角度看这个类。如何进行单元测试?

    这并不容易。

    如果有其他东西,比如数据验证和错误日志记录,该怎么办?那么你如何进行单元测试呢?

    事实是,这个类做了三个事情:

    1. 读取CSV文件
    2. 转换CSV文件
    3. 储存数据

    在类中做很多事情是不好的,不仅因为很难进行单元测试,而且它增加了引入bug的几率。

    如果您在解析部分更改了代码,并且添加了一个错误,那么读取和存储也会中断。而且,因为单元测试将不存在或非常复杂,所以跟踪和修复错误也需要更长的时间。

    添加单一责任原则

    为了解决这个问题,我们需要将代码分解成单独的部分。

    您可能认为可以只使用三个方法,每个方法对应一个功能块。但是回到SRP的定义。它说一个类应该只有一个目的。

    因此,我们需要三个类来完成这项工作。好的,我们马上会看到更多。

    修复这段代码的方法是通过代码重构。最初,我们将把每个功能块放入它自己的方法中。

    public class CsvFileProcessor
    {
        public void Process(string filename)
        {
            var csvData = ReadCsv(filename);
            var parsedData = ParseCsv(csvData);
            StoreCsvData(parsedData);
        }
    
        public string ReadCsv(string filename)
        {
            TextReader tr = new StreamReader(filename);
            tr.ReadToEnd();
            tr.Close();
            return tr.ToString();
        }
    
        public string[] ParseCsv(string csvData)
        {
            return csvData.ToString().Split(new string[] { @"
    l" }, StringSplitOptions.RemoveEmptyEntries);
        }
    
        public void StoreCsvData(string[] csvData)
        {
            var conn = new SqlConnection("server=(local);integrated security=sspi;database=SRP");
            conn.Open();
            foreach (string line in csvData)
            {
                string[] columns = line.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                var command = conn.CreateCommand();
                command.CommandText = "INSERT INTO People (FirstName, LastName, Email) VALUES (@FirstName, @LastName, @Email)";
                command.Parameters.AddWithValue("@FirstName", columns[0]);
                command.Parameters.AddWithValue("@LastName", columns[1]);
                command.Parameters.AddWithValue("@Email", columns[2]);
                command.ExecuteNonQuery();
            }
            conn.Close();
        }
    }

    正如你所看到的,事情仍然不太对。

    我们用 ParseCsv() 这个方法 CSV文件转换为一行一行的 ,但是StoreCsvData()  这个方法是将行转换为一列一列的。

    解决这个问题的方法是 采用ContactDTO  来储存每一行的数据。

    下一步是添加DTO,但我将跳过一个步骤,并将每个方法分解到它自己的类中。

    但是我同时也预想到一些问题,如果这些数据不是来自CSV呢?如果它来自XML、JSON或者其它数据格式怎么办?

    你用 接口 解决了这个问题

    public interface IContactDataProvider
    {
        string Read();
    }
    public interface IContactParser
    {
        IList<ContactDTO> Parse(string contactList);
    }
    public interface IContactWriter
    {
        void Write(IList<ContactDTO> contactData);
    }
    public class ContactProcessor
    {
        public void Process(IContactDataProvider cdp, IContactParser cp, IContactWriter cw)
        {
            var providedData = cdp.Read();
            var parsedData = cp.Parse(providedData);
            cw.Write(parsedData);
        }
    }
    public class CSVContactDataProvider : IContactDataProvider
    {
        private readonly string _filename;
    
        public CSVContactDataProvider(string filename)
        {
            _filename = filename;
        }
        
        public string Read()
        {
            TextReader tr = new StreamReader(_filename);
            tr.ReadToEnd();
            tr.Close();
            return tr.ToString();
        }
    }
    
    public class CSVContactParser : IContactParser
    {
        public IList<ContactDTO> Parse(string csvData)
        {
            IList<ContactDTO> contacts = new List<ContactDTO>();
            string[] lines = csvData.Split(new string[] { @"
    l" }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string line in lines)
            {
                string[] columns = line.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                var contact = new ContactDTO
                {
                    FirstName = columns[0],
                    LastName = columns[1],
                    Email = columns[2]
                };
                contacts.Add(contact);
            }
    
            return contacts;
        }
    }
    
    public class ADOContactWriter : IContactWriter
    {
        public void Write(IList<ContactDTO> contacts)
        {
            var conn = new SqlConnection("server=(local);integrated security=sspi;database=SRP");
            conn.Open();
            foreach (var contact in contacts)
            {
                var command = conn.CreateCommand();
                command.CommandText = "INSERT INTO People (FirstName, LastName, Email) VALUES (@FirstName, @LastName, @Email)";
                command.Parameters.AddWithValue("@FirstName", contact.FirstName);
                command.Parameters.AddWithValue("@LastName", contact.LastName);
                command.Parameters.AddWithValue("@Email", contact.Email);
                command.ExecuteNonQuery();
            }
            conn.Close();
    
        }
    }
    
    public class ContactDTO
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
    }

     我们使用通用的方法名Read,Parse,Write,因为我们不知道我们将得到什么类型的数据。

    现在我们可以轻松地对这段代码进行单元测试。

    我们还可以轻松地修改解析代码,如果引入新的错误,它不会影响读取和写入代码。

    另一个好处是我们实现了 低耦合。

    这就是结果。我们采用了相当常见(。。。。常见吗?)的面向过程代码,并使用单一责任原则对其进行重构。

    下次查看一个类时,问问自己是否可以重构它以使用SRP。应用SOLID的S将帮助您的代码变得绿色、繁茂和有活力,您正在走向拥有一个软件花园的道路上。

    把软件开发比作建筑,我们可以看出软件是坚固的,而且很难改变。相反,我们应该把软件开发比作园艺,因为花园总是在变化的。

     软件园艺包含实践和工具,可以帮助您为您的软件创建尽可能好的花园,花最少的努力让它增长和改变。了解更多关于什么是软件园艺。

  • 相关阅读:
    UIWebView的高度不对问题
    SQL --分组【Group By】
    SQL--Order By
    SQL--空值处理
    SQ--模糊查询
    SQL 聚合函数
    SQL 语句的TOP,Distinct语句
    SQL约束
    1 翻译系列:什么是Code First(EF 6 Code First 系列)
    终极版:由简单工厂模式,升级到抽象工厂模式(用到反射)
  • 原文地址:https://www.cnblogs.com/xieweikang/p/14158707.html
Copyright © 2011-2022 走看看