在线咨询
QQ咨询
服务热线
服务热线:13125520620
TOP

设计原则

发布时间:2018-3-3 浏览:2949

一、面向对象应用程序开发原则(SOLID)
 
1单一职责原则(SRP)
 
定义:
 
一个类应该只有一个发生变化的原因。这条原则曾被称为内聚性,即一个模块的组成元素之间的功能相关性。
 
为什么要遵守这条原则?
 
如果一个类承担的职责过多,就等于把这些职责耦合到了一起。一个职责的变化可能削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当发生变化时,设计会遭受到意想不到的破坏。
 
运用与辨析
 
例1:记录日志
 
public class Logger
{
        public void LogToFile<T>(T msg);
        public void LogToDB<T>(T msg);
        public void LogToWindows<T>(T msg);
}
这个例子定义了一个日志类,包含三种方法:将日志写入本地文件、数据库或windows系统日志。一般会人为日志类记录日志这个动作算做一个职责,然而事实并非如此,将日志记录到不同的存储介质算作不同的职责。基于这种认识,断定这个类包含了太多的职责,应该将职责分离出来。
 
 
 
例2:一个大的业务层类
 
一个用户履历操作相关的类,包括:用户的教育背景,社会兼职职务,工作经历个人简历,获得的荣誉等,示例如下:
 
 
public class UserResumeService
{
#region 社会兼职
        //添加社会兼职
        public bool AddParttime(int userId, Parttime item)
        {
            //具体实现
        }
        //删除社会兼职信息
        public bool DelParttime(int userId, string parttimeId)
        {
            //具体实现
        }
        //更新社会兼职
        public bool UpdateParttime(int userId, Parttime item)
        {
            //具体实现
        }
        #endregion
 
        #region 教育背景
        //添加教育背景
        public bool AddEducation(int userId, EducationInfo item)
        {
            //具体实现
        }
        //删除教育背景
        public bool DelEducation(int userId, string educationId)
        {
            //具体实现
        }
        //更新教育背景
        public bool UpdateEducation(int userId, EducationInfo item)
        {
            //具体实现
        }
        #endregion        
 
        #region 工作经历
        //添加工作经历
        public bool AddWork(int userId, WorkInfo item)
        {
            //具体实现
        }
        //删除工作经历
        public bool DelWork(int userId, string workId)
        {
            //具体实现
        }
        //更新工作经历
        public bool UpdateWork(int userId, WorkInfo item)
        {
            //具体实现
        }
        #endregion
 
        #region 科研项目
        //添加科研项目
        public bool AddProject(int userId, Project item)
        {
            //具体实现
        }
        //删除科研项目
        public bool DelProject(int userId, string projectId)
        {
            //具体实现
        }
        //更新科研项目
        public bool UpdateProject(int userId, Project item)
        {
            //具体实现
        }
        #endregion
}
 
 这个类实在太大了,以至于不等不用#region将每块功能收起来。虽然这些操作都是针对一个用户的,但这不是一个职责,也不是俩个职责,这个类包含了太多职责,然而这不是一个工具类!如果是工具类还说得过去。解决的办法就是,将这个大类拆为几个小类,每个类表达一个职责,譬如教育背景相关操作归为一个小类,社会兼职相关操作也归为一个小类,其他依次类推。
 
 
 
2 开放封闭原则(OCP)
 
定义
 
软件实体(类、模块、函数等)应该是可以扩展的,但不可修改。
 
为什么要遵守此原则?
 
任何系统在其生命周期都极有可能发生变化,如果不遵循此原则,那么系统将难以应对发生的变化,这很可能迫使我们抛弃现有版本,这会给我们带来极大的损失。
 
违反原则的情形
 
那些包含switch、if/else的代码段极有可能违反了开放封闭原则。
 
运用的方式方法
 
创建出固定的、能够描述一组任意个可能行为的抽象基类或接口,然后针对每一个可能的行为创建一个派生自抽象基类或接口的子类。
 
运用与辨析
 
这种做法的缺点是有可能会产生很多类,这样就增加了代码量。
 
据此修改上面日志记录的例子:
 
定义日志接口
 
public interface ILogger
{
        void Log<T>(T msg);
}
实现接口
 
 
public class LoggerToFile : ILogger
{
        public void Log<T>(T msg)
        {
            //具体实现
        }
}
 
public class LoggerToDB : ILogger
{
        public void Log<T>(T msg)
        {
            //具体实现
        }
}
 
public class LoggerToWindows : ILogger
{
        public void Log<T>(T msg)
        {
            //具体实现
        }
}
 
 
 
3里氏替换原则(LSP)
 
定义
 
子类型能够替换掉它们的基类型,而不影响对象的行为和规则。
 
