正反案例介绍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
正反案例介绍SOLID原则相关推荐
- 实践GoF的23种设计模式:SOLID原则(上)
本文分享自华为云社区<实践GoF的23种设计模式:SOLID原则(上)>,作者:元闰子. 前言 从1995年GoF提出23种设计模式到现在,25年过去了,设计模式依旧是软件领域的热门话题. ...
- SOLID原则的含义和具体使用
单一职责原则(SRP) 开放封闭原则(OCP) 里氏替换原则(LSP) 接口隔离原则(ISP) 依赖倒置原则(DIP) 小结 SOLID 是面向对象设计5大重要原则的首字母缩写,当我们设计类和模块时, ...
- 浅谈 SOLID 原则
单一职责原则(SRP) 开放封闭原则(OCP) 里氏替换原则(LSP) 接口隔离原则(ISP) 依赖倒置原则(DIP) 小结 SOLID 是面向对象设计5大重要原则的首字母缩写,当我们设计类和模块时, ...
- 浅谈 SOLID 原则的具体使用
参考文章:浅谈 SOLID 原则的具体使用 目录 单一职责原则(SRP) 开放封闭原则(OCP) 里氏替换原则(LSP) 接口隔离原则(ISP) 依赖倒置原则(DIP) 小结 单一职责原则(SRP) ...
- 独家 | Python中的SOLID原则(附链接)
作者:Mattia Cinelli翻译:朱启轩校对:欧阳锦本文约3500字,建议阅读15分钟本文通过一些Python示例代码介绍了可以提高代码可靠性的SOLID编码准则. 标签:数据结构,编程,数据科 ...
- 每个Web开发者都应该知道的SOLID原则
原创: 前端之巅 前端之巅 10月20日 作者|Chidume Nnamdi 编辑|谢丽 面向对象的编程并不能防止难以理解或不可维护的程序.因此,Robert C. Martin 制定了五项指导原则, ...
- SOLID 原则的可靠指南
山姆·米灵顿 读完需要 7 分钟 速读仅需 3 分钟 山姆是牛津大学的一名软件开发人员,目前在生物信息学领域工作.山姆的专长领域是 Java,他每天都在为生物学研究编写多线程分析工具.他是一位热情的程 ...
- 实践GoF的23的设计模式:SOLID原则(下)
本文分享自华为云社区<实践GoF的23的设计模式:SOLID原则(下)>,作者: 雷电与骤雨. 在<实践GoF的23种设计模式:SOLID原则(上)>中,主要讲了SOLID原则 ...
- 什么是SOLID原则(第1部分)
翻译自:What's the deal with the SOLID principles?(part 1) 即使你是一个初级开发人员,你也可能听说过 SOLID 原则.它们无处不在.工作面试时,你也 ...
最新文章
- mysql 按重复排序_php-按日期排序并允许重复的日期时,获取MySQL中的上一个和下一个记录...
- mySQL教程 第1章 数据库设计
- DI 之Spring更多DI的知识
- java数组按照大小排列_Java怎么让数组中元素按照牌值从小到大的顺序排列
- hdu 3790 最短路径问题
- 计算机网络————P1 概念、组成、功能和分类
- Key ssd_300_vgg/block3_box/L2Normalization/gamma not found in checkpoint的解决方案
- 如何在内存中存储有序数据?
- Qt获取本地ip地址
- PICKIT3脱机烧写指导书
- hzhost防asp攻击函数
- 深度学习常用算子(一)
- Flink实战(八十五):flink-sql使用(十二)Flink 与 hive 结合使用(四)Hive Read Write
- 日期对象 date.getDate() date.getDay()的区别
- 12月第二个星期学习计划
- 哈希算法原理与应用:确保数据完整性和安全性的关键技术
- 添加、修改、删除以及查看本地git的用户名和邮箱
- 92天倒计时,蓝桥杯省赛备赛攻略来啦~
- 设计模式、原则、饿汉式单例模式、抽象工厂、代理模式、观察者模式、模板方法模式使用场景
- AsyncTask隐藏的陷阱