asp.net core系列 65 正反案例介绍SOLID原则
一.概述
SOLID五大原则使我们能够管理解决大多数软件设计问题。由Robert C. Martin在20世纪90年代编写了这些原则。这些原则为我们提供了从紧耦合的代码和少量封装转变为适当松耦合和封装业务实际需求的结果方法。使用这些原则,我们可以构建一个具有整洁,可读且易于维护的代码应用程序。
SOLID缩写如下:
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 != )
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 = ,
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 == ;
}
} /// <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 = ,
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 = ;
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 = ;
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);
}
}
}
参考文献
asp.net core系列 65 正反案例介绍SOLID原则的更多相关文章
- asp.net core系列 53 IdentityServer4 (IS4)介绍
一.概述 在物理层之间相互通信必须保护资源,需要实现身份验证和授权,通常针对同一个用户存储.对于资源安全设计包括二个部分,一个是认证,一个是API访问. 1 认证 认证是指:应用程序需要知道当前用户的 ...
- asp.net core系列 72 Exceptionless使用介绍
一.Exceptionless介绍 Exceptionless专注于.net平台提供实时错误和日志报告.主要包括:错误通知.智能分组异常.详细错误报告堆栈跟踪.支持离线.UI查看重要错误和确定优先级. ...
- 【目录】asp.net core系列篇
随笔分类 - asp.net core系列篇 asp.net core系列 68 Filter管道过滤器 摘要: 一.概述 本篇详细了解一下asp.net core filters,filter叫&q ...
- asp.net core 系列 18 web服务器实现
一. ASP.NET Core Module 在介绍ASP.NET Core Web实现之前,先来了解下ASP.NET Core Module.该模块是插入 IIS 管道的本机 IIS 模块(本机是指 ...
- asp.net core系列 40 Web 应用MVC 介绍与详细示例
一. MVC介绍 MVC架构模式有助于实现关注点分离.视图和控制器均依赖于模型. 但是,模型既不依赖于视图,也不依赖于控制器. 这是分离的一个关键优势. 这种分离允许模型独立于可视化展示进行构建和测试 ...
- asp.net core系列 39 Web 应用Razor 介绍与详细示例
一. Razor介绍 在使用ASP.NET Core Web开发时, ASP.NET Core MVC 提供了一个新特性Razor. 这样开发Web包括了MVC框架和Razor框架.对于Razor来说 ...
- asp.net core系列 39 Razor 介绍与详细示例
原文:asp.net core系列 39 Razor 介绍与详细示例 一. Razor介绍 在使用ASP.NET Core Web开发时, ASP.NET Core MVC 提供了一个新特性Razor ...
- Ajax跨域问题及解决方案 asp.net core 系列之允许跨越访问(Enable Cross-Origin Requests:CORS) c#中的Cache缓存技术 C#中的Cookie C#串口扫描枪的简单实现 c#Socket服务器与客户端的开发(2)
Ajax跨域问题及解决方案 目录 复现Ajax跨域问题 Ajax跨域介绍 Ajax跨域解决方案 一. 在服务端添加响应头Access-Control-Allow-Origin 二. 使用JSONP ...
- asp.net core系列 38 WebAPI 返回类型与响应格式--必备
一.返回类型 ASP.NET Core 提供以下 Web API Action方法返回类型选项,以及说明每种返回类型的最佳适用情况: (1) 固定类型 (2) IActionResult (3) Ac ...
随机推荐
- tomcat BIO 、NIO 、AIO
11.11活动当天,服务器负载过大,导致部分页面出现了不可访问的状态.那后来主管就要求调优了,下面是tomcat bio.nio.apr模式以及后来自己测试的一些性能结果. 原理方面的资料都是从网上找 ...
- shell脚本,tee小工具的用法。
解释: tee是个工具 , 它的作用就是把标准输出,复制一份,扔文件里 ,原标准输出还输出,-a就相当于 >> 追加到文件里的意思. 不加就是 > 重定向到文件里去.
- java 操作mongodb查询条件的常用设置
java操作mongodb进行查询,常用筛选条件的设置如下: 条件列表:BasicDBList condList = new BasicDBList(); 临时条件对象:BasicDBObject c ...
- Ukulele 那些花儿
- MySQLfailover错误一则
由于公司现有主库要转移到新的主库上,所以,我打算利用MySQLfailover工具的故障转移. 1.开发把程序账号转移到新主库上 2.停止现有主库,使之进行故障转移,转移期间会自动锁表,保持数据一致性 ...
- laravel如何利用数据库的形式发送通知
具体实现需要分几步: 1.修改驱动为database; 2.创建database的queue表 3.创建任务sendMessage 4.创建发送逻辑dispatch 5.启动队列 接下来我们进行实操: ...
- tiny4412 busybox制作根文件系统rootfs nfs 挂载 ubuntu 14.04
http://blog.csdn.net/liudijiang/article/details/50555429(转) 首先得要有制作好的uboot和linux内核镜像zImage,先烧录到sd卡里, ...
- bash初识,特性,用法/网站
目录 一.Bash初识 Bash Shell介绍 Bash Shell的作用 Bash 两种方式 命令提示符 二.Shell的基本语法 三.Shell的基本特性 1.命令补全 tab 2. Linux ...
- 多线程并发情况下 重复insert问题
代码逻辑: if(数据不存在){ insert(); } 线程启动后,发现数据库表中有相同的记录 解决方案 synchronized同步代码块即加同步锁,synchronized同步代码块的功能: 当 ...
- cs229_part2
part2 这节课主要讲的是生成式模型,那么与这个生成式模型相对于的就是我们上节课所讲那几个辨别式模型.所以生成式模型和辨别式模型的区别是什么呢.我先给出数学上的定义: 这是我们上节课线性回归所用的给 ...