正反案例介绍SOLID原则

一.概述

  SOLID五大原则使我们能够管理解决大多数软件设计问题。由Robert C. Martin在20世纪90年代编写了这些原则。这些原则为我们提供了从紧耦合的代码和少量封装转变为适当松耦合和封装业务实际需求的结果方法。使用这些原则,我们可以构建一个具有整洁,可读且易于维护的代码应用程序。

  SOLID缩写如下:  

  • SRP  单一责任原则

  • OCP 开放/封闭原则

  • LSP  里氏替换原则

  • ISP   接口分离原则

  • DIP   依赖反转原则

  1.单一责任原则SRP 

      一个类承担的责任在理想情况下应该是多少个呢?答案是一个。这个责任是围绕一个核心任务构建,不是简化的意思。通过暴露非常有限的责任使这个类与系统的交集更小。

    (1) 演示:违反了单一责任原则,原因是:顾客类中承担了太多无关的责任。  

    /// <summary>
/// 顾客类所有实现
/// </summary>
public class Cliente
{
public int ClienteId { get; set; }
public string Nome { get; set; }
public string Email { get; set; }
public string CPF { get; set; }
public DateTime DataCadastro { get; set; }

public string AdicionarCliente()
{
//顾客信息验证
if (!Email.Contains("@"))
return "Cliente com e-mail inválido";

if (CPF.Length != 11)
return "Cliente com CPF inválido";

//保存顾客信息
using (var cn = new SqlConnection())
{
var cmd = new SqlCommand();

cn.ConnectionString
= "MinhaConnectionString";
cmd.Connection
= cn;
cmd.CommandType
= CommandType.Text;
cmd.CommandText
= "INSERT INTO CLIENTE (NOME, EMAIL CPF, DATACADASTRO) VALUES (@nome, @email, @cpf, @dataCad))";

cmd.Parameters.AddWithValue(
"nome", Nome);
cmd.Parameters.AddWithValue(
"email", Email);
cmd.Parameters.AddWithValue(
"cpf", CPF);
cmd.Parameters.AddWithValue(
"dataCad", DataCadastro);

cn.Open();
cmd.ExecuteNonQuery();
}

//发布邮件
var mail = new MailMessage("empresa@empresa.com", Email);
var client = new SmtpClient
{
Port
= 25,
DeliveryMethod
= SmtpDeliveryMethod.Network,
UseDefaultCredentials
= false,
Host
= "smtp.google.com"
};

mail.Subject
= "Bem Vindo.";
mail.Body
= "Parabéns! Você está cadastrado.";
client.Send(mail);

return "Cliente cadastrado com sucesso!";
}
}

    

 (2) 解决方案,使用单一责任原则,每个类只负责自己的业务。

    /// <summary>
/// 顾客实体
/// </summary>
public class Cliente
{
public int ClienteId { get; set; }
public string Nome { get; set; }
public string Email { get; set; }
public string CPF { get; set; }
public DateTime DataCadastro { get; set; }

/// <summary>
/// 顾客信息验证
/// </summary>
/// <returns></returns>
public bool IsValid()
{
return EmailServices.IsValid(Email) && CPFServices.IsValid(CPF);
}
}

/// <summary>
/// 保存顾客信息
/// </summary>
public class ClienteRepository
{
/// <summary>
/// 保存
/// </summary>
/// <param name="cliente">要保存的顾客实体</param>
public void AdicionarCliente(Cliente cliente)
{
using (var cn = new SqlConnection())
{
var cmd = new SqlCommand();

cn.ConnectionString
= "MinhaConnectionString";
cmd.Connection
= cn;
cmd.CommandType
= CommandType.Text;
cmd.CommandText
= "INSERT INTO CLIENTE (NOME, EMAIL CPF, DATACADASTRO) VALUES (@nome, @email, @cpf, @dataCad))";

cmd.Parameters.AddWithValue(
"nome", cliente.Nome);
cmd.Parameters.AddWithValue(
"email", cliente.Email);
cmd.Parameters.AddWithValue(
"cpf", cliente.CPF);
cmd.Parameters.AddWithValue(
"dataCad", cliente.DataCadastro);

cn.Open();
cmd.ExecuteNonQuery();
}
}
}