为什么要遵循此原则?
 
我们要遵循OCP原则,OCP背后的机制是抽象和多态,支持抽象和多态的关键机制是继承(比如C#是这样),那么是什么设计规则支配着这种继承用法?最佳的继承层次特征是什么?如何使我们创建的类层次结构符合OCP?这是本原则要解答的问题。
 
违反原则的情形
 
1)显示的使用if语句或if/else语句去确定一个对象的类型,以便可以选择针对不同对象实现不同操作。
 
2)对于继承是IS-A(是一个)关系,即如果一个新类型的对象被认为和一个已知类型的对象之间满足IS-A关系,那么这个新对象的类应该从这个已有对象的类派生。
 
3)完成的功能少于其基类的派生类通常是不能替换其基类的,因此违反LSP。
 
4)当派生类中抛出基类没有的异常时,违反LSP。
 
运用的方式方法
 
1)基于契约编程
 
契约是通过为每一个方法声明前置条件和后置条件来指定的。要使一个方法得以执行,前置条件必须要为真;执行完毕后,该方法要保证后置条件为真。
 
派生类的前置条件和后置条件规则为:在重新声明派生类中的例程时,只能使用相等或者更弱的前置条件来替换原始的前置条件,只能使用相等或者更强的后置条件来替换原始的后置条件。
 
2)提取公共部分而不使用继承
 
如果一组类都支持一个公共的职责,将这个职责提取出来,放到一个父类中,然后让这组类继承此父类。
 
运用与辨析
 
见接口隔离原则的例子。
 
 
 
4接口隔离原则(ISP)
 
定义
 
不应该强迫客户程序依赖并未使用的方法。接口不应包含所有的对象行为,接口应尽可能的小。这个原则用来处理“胖”接口所存在的缺点。
 
为什么要遵循此原则?
 
如果程序中的一部分更改会影响到程序中完全和它无关的其他部分,那么更改的代价和影响就变得不可预测。
 
违反原则的情形
 
接口污染,即接口被一个它不总是需要的方法污染,也就是说不是此接口的每一个派生类都需要那个方法。但由于接口已经定义了这个方法,那么不需要它的派生类也要实现这个方法。
 
运用的方式方法
 
1)使用委托分离接口
 
对象的客户端不通过该对象的接口去访问它,而是通过委托去访问他。此方案的缺点:委托处理会导致一些很小但仍然存在的运行时间和内存的开销。
 
2)使用多重继承分离接口:通常这种做法是首选的。
 
运用与辨析
 
在web应用开发中使用仓储模式来封装对底层数据库的访问,为此创建IRepository<T>接口:
 
 
public interface IRepository<T>
    {
        T GetById(int id);
        bool Delete(T entity);
        bool Save(T entity);
        void Update(T entity);
        IList<T> Get(string condition);
 
        ......
    }
 
 这是一个典型的胖接口,并不是每一个子类都会实现这么多的方法。对于继承了这个接口却不需要实现其中某些方法的接口,只能将方法体设置为空实现或抛出异常。例如下面的类中不需要实现Get方法,所以在方法体中抛出了异常
 
 
public class MRepository<T> : IRepository<T>
    {
        public T GetById(int id)
        {
            //具体实现
        }
 
        public bool Delete(T entity)
        {
            //具体实现
        }
 
        public bool Save(T entity)
        {
            //具体实现
        }
 
        public void Update(T entity)
        {
            //具体实现
        }
 
        //不需要实现此方法
        public IList<T> Get(string condition)
        {
            throw new NotImplementedException();
        }
}
 
 在接口的实现里抛出异常,这样做显然违背了里氏替换原则(LSP),解决的办法是将IRepository<T>拆分成两个以上的更小的接口,按需实现接口,修改如下:
 
 
public interface IRepository<T>
    {
        T GetById(int id);
        bool Delete(T entity);
        bool Save(T entity);
        void Update(T entity);
 
    }
 
public interface IRepositoryAL<T>
    {
        IList<T> Get(string condition);
}
 
public class MRepository<T> : IRepository<T>
    {
        public T GetById(int id)
        {
            //具体实现
        }
 
        public bool Delete(T entity)
        {
            //具体实现
        }
 
        public bool Save(T entity)
        {
            //具体实现
        }
 
        public void Update(T entity)
        {
            //具体实现
        }
    }
 
 
 
5 依赖倒置原则(DIP)
 
定义
 
高层模块不应依赖于低层模块。二者都应依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。这样高层组件与低层组件之间通过抽象的接口来交换而不是具体类。该原则是框架设计的核心。
 
为什么要遵守此原则?
 
