asp.net core系列 65 正反案例介绍SOLID原则

原文:asp.net core系列 65 正反案例介绍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("[email protected]", 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("[email protected]", 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/lonelyxmas/p/11108181.html

时间: 2024-10-01 15:51:25

asp.net core系列 65 正反案例介绍SOLID原则的相关文章

asp.net core系列 28 EF模型配置(字段,构造函数,拥有实体类型)

原文:asp.net core系列 28 EF模型配置(字段,构造函数,拥有实体类型) 一. 支持字段 EF允许读取或写入字段而不是一个属性.在使用实体类时,用面向对象的封装来限制或增强应用程序代码对数据访问的语义时,这可能很有用.无法使用数据注释配置.除了约定,还可以使用Fluent API为属性配置支持字段. 1.1 约定 public class Blog { // _<camel-cased property name> private string _url; public int

asp.net core系列 64 结合eShopOnWeb全面认识领域模型架构

原文:asp.net core系列 64 结合eShopOnWeb全面认识领域模型架构 一.项目分析 在上篇中介绍了什么是"干净架构",DDD符合了这种干净架构的特点,重点描述了DDD架构遵循的依赖倒置原则,使软件达到了低藕合.eShopOnWeb项目是学习DDD领域模型架构的一个很好案例,本篇继续分析该项目各层的职责功能,主要掌握ApplicationCore领域层内部的术语.成员职责. 1. web层介绍 eShopOnWeb项目与Equinox项目,双方在表现层方面对比,没有太大

ASP.NET CORE系列【五】webapi整理以及RESTful风格化

原文:ASP.NET CORE系列[五]webapi整理以及RESTful风格化 介绍 什么是RESTful?  这里不多做赘述,详情请百度! 哈哈,本来还想巴拉巴拉介绍一些webapi, RESTful的, 还是算了,咱们直接上干货!(原因是懒!哈哈) 使用 以前使用过mvc的人对webapi 应该都很熟悉,先看一段熟悉的代码 大伙发现了什么没?跟以往mvc大多数相同,但有些地方不同 ,我们来一起看看有何区别 1.首先SysUsersController上面有一段代码 [Produces("a

ASP.NET CORE系列【一】搭建ASP.NET CORE项目

原文:ASP.NET CORE系列[一]搭建ASP.NET CORE项目 为什么要使用 ASP.NET Core? NET Core 刚发布的时候根据介绍就有点心里痒痒,微软的尿性都懂的,新东西bug太多,现在2.0也发布很久了,决定研究一下. ASP.NET Core官方文档https://docs.microsoft.com/en-us/aspnet/core/getting-started ASP.NET Core 具有如下优点: 生成 Web UI 和 Web API 的统一场景. 集成

ASP.NET CORE系列【二】使用Entity Framework Core进行增删改查

原文:ASP.NET CORE系列[二]使用Entity Framework Core进行增删改查 介绍 EntityFrameworkCore EF core 是一个轻量级的,可扩展的EF的跨平台版本.对于EF而言 EF core 包含许多提升和新特性,同时 EF core 是一个全新的代码库,并不如 EF6 那么成熟和稳定.EF core 保持了和EF相似的开发体验,大多数顶级API都被保留了下来,所以,如果你用过EF6,那么上手EF core你会觉得非常轻松和熟悉,EF core 构建在一

目录导航「深入浅出ASP.NET Core系列」

希望给你3-5分钟的碎片化学习,可能是坐地铁.等公交,积少成多,水滴石穿,谢谢关注. 入门篇 1.1课程介绍「深入浅出ASP.NET Core系列」 1.2环境安装「深入浅出ASP.NET Core系列」 1.3创建项目「深入浅出ASP.NET Core系列」 1.4部署到IIS「深入浅出ASP.NET Core系列」 1.5准备CentOS和Nginx环境「深入浅出ASP.NET Core系列」 1.6部署到CentOS「深入浅出ASP.NET Core系列」 2.1命令行和JSON的配置「深

asp.net core 系列 14 错误处理

一.概述 本文介绍处理 ASP.NET Core 应用中常见错误的一些方法.主要是关于:开发环境异常页:非开发环境配置自定义异常处理页:配置状态代码页(没有正文响应,http状态400~599的). 1.1 开发环境异常页 要将应用配置为显示有关异常的详细信息的页面,请使用开发环境异常页.要环境设置为 Development,具体查看:asp.net core系列9环境.下面向 Startup.Configure 方法添加代码行: if (env.IsDevelopment()) { //注意:

asp.net core 系列 15 中间件

原文:asp.net core 系列 15 中间件 一.概述 中间件(也叫中间件组件)是一种装配到应用管道以处理请求和响应的软件. 每个组件:(1)选择是否将请求传递到管道中的下一个组件;(2)可以在管道中的下一个组件之前和之后执行工作. 请求委托用于生成请求管道. 请求委托会处理每个 HTTP 请求.使用以下方法配置请求委托:Run,  Map, Use扩展方法.可以将单个请求委托作为匿名方法(称为内联中间件in-line middleware) 或者可以在可重用类中定义.这些可重用的类和内联

asp.net core 系列 22 EF(连接字符串,连接复原,DbContext)

原文:asp.net core 系列 22 EF(连接字符串,连接复原,DbContext) 一.连接字符串 在上二篇中,ASP.NET Core 应用程序连接字符串是写死在ConfigureServices代码中,下面介绍通过配置来实现.连接字符串可以存储在 appsettings.json.用户机密存储.其他配置源中. 下面示例演示appsettings.json 中存储的连接字符串,这样不管asp.net core在什么环境(Development.Staging .Production)