/// <summary>
/// CPF服务
/// </summary>
public static class CPFServices
{
public static bool IsValid(string cpf)
{
return cpf.Length == 11;
}
}

/// <summary>
/// 邮件服务
/// </summary>
public static class EmailServices
{
public static bool IsValid(string email)
{
return email.Contains("@");
}

public static void Enviar(string de, string para, string assunto, string mensagem)
{
var mail = new MailMessage(de, para);
var client = new SmtpClient
{
Port
= 25,
DeliveryMethod
= SmtpDeliveryMethod.Network,
UseDefaultCredentials
= false,
Host
= "smtp.google.com"
};

mail.Subject
= assunto;
mail.Body
= mensagem;
client.Send(mail);
}
}

/// <summary>
/// 客户服务,程序调用入口
/// </summary>
public class ClienteService
{
public string AdicionarCliente(Cliente cliente)
{
//先验证
if (!cliente.IsValid())
return "Dados inválidos";

//保存顾客
var repo = new ClienteRepository();
repo.AdicionarCliente(cliente);

//邮件发送
EmailServices.Enviar("empresa@empresa.com", cliente.Email, "Bem Vindo", "Parabéns está Cadastrado");

return "Cliente cadastrado com sucesso";
}
}
  2. 开放/封闭原则OCP

    类应该是可以可扩展的,可以用作构建其他相关新功能,这叫开放。但在实现相关功能时,不应该修改现有代码(因为已经过单元测试运行正常)这叫封闭。

    (1) 演示:违反了开放/封闭原则,原因是每次增加新形状时,需要改变AreaCalculator 类的TotalArea方法,例如开发后期又增加了圆形形状。

    /// <summary>
/// 长方形实体
/// </summary>
public class Rectangle
{
public double Height { get; set; }
public double Width { get; set; }
}

/// <summary>
/// 圆形
/// </summary>
public class Circle
{
/// <summary>
/// 半径
/// </summary>
public double Radius { get; set; }
}

/// <summary>
/// 面积计算
/// </summary>
public class AreaCalculator
{
public double TotalArea(object[] arrObjects)
{
double area = 0;
Rectangle objRectangle;
Circle objCircle;
foreach (var obj in arrObjects)
{
if (obj is Rectangle)
{
objRectangle
= (Rectangle)obj;
area
+= objRectangle.Height * objRectangle.Width;
}
else
{
objCircle
= (Circle)obj;
area
+= objCircle.Radius * objCircle.Radius * Math.PI;
}
}
return area;
}
}

     (2) 解决方案,使用开放/封闭原则,每次增加新形状时(开放),不需要修改TotalArea方法(封闭)

   /// <summary>
/// 形状抽象类
/// </summary>
public abstract class Shape
{
/// <summary>
/// 面积计算
/// </summary>
/// <returns></returns>
public abstract double Area();
}

/// <summary>
/// 长方形
/// </summary>
public class Rectangle : Shape
{
public double Height { get; set; }
public double Width { get; set; }
public override double Area()
{
return Height * Width;
}
}

/// <summary>
/// 圆形
/// </summary>
public class Circle : Shape
{
public double Radius { get; set; }
public override double Area()
{
return Radius * Radius * Math.PI;
}
}

/// <summary>
/// 面积计算
/// </summary>
public class AreaCalculator
{
public double TotalArea(Shape[] arrShapes)
{
double area = 0;
foreach (var objShape in arrShapes)
{
area
+= objShape.Area();
}
return area;
}
}
  3.里氏替换原则LSP

    这里也涉及到了类的继承,也适用于接口。子类可以替换它们的父类。里氏替换原则常见的代码问题是使用虚方法,在父类定义虚方法时,要确保该方法里没有任何私有成员。

    (1) 演示:违反了里氏替换原则, 原因是不能使用ReadOnlySqlFile子类替代SqlFile父类。

    /// <summary>
/// sql文件类 读取、保存
/// </summary>
public class SqlFile
{
public string FilePath { get; set; }
public string FileText { get; set; }
public virtual string LoadText()
{
/* Code to read text from sql file */
return "..";
}
public virtual void SaveText()
{
/* Code to save text into sql file */
}
}