如果高层模块依赖于低层模块,那么对低层模块的改动会直接影响到高层模块,从而迫使他们一次做出改动。
 
违反原则的情形
 
高低层组件通过具体类来实现交互。
 
运用的方式方法
 
“倒置”不仅仅是依赖关系的倒置,也是接口所有权的倒置。当使用DIP原则时,往往客户拥有抽象接口,而他们的服务者则从这些抽象接口派生。
 
启发式的方法:
 
1)找到那些指向具体类的引用的变量。
 
2)找到任何派生自具体类的类。
 
3)找到那些重写方法,而基类方法已经实现过了。
 
运用与辨析
 
依赖倒置式控制反转的精髓,通过控制反转可以深刻的体会到依赖倒置的作用。
 
 
 
6 迪米特法则(LoD,又名最少知道原则)
 
定义
 
一个对象应当对其他对象有尽可能少的了解,只和自己关系最密切对象直接作用。
 
关系最密切的对象含义是:
 
当前对象本身,通过该对象方法参数传入的对象,此类的其他实例化对象,以及其所在聚集类的其他成员。
 
为什么要遵守此原则?
 
降低耦合,减少依赖。
 
违反原则的情形
 
和除了上述关系最密切的对象之间通信。
 
运用的方式方法
 
1)限制类及其成员的访问权限。
 
2)引入门面模式和中介者模式。
 
 
 
7 组合/聚合复用原则(CARP)
 
定义
 
将已有的多个对象组成一个新对象,达到复用的目的。
 
为什么要遵守此原则?
 
在建模的过程中,我们会发现,某些实体之间不具有继承关系,但是他们之间却有一些像是的操作,为了实现这种无法用继承表达的关系,我们遵照CARP原则。
 
 
 
二、DRY原则(不要重复自己)
 
避免重复相同或相似的代码。
 
运用与辨析
 
定义拦截器或过滤器充分体现了DRY原则。
 
例如使用ASP.NET MVC创建企业级应用的过程中,定义了如下的控制器: 
 
 
public class ExcludedDataController : BaseController
{
       [HttpPost]
        public ActionResult Add(ExcludedDataInfo info)
        {
            if (Request.IsAjaxRequest())
            {
               //其他代码
            }
            return new EmptyResult();
        }
 
        public ActionResult Del(ExcludedDataInfo info)
        {
            if (Request.IsAjaxRequest())
            {
                //其他代码
            }
            return new EmptyResult();
        }
 
        public ActionResult BatchAdd(string itemCodes, int currentNavId, int library_DataBase_ID)
       {
            if (Request.IsAjaxRequest())
            {
               //其他代码
            }
            return new EmptyResult();
        }
}
 
 其中三个方法中都调用了Request.IsAjaxRequest()方法,明显违反了DRY原则,解决的办法是可以在控制器上添加拦截器。但是或许此控制器的操作中还有不被Ajax调用的操作,那么可以将这些操作移除,放入一个新的控制器中。
 
 
 
三、控制反转(IoC)
 
控制反转是基于面向对象的原则,提倡松耦合理念的设计原则,允许独立开发应用程序的各个组件。
 
实现方式
 
实现方式有两种:依赖注入,服务定位。
 
依赖注入:引用其他的dll,组件之间的引用,一个类持有另一个类,这些都可以被看做是依赖。最常遇到的是一个类持有另一个类的问题。
 
依赖注入有三种方式:构造函数注入,属性注入,方法注入。最常使用的是构造函数的注入。
 
服务定位:通过IoC容器获取依赖的具体类型,并将其赋给接口。
 
运用与辨析
 
记录Entity Framework执行SQL语句对优化系统有极大的帮助。为记录SQL定扩展命令拦截器IDbCommandInterceptor,在实现的方法中记录SQL。可以将SQL记录到本地文本文件中,也可以将SQL存储到数据库中,实现如下:
 
 
public class CommandInterceptor : IDbCommandInterceptor
    {
        private Logger logger = new Logger();
        
        public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
            this.logger.Log<int>(command, interceptionContext);
        }
//其他方法......
}
 
 上面的实现包含了一个依赖项,即Logger,如果后续改变存储SQL的媒介,那么就要修改Logger.Log这个方法,明显违反了OCP原则,也没有遵循DIP原则。所以将其更改如下:
 
 
public class CommandInterceptor : IDbCommandInterceptor
{
        private ICommandLogger logger;
        public CommandInterceptor(ICommandLogger logger)
        {
            this.logger = logger;
        }
        public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
            this.logger.Log<int>(command, interceptionContext);
        }
 
        //其他代码
 
}
 
 
 

TAG
软件定制,软件开发,瀚森HANSEN
0
该内容对我有帮助