c# – 当SRP违规似乎将复杂性传递到链中时,正确的方法

c# – 当SRP违规似乎将复杂性传递到链中时,正确的方法,第1张

概述在实现我的课程之后,目前处于实质性的重构工作中.我试图分解一些事情,更好地遵循SRP,但我总是发现很难评估一个班级是否有“改变的一个理由”的格言.我希望这个实际的例子可以帮助我理解. 有问题的代码旨在清理数据.目前这里有两个独立的进程 – 我们通过使用通过代码调用的外部应用程序来清理地址数据.我们使用C#中的内部算法清理其他数据字段. 当我被告知我们可能希望将来更改这两个进程时,这个重构就开始了 在实现我的课程之后,目前处于实质性的重构工作中.我试图分解一些事情,更好地遵循SRP,但我总是发现很难评估一个班级是否有“改变的一个理由”的格言.我希望这个实际的例子可以帮助我理解.

有问题的代码旨在清理数据.目前这里有两个独立的进程 – 我们通过使用通过代码调用的外部应用程序来清理地址数据.我们使用C#中的内部算法清理其他数据字段.

当我被告知我们可能希望将来更改这两个进程时,这个重构就开始了 – 例如使用数据库存储过程来执行这两个作业而不是C#代码和外部应用程序.所以我的第一直觉是将这两个函数隐藏在接口后面(fileRow和fileContents只是DTO):

public interface IAddressCleaner{    string CleanAddress(StringBuilder inputAddress);    voID Cleanfile(fileContents fc);}public interface IFIEldCleaner{    string CleanPhoneNumber(string phonetoClean);    voID CleanAllPhoneFIElds(fileRow row,fileContents fc);    voID MatchObscentitIEs(fileRow row,fileContents fc);    voID CleanEmailFIElds(fileRow row,fileContents fc);}

哪个好.然而,实际上,我无法想象一个班级会在没有其他班级的情况下使用其中一个.因此将它们(及其实现)合并到一个类中似乎是有意义的.考虑到我们可能用一个解决方案(如数据库)替换这两个函数,这也是有意义的.

另一方面,似乎IFIEldCleaner已经违反了SRP,因为它正在做三件事:清理电话号码,发送电子邮件和寻找粗鲁的单词,所有这些都是逻辑上不同的过程.因此似乎有理由将其拆分为IPhoneCleaner,IObscenityMatcher和IEmailCleaner.

对后一种方法特别困扰的是这些类在服务中使用,该服务已经具有愚蠢的接口依赖性:

public class ReadfileService : IExecutableObject{    private ILogger _log;    private IRepository _rep;    private IfileHelper _fileHelp;    private IFIEldCleaner _fIEldCleaner;    private IfileParser _fileParser;    private IfileWriter _fileWriter;    private IEmailService _mailService;    private IAddressCleaner _addressCleaner;    public ReadfileService(ILogger log,IRepository rep,IfileHelper fileHelp,IfileParser fileParse,IfileWriter fileWrite,IEmailService email,IAddressCleaner addressCleaner)    {        // assign to privates    }  // functions}

而且,反过来看,它似乎也违反了SRP到一个荒谬的程度,而没有增加额外的两个接口.

这里的方法是什么?我应该有一个ICleaner接口,还是将其分成五个?

解决方法 免责声明:我不是专家,人们可能不同意我的一些想法.提供一个直接的答案是很难的,因为它在很大程度上取决于幕后的内容.可能还有很多“正确”的答案,但这完全取决于我们在这里缺少的信息.尽管如此,还没有人回答过,我认为有些事情我可以指出可以指导你朝着正确的方向前进.

祝你好运!

你有权使用Pluralsight吗?购买快速的月份是完全值得的,只需要经历Encapsulation and SOLID.我经历过的“啊哈”时刻之一就是看看你的方法签名,以帮助识别你可以提取的界面,以帮助简化代码.忽略名称,只需查看参数即可.

我将尝试使用您提供的代码进行练习,但我需要在可能不正确的方式上做出假设.

在IFIEldCleaner上,您有3个具有相同签名的方法:

voID CleanAllPhoneFIElds(fileRow row,fileContents fc);voID MatchObscentitIEs(fileRow row,fileContents fc);voID CleanEmailFIElds(fileRow row,fileContents fc);

注意这些方法是如何完全相同的.这表明您可以使用3个实现提取单个接口:

interface IFIEldCleaner {  voID Clean(fileRow row,fileContents fc);}class PhoneFIEldCleaner : IFIEldCleaner { }class ObscentitIEsFIEldCleaner : IFIEldCleaner { }class EmailFIEldCleaner : IFIEldCleaner { }

现在,这很好地将清理这些田地的责任分成了一口大小的课程.

现在您还有其他一些清洁方法:

string CleanPhoneNumber(string phoneNumber);string CleanAddress(StringBuilder inputAddress);

这些是非常相似的,除了一个采用StringBuilder可能是因为实现关心各个行?让我们把它切换成一个字符串并假设实现将处理行拆分/解析,然后我们得到与以前相同的结果 – 具有相同签名的两个方法:

string CleanPhoneNumber(string phoneNumber);string CleanAddress(string inputAddress);

因此,按照我们之前的逻辑,让我们创建一个与清理字符串相关的接口:

interface IStringCleaner {  string Clean(string s);}class PhoneNumberStringCleaner : IStringCleaner { }class AddressstringCleaner : IStringCleaner { }

现在我们将这些职责分离到他们自己的实现中.

在这一点上,我们只有一个方法可以解决:

voID Cleanfile(fileContents fc);

我不确定这种方法是做什么的.为什么它是IAddressCleaner的一部分?因此,现在我将其排除在讨论之外 – 也许这是一种读取文件,查找地址,然后清理它的方法,在这种情况下,您可以通过调用我们的新AddressstringCleaner来完成.

那么让我们看看到目前为止我们所处的位置.

interface IFIEldCleaner {  voID Clean(fileRow row,fileContents fc);}class PhoneFIEldCleaner : IFIEldCleaner { }class ObscentitIEsFIEldCleaner : IFIEldCleaner { }class EmailFIEldCleaner : IFIEldCleaner { }interface IStringCleaner {  string Clean(string s);}class PhoneNumberStringCleaner : IStringCleaner { }class AddressstringCleaner : IStringCleaner { }

这些似乎都与我相似,闻起来有些气味.根据您的原始方法名称(如CleanAllFIElds),您可能正在使用循环来清除fileRow中的某些列?但为什么还要依赖fileContents?再说一次,我看不到你的实现,所以我不太确定.也许您打算传递原始文件或数据库输入?

我也无法看到存储清理结果的位置 – 大多数先前的方法返回voID,这意味着调用方法有一些副作用(即它是一个Command),而一些方法只返回一个干净的字符串(一个query).

因此,我将假设整体意图是清理字符串,无论它们来自何处,并将它们存储在某处.如果是这种情况,我们可以进一步简化我们的模型:

interface IStringCleaner {  string Clean(string s);}class PhoneNumberStringCleaner : IStringCleaner { }class AddressstringCleaner : IStringCleaner { }class ObscenitIEsstringCleaner : IStringCleaner { }class EmailStringCleaner : IStringCleaner { }

请注意,我们已经删除了对IFIEldCleaner的需求,因为这些字符串清除程序只处理要清理的输入字符串.

现在回到原始上下文 – 似乎您可以从文件中获取数据并且这些文件可能有行?这些行包含我们需要清理其值的列.我们还需要坚持我们所做的清洁改变.

因此,基于您提供的服务,我看到了一些可能对我们有帮助的事情:

IRepositoryIfileHelperIfileWriterIfileParser

我的假设是,我们打算将清理过的字段保留回来 – 我不确定的地方,我看到的是“存储库”,然后是“fileWriter”.

无论如何,我们知道我们需要最终从字段中获取字符串,也许IfileParser可以帮忙吗?

interface IfileParser {  fileContents ReadContents(file file);  fileRow[] ReadRows(fileContents fc);  fileFIEld ReadFIEld(fileRow row,string column);}

这可能比它需要的更复杂 – fileFIEld可以负责存储字段值,因此可能你可以将所有这些组合在一起形成一个fileContents来保存回磁盘.

所以,现在我们已经将输入来自(文件,数据库等)的最终目标(干净的东西)与我们如何持久化(返回文件,数据库等)分开.

您现在可以使用您的服务根据需要撰写此流程.例如,您说当前您调用外部程序来清理地址?没问题:

class ExternalAddressstringCleaner : IStringCleaner {  // depend on whatever you need here  public string Clean(string s) {    // call external program    return cleanString;  }}

现在切换到存储过程?好的,也没问题:

class DatabaseAddressstringCleaner : IStringCleaner {  // depend on database  DatabaseAddressstringCleaner(IRepository repository) {  }  string Clean(string s) {    // call your database sproc    return cleanString;  }}

很难为您的服务推荐想法 – 但您可以将其拆分为单独的较小服务(fileReaderService,fileCleaningService和fileStoreService)或简化您所采用的依赖关系.

既然你只有一个接口IStringCleaner,你可以只声明你需要的清洁工并将它们换掉/改变它们.

public fileCleanerService {  private IStringCleaner _addressCleaner;  private IStringCleaner _phoneCleaner;  private IStringCleaner _obscenityCleaner;  private IStringCleaner _emailCleaner;  ctor(IfileParser parser,/* deps */) {    _parser = parser;    _addressCleaner = new ExternalAddressstringCleaner(/* deps */);    _phoneCleaner = new PhonestringCleaner();    _obscenityCleaner = new ObscenityStringCleaner();    _emailCleaner = new EmailStringCleaner();  }  public voID Clean(fileContents fc) {    foreach(var row in _parser.ReadRows(fc)) {      var address = _parser.ReadFIEld(row,"Address");      var phone   = _parser.ReadFIEld(row,"Phone");      var post    = _parser.ReadFIEld(row,"PostContent");      var email   = _parser.ReadFIEld(row,"Email");      // assumes you want to write back to the fIEld?      // handle this however you want      address.Value = _addressCleaner.Clean(address.Value);      phone.Value = _phoneCleaner.Clean(phone.Value);      post.Value = _obscenityCleaner.Clean(post.Value);      email.Value = _emailCleaner.Clean(email.Value);    }}

我对你的过程和代码做了很多假设,所以这可能比我想象的要复杂得多.没有所有信息,很难提供指导 – 但是通过查看界面和名称仍然可以推断出基本的东西,我希望我已经证明了这一点.有时你只需要看过表面看到矩阵后面的1和0然后它们都有意义;)

为长篇文章道歉,但我完全理解你来自哪里.弄清楚如何重构事物是令人生畏,令人困惑的,似乎没有人能够提供帮助.希望这能让你在重构时有所作为.这是一项艰巨的任务,但只是坚持一些简单的指导方针和模式,并且根据你投入的努力,最终可能会更容易维护.再次,我绝对推荐PluralSight课程.

总结

以上是内存溢出为你收集整理的c# – 当SRP违规似乎将复杂性传递到链中时,正确的方法全部内容,希望文章能够帮你解决c# – 当SRP违规似乎将复杂性传递到链中时,正确的方法所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/1245707.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-07
下一篇 2022-06-07

发表评论

登录后才能评论

评论列表(0条)

保存