/// <summary>
/// 开发途中增加了sql文件只读类
/// </summary>
public class ReadOnlySqlFile : SqlFile
{
public override string LoadText()
{
/* Code to read text from sql file */
return "..";
}
public override void SaveText()
{
/* Throw an exception when app flow tries to do save. */
throw new IOException("Can't Save");
}
}


public class SqlFileManager
{
/// <summary>
/// 集合中存在两种类:SqlFile和ReadOnlySqlFile
/// </summary>
public List<SqlFile> lstSqlFiles { get; set; }

/// <summary>
/// 读取
/// </summary>
/// <returns></returns>
public string GetTextFromFiles()
{
StringBuilder objStrBuilder
= new StringBuilder();
foreach (var objFile in lstSqlFiles)
{
objStrBuilder.Append(objFile.LoadText());
}
return objStrBuilder.ToString();
}

/// <summary>
/// 保存
/// </summary>
public void SaveTextIntoFiles()
{
foreach (var objFile in lstSqlFiles)
{
//检查当前对象是ReadOnlySqlFile类,跳过调用SaveText()方法
if (!(objFile is ReadOnlySqlFile))
{
objFile.SaveText();
}
}
}
}

     (2) 解决方案,使用里氏替换原则,子类可以完全代替父类

   public interface IReadableSqlFile
{
string LoadText();
}
public interface IWritableSqlFile
{
void SaveText();
}

public class ReadOnlySqlFile : IReadableSqlFile
{
public string FilePath { get; set; }
public string FileText { get; set; }
public string LoadText()
{
/* Code to read text from sql file */
return "";
}
}


public class SqlFile : IWritableSqlFile, IReadableSqlFile
{
public string FilePath { get; set; }
public string FileText { get; set; }
public string LoadText()
{
/* Code to read text from sql file */
return "";
}
public void SaveText()
{
/* Code to save text into sql file */
}
}


public class SqlFileManager
{
public string GetTextFromFiles(List<IReadableSqlFile> aLstReadableFiles)
{
StringBuilder objStrBuilder
= new StringBuilder();
foreach (var objFile in aLstReadableFiles)
{
//ReadOnlySqlFile的LoadText实现
objStrBuilder.Append(objFile.LoadText());
}
return objStrBuilder.ToString();
}

public void SaveTextIntoFiles(List<IWritableSqlFile> aLstWritableFiles)
{
foreach (var objFile in aLstWritableFiles)
{
//SqlFile的SaveText实现
objFile.SaveText();
}
}
}
  4.接口分离原则ISP

    接口分离原则是解决接口臃肿的问题,建议接口保持最低限度的函数。永远不应该强迫客户端依赖于它们不用的接口。

     (1)  演示:违反了接口分离原则。原因是Manager无法处理任务,同时没有人可以将任务分配给Manager,因此WorkOnTask方法不应该在Manager类中。

   /// <summary>
/// 领导接口
/// </summary>
public interface ILead
{
//创建任务
void CreateSubTask();
//分配任务
void AssginTask();
//处理指定任务
void WorkOnTask();
}

/// <summary>
/// 团队领导
/// </summary>
public class TeamLead : ILead
{
public void AssginTask()
{
//Code to assign a task.
}
public void CreateSubTask()
{
//Code to create a sub task
}
public void WorkOnTask()
{
//Code to implement perform assigned task.
}
}

/// <summary>
/// 管理者
/// </summary>
public class Manager : ILead
{
public void AssginTask()
{
//Code to assign a task.
}
public void CreateSubTask()
{
//Code to create a sub task.
}
public void WorkOnTask()
{
throw new Exception("Manager can't work on Task");
}
}

     (2) 解决方案,使用接口分离原则

    /// <summary>
/// 程序员角色
/// </summary>
public interface IProgrammer
{
void WorkOnTask();
}

/// <summary>
/// 领导角色
/// </summary>
public interface ILead
{
void AssignTask();
void CreateSubTask();
}

/// <summary>
/// 程序员:执行任务
/// </summary>
public class Programmer : IProgrammer
{
public void WorkOnTask()
{
//code to implement to work on the Task.
}
}

/// <summary>
/// 管理者:可以创建任务、分配任务
/// </summary>
public class Manager : ILead
{
public void AssignTask()
{
//Code to assign a Task
}
public void CreateSubTask()
{
//Code to create a sub taks from a task.
}
}

/// <summary>
/// 团队领域:可以创建任务、分配任务、执行执行
/// </summary>
public class TeamLead : IProgrammer, ILead
{
public void AssignTask()
{
//Code to assign a Task
}
public void CreateSubTask()
{
//Code to create a sub task from a task.
}
public void WorkOnTask()
{
//code to implement to work on the Task.
}
}
  5. 依赖反转原则DIP

    依赖反转原则是对程序的解耦。高级模块/类不应依赖于低级模块/类,两者都应该依赖于抽象。意思是:当某个类被外部依赖时,就需要把该类抽象成一个接口。接口如何变成可调用的实例呢?实践中多用依赖注入模式。这个依赖反转原则在DDD中得到了很好的运用实践(参考前三篇)。

    (1) 演示:违反了依赖反转原则。原因是:每当客户想要引入新的Logger记录形式时,我们需要通过添加新方法来改变ExceptionLogger类。这里错误的体现了:高级类 ExceptionLogger直接引用低级类FileLogger和DbLogger来记录异常。

   /// <summary>
/// 数据库日志类
/// </summary>
public class DbLogger
{
//写入日志
public void LogMessage(string aMessage)
{
//Code to write message in database.
}
}


/// <summary>
/// 文件日志类
/// </summary>
public class FileLogger
{
//写入日志
public void LogMessage(string aStackTrace)
{
//code to log stack trace into a file.
}
}

public class ExceptionLogger
{
public void LogIntoFile(Exception aException)
{
FileLogger objFileLogger
= new FileLogger();
objFileLogger.LogMessage(GetUserReadableMessage(aException));
}

public void LogIntoDataBase(Exception aException)
{
DbLogger objDbLogger
= new DbLogger();
objDbLogger.LogMessage(GetUserReadableMessage(aException));
}

private string GetUserReadableMessage(Exception ex)
{
string strMessage = string.Empty;
//code to convert Exception's stack trace and message to user readable format.
return strMessage;
}
}


public class DataExporter
{
public void ExportDataFromFile()
{
try
{
//code to export data from files to database.
}
catch (IOException ex)
{
new ExceptionLogger().LogIntoDataBase(ex);
}
catch (Exception ex)
{
new ExceptionLogger().LogIntoFile(ex);
}
}
}

     (2) 解决方案,使用依赖反转原则,这里演示没有用依赖注入。

   public interface ILogger
{
void LogMessage(string aString);
}


/// <summary>
/// 数据库日志类
/// </summary>
public class DbLogger : ILogger
{
//写入日志
public void LogMessage(string aMessage)
{
//Code to write message in database.
}
}


/// <summary>
/// 文件日志类
/// </summary>
public class FileLogger : ILogger
{
//写入日志
public void LogMessage(string aStackTrace)
{
//code to log stack trace into a file.
}
}


public class ExceptionLogger
{
private ILogger _logger;
public ExceptionLogger(ILogger aLogger)
{
this._logger = aLogger;
}

//可以与这些日志类达到松散耦合
public void LogException(Exception aException)
{
string strMessage = GetUserReadableMessage(aException);
this._logger.LogMessage(strMessage);
}

private string GetUserReadableMessage(Exception aException)
{
string strMessage = string.Empty;
//code to convert Exception's stack trace and message to user readable format.
return strMessage;
}
}

public class DataExporter
{
public void ExportDataFromFile()
{
ExceptionLogger _exceptionLogger;
try
{
//code to export data from files to database.
}
catch (IOException ex)
{
_exceptionLogger
= new ExceptionLogger(new DbLogger());
_exceptionLogger.LogException(ex);
}
catch (Exception ex)
{
_exceptionLogger
= new ExceptionLogger(new FileLogger());
_exceptionLogger.LogException(ex);
}
}
}

  参考文献

    SOLID原则简介

原文地址:https://www.cnblogs.com/MrHSR/p/10912615.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 
640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/315762.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ASP.NET Core 中的静态文件

1.前言当我们创建Core项目的时候&#xff0c;Web根目录下会有个wwwroot文件目录&#xff0c;wwwroot文件目录里面默认有HTML、CSS、IMG、JavaScript等文件&#xff0c;而这些文件都是Core提供给客户端使用的静态文件。但是这些静态文件需要在Core里面配置才可以对外公开访问。2…

基于Kebernetes 构建.NET Core技术中台

今天下午在腾讯云社区社区分享了《基于Kubernetes 构建.NET Core技术中台》&#xff0c;下面是演讲内容的文字实录。我们为什么需要中台我们现在处于企业信息化的新时代。为什么这样说呢&#xff1f;过去企业信息化的主流重心是企业内部信息化。但现在以及未来的企业信息化的主…

RedLock 实现分布式锁

并发是程序开发中不可避免的问题&#xff0c;根据系统面向用户、功能场景的不同&#xff0c;并发的重视程度会有不同。从程序的角度来说&#xff0c;并发意味着相同的时间点执行了相同的代码&#xff0c;而有些情况是不被允许的&#xff0c;比如&#xff1a;转账、抢购占库存等…

[翻译] NumSharp的数组切片功能 [:]

原文地址&#xff1a;https://medium.com/scisharp/slicing-in-numsharp-e56c46826630翻译初稿&#xff08;英文水平有限&#xff0c;请多包涵&#xff09;&#xff1a;由于Numsharp新推出了数组切片这个牛逼的功能&#xff0c;所以.NET社区距离拥有强大的开源机器学习平台又近…

Visual Studio 2019 16.1发布,更快更高效

Visual Studio 2019 16.1 已正式发布&#xff0c;可以看到&#xff0c;新版本的启动速度有了显著的提升&#xff0c;还节省了不少的内存空间。主要更新如下&#xff1a;IDE现已公开发布 Visual Studio IntelliCode&#xff0c;并且可以随任何支持 C#、C、TypeScipt/JavaScript …

.NET Core 3.0之创建基于Consul的Configuration扩展组件

经过前面三篇关于.NET Core Configuration的文章之后&#xff0c;本篇文章主要讨论如何扩展一个Configuration组件出来。如果前面三篇文章没有看到&#xff0c;可以点击如下地址访问.NET Core 3.0之深入源码理解Configuration(一).NET Core 3.0之深入源码理解Configuration(二)…

Mono 和 .NET Core比翼双飞

大家好&#xff0c;今天给大家分享.NET 蓝图之下的Mono和.NET Core 话题&#xff0c;微软在Build 2019 大会上给.NET 做了一个五年规划&#xff0c;所以分享的主题就是《Mono和.NET Core 比翼双飞》&#xff0c;将在完成这个五年规划的时候合体。在开始这个主题之前&#xff0c…

在上司面前硬不起来?教你如何快速将字符串转换为可执行代码

老是因为活不好被上司欺凌&#xff1f;在上司面前很没面子&#xff1f;在上司面前硬不起来&#xff1f; 是时候分享一个可以快速将字符串转换为可执行代码的项目给你了 - YACEP !不过&#xff0c;这不是一篇专门对YACEP 做详细介绍的随笔&#xff0c;想知道更详细的的YACEP 细节…

ConcurrentDictionary线程不安全么,你难道没疑惑,你难道弄懂了么?

事情不太多时&#xff0c;会时不时去看项目中同事写的代码可以作个参考或者学习&#xff0c;个人觉得只有这样才能走的更远&#xff0c;抱着一副老子天下第一的态度最终只能是井底之蛙。前两篇写到关于断点传续的文章&#xff0c;还有一篇还未写出&#xff0c;后续会补上&#…

记一次ORM的权衡和取舍

面对ORM的选型&#xff0c;有些人是根据自己熟悉程度来评判&#xff0c;有些人是根据他人的推荐来抉择&#xff0c;有些人觉得都差不多&#xff0c;随便了。当自己要真正做选择的时候&#xff0c;以上的这些依据都无法真正说服自己&#xff0c;因为不同的业务需求&#xff0c;不…

出让执行权:Task.Yield, Dispatcher.Yield

一个耗时的任务&#xff0c;可以通过 Task.Yield 或者 Dispatcher.Yield 来中断以便分割成多个小的任务片段执行。Yield 这个词很有意思&#xff0c;叫做“屈服”“放弃”“让步”&#xff0c;字面意义上是让出当前任务的执行权&#xff0c;转而让其他任务可以插入执行。Task、…

VS Code 即将迎来再一次的 logo 更新!已可在 Insiders 版本尝鲜

为什么要说“再一次”&#xff1f; 相信 VS Code 的老用户都还记得两年前的 logo 更新风波吧。当时 VS Code 改了新 logo 之后&#xff0c;VS Code 的用户们一片哀嚎&#xff0c;纷纷觉得新 logo 太丑&#xff0c;在 GitHub 和各种社交媒体上各种吐槽&#xff01;不过幸运的是&…

从零开始在 Windows 上部署 .NET Core 到 Kubernetes

本章节所有代码已上传至&#xff1a;https://github.com/Seanwong933/.NET-Core-on-Kubernetes文末附有本人遇到过的 Docker 和 k8s 的故障排除。本文目标&#xff1a;带领大家在 Kubernetes 上部署一个 .NET Core Api 的单节点集群。后续文章会帮助大家继续深入。安装 Kuberne…

.NET Core微服务 权限系统+工作流(一)权限系统

一、前言实际上权限系统老早之前我就在一直开发&#xff0c;大概在刚毕业没多久就想一个人写一个系统&#xff0c;断断续续一直坚持到现在&#xff0c;毕竟自己亲动手自写的系统才有收获&#xff0c;本篇仅介绍权限。小小系统上不了台面&#xff0c;望各位大神勿喷。二、系统介…

iNeuOS云操作系统,.NET Core全系打造

一.演示地址演示地址&#xff1a; 进入iNeuOS系统。&#xff08;建议使用chrome浏览器&#xff09;http://192.144.173.38:8081/login.html测试名称&#xff1a;admin测试密码&#xff1a;admin下载《iNeuOS云操作系统演示应用手册》 链接&#xff1a;https://pan.baidu.co…

译 | 你到底有多精通 C# ?

点击上方蓝字关注“汪宇杰博客”文&#xff1a;Damir Arh译&#xff1a;Edi Wang即使是具有良好 C# 技能的开发人员有时候也会编写可能会出现意外行为的代码。本文介绍了属于该类别的几个 C# 代码片段&#xff0c;并解释了令人惊讶的行为背后的原因。Null 值我们都知道&#xf…

各大主流K8S服务全方位能力比对

大家好&#xff0c;趁打开流量主的东风&#xff0c;特此贡献一篇长文&#xff0c;分析一下目前国内国外几大著名云厂商的kubernetes服务&#xff0c;以飨诸君。文起之前&#xff0c;先聊态度。 我本人是十分看好k8s的发展的&#xff0c;为何&#xff1f; 理因古往今来&#xff…

.NET Core 的Generic Host 之Generic Host Builder

通用Host(Generic Host) 与 web Host 不同的地方就是通用Host解耦了Http请求管道&#xff0c;使得通用Host拥有更广的应用场景。比如&#xff1a;消息收发、后台任务以及其他非http的工作负载。这些场景都可以通过使用通用Host拥有横切&#xff08;Cross-cutting&#xff09;的…

.NET Core微服务 权限系统+工作流(二)工作流系统

一、前言接上一篇 .NET Core微服务 权限系统工作流&#xff08;一&#xff09;权限系统 &#xff0c;再来一发工作流&#xff0c;我在接触这块开发的时候一直好奇它的实现方式&#xff0c;翻看各种工作流引擎代码&#xff0c;探究其实现方式&#xff0c;个人总结出来一个核心要…

开源分布式Job系统,调度与业务分离-如何创建一个计划HttpJob任务

项目介绍&#xff1a;Hangfire&#xff1a;是一个开源的job调度系统,支持分布式JOB&#xff01;&#xff01;Hangfire.HttpJob 是我针对Hangfire开发的一个组件,该组件和Hangfire本身是独立的。可以独立更新Hangfire版本不影响&#xff01;该组件已被Hangfire官方采纳&#xff…