稳定的框架来源于好的设计,好的设计才能出好的作品,掌握面向对象基本原则才会使我们的设计灵活、合理、不僵化,今天就来谈一谈我们.net 面向对象设计的基本原则。

对于一个没有任何设计经验的开发者来说,如果不假思索和探究式的去设计系统软件的框架,势必会导致系统代码出现这样或者那样的问题,比如:代码复杂和重复,不能剥离出独立的复用组件,系统不稳定等。通过灵活的设计原则,加上一定的设计模式,封装变化,降低耦合,实现软件的复用和扩展,这正是设计原则的最终意义。

我们都知道面向对象的三大要素是封装、继承和多态,这三大完整体系以抽象来封装变化,隐藏具体实现,保护内部信息;继承实现复用;多态改写对象行为。在此基础上我们来聊一聊.net中七大面向对象设计原则:

 一、单一职责原则

    系统好不好,强调的是模块间保持低耦合、高内聚的关系,面向对象设计原则第一条则是:单一职责原则(SRP)。

核心:单一职责原则强调的是职责分离,一个类应该只有一种引起它变化的原因,不应该将变化原因不同的职责封装在一起。

实现单一职责原则最好一个类只做一件事,职责过多,可能引起它变化的原因就很多,这将导致职责依赖,相互之间就会产生影响。

应用如下:

举一个项目中经常用到的例子,比如通过不同的权限来设计数据库不同的操作。

以上的设计中,把数据库的操作类和用户权限的判断封装在一个类中,设计就显得有点僵硬,因为权限的规则变化和数据库的规则变化都会改变DBManager类。好的设计是需要将二者分开,设计如下:

新增类DBManagerProxy,有效实现了职责分离,类DBAction至关注数据库操作,不用担心权限判断,使用职责分离的代码如下:

     /// <summary>
/// 数据库操作实体
/// </summary>
public class DBAction : IDBAction
{
/// <summary>
/// 增加
/// </summary>
public void Add()
{
Console.WriteLine("Add Succeed");
}
/// <summary>
/// 删除
/// </summary>
/// <returns></returns>
public int Delete()
{
Console.WriteLine("Delete Succeed");
return ;
}
/// <summary>
/// 查询页面
/// </summary>
public void View()
{
}
}

DBAction类

      /// <summary>
/// 权限判断和数据操作代理类
/// </summary>
public class DBManagerProxy : IDBAction
{
private IDBAction _dbManager = null;
public DBManagerProxy(IDBAction dbManager)
{
_dbManager = dbManager;
} /// <summary>
/// 获取权限
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public string GetPermission()
{
return "CanAdd,CanDelete";
} /// <summary>
/// 添加
/// </summary>
public void Add()
{
if (GetPermission().Contains("CanAdd"))
{
_dbManager.Add();
}
}
/// <summary>
/// 删除
/// </summary>
/// <returns></returns>
public int Delete()
{
int result = ;
if (GetPermission().Contains("CanDelete"))
{
result = _dbManager.Delete();
} return result;
}
/// <summary>
/// 查询页面
/// </summary>
public void View()
{
}
}

DBManagerProxy类

   public class DBClient
{
public static void Main()
{ IDBAction dBAction = new DBManagerProxy(new DBAction());
dBAction.Add();
dBAction.Delete(); Console.ReadKey();
}
}

DBClient类

  /// <summary>
/// 操作数据库接口
/// </summary>
public interface IDBAction
{
/// <summary>
/// 增加
/// </summary>
void Add();
/// <summary>
/// 删除
/// </summary>
/// <returns></returns>
int Delete();
/// <summary>
/// 查询页面
/// </summary>
void View();
}

IDBAction类

       优点:模块间保持低耦合、高内聚的关系。

       建议:SRP应该由引起变化的原因决定,而不是功能所决定的,这个需要自己在设计的过程中慎重权衡。

 二、开放封闭原则

开放封闭原则(OCP)是面向对象原则的核心,软件本身就是封装变化,降低耦合,而开放封闭原则就是这一目标的直接体现。

核心:对扩展开放,对修改关闭。

在实际系统开发过程中需求是千变万化的,不是一成不变的,所以在一定程度上我们要满足系统本身的可扩展性,这就要求我们在设计能够灵活的扩展。实现这一原则的核心思想就是对抽象编程,而不是对具体编程。让类依赖于固定的抽象,所以对修改就是关闭的;通过对对象的继承和多态机制,实现新的扩展,所以是对扩展是开放的。

实际应用如下:

以上设计是机场售票业务,AirportStaff是业务员处理的业务类,AirportTicketProcess是客户信息类,设计中每个售票员都会面对买票、退票和咨询问题的客户,这样售票员不仅繁忙而且效率低下,在设计上更是拙劣,不利于扩展,为了更好地扩展,改造如下:

IAirportTicketProcess是抽象出来的机场业务接口,不管你是退票、查询还是买票都是业务的一种类型,那么我们只需继承该接口实现不同的业务即可,即便有新增业务类型,只需要继承该接口,仍然不影响原有设计,对于客户来说,根据自己的需求找对应的业务员处理,这样设计清晰职责单一。代码实现如下:

     /// <summary>
/// 机场买票窗口接口
/// </summary>
public interface IAirportTicketProcess
{
/// <summary>
/// 业务处理
/// </summary>
void Process();
}

IAirportTicketProcess类

     /// <summary>
/// 买票类
/// </summary>
public class CollectTicketProcess : IAirportTicketProcess
{
/// <summary>
/// 买票业务
/// </summary>
public void Process()
{
Console.WriteLine("只接受买票业务");
}
}

CollectTicketProcess类

     /// <summary>
/// 查询票务类
/// </summary>
public class ReferTicketProcess : IAirportTicketProcess
{
/// <summary>
/// 查询票务业务
/// </summary>
public void Process()
{
Console.WriteLine("只接受票务查询业务");
}
}

ReferTicketProcess类

     /// <summary>
/// 退票类
/// </summary>
public class RefoundTicketProcess : IAirportTicketProcess
{
/// <summary>
/// 退票业务
/// </summary>
public void Process()
{
Console.WriteLine("只接受退票业务");
}
}

RefoundTicketProcess类

     /// <summary>
/// 业务员类
/// </summary>
public class AirportStaff
{
private IAirportTicketProcess _airportTicketProcess = null;
/// <summary>
/// 具体业务员操作的业务类
/// </summary>
public void HandleProcess(Client client)
{
_airportTicketProcess = client.CreateProcess();
_airportTicketProcess.Process();
}
}

AirportStaff类

     /// <summary>
/// 客户类
/// </summary>
public class Client
{
private string _clientType;
public Client(string clientType)
{
_clientType = clientType;
}
/// <summary>
/// 客户业务处理判断
/// </summary>
/// <returns></returns>
public IAirportTicketProcess CreateProcess()
{
switch (_clientType)
{
case "查询客户":
return new ReferTicketProcess();
case "买票客户":
return new CollectTicketProcess();
case "退票客户":
return new RefoundTicketProcess();
} return null;
}
}

Client类

  public class AirportProcess
{
public static void Main()
{ AirportStaff staff = new AirportStaff();
staff.HandleProcess(new Client("买票客户"));
staff.HandleProcess(new Client("查询客户"));
staff.HandleProcess(new Client("退票客户")); Console.ReadKey();
}
}

AirportProcess类

这种设计无论对业务员还是客户一切都变得简单而有序,如果新增挂失业务,只需要继承接口新增具体挂失类即可

     /// <summary>
/// 挂失处理
/// </summary>
public class LossTicketProcess : IAirportTicketProcess
{
/// <summary>
/// 挂失处理
/// </summary>
public void Process()
{
Console.WriteLine("只接受挂失业务");
}
}

LossTicketProcess类

       优点:有效降低实体与实体之间的耦合度;对容易变化的因素进行抽象处理,从而改善类的内聚性。

       建议:封装变化是实现OCP的重要思路,对于经常发生变化的状态一般将其封装为一个抽象,同时拒绝乱用抽象,只将经常变化的部分进行抽象。

三、依赖倒置原则

核心:依赖存在于类与类、模块与模块之间,核心是依赖于抽象,具体体现在高层模块不应该依赖于底层模块,二者都应该依赖于接口, 抽象不应该依赖于具体,具体应该依赖于对象。缩写DIP

当两个模块发生紧耦合的关系时,最好的办法就是分离接口和实现:在依赖之间定义一个接口,使得高层模块调用接口,底层模块实现接口,达到依赖与抽象的目的。

实际应用如下:

在上述机场售票业务中,还有个不足之处就是在AirportStaff业务员处理的业务类依赖于客户类Client,这样就导致了业务依赖于客户类型,如果新增业务类型,就需要新增对客户的依赖。改造后如下:

可以把客户也抽象一个接口,不同的客户调用对应的业务。代码如下:

     /// <summary>
/// 客户接口
/// </summary>
public interface IClient
{
IAirportTicketProcess CreateProcess();
}

IClient接口

     /// <summary>
/// 买票客户
/// </summary>
public class CollectTicketClient : IClient
{
public IAirportTicketProcess CreateProcess()
{
return new CollectTicketProcess();
}
}

CollectTicketClient类

     /// <summary>
/// 票务查询客户
/// </summary>
public class ReferTicketClient : IClient
{
public IAirportTicketProcess CreateProcess()
{
return new ReferTicketProcess();
}
}

ReferTicketClient类

     /// <summary>
/// 退票客户
/// </summary>
public class ReFoundTicketClient : IClient
{
public IAirportTicketProcess CreateProcess()
{
return new RefoundTicketProcess();
}
}

ReFoundTicketClient类

    public class AirportStaffNew
{
private IAirportTicketProcess _airportTicketProcess = null;
/// <summary>
/// 具体业务员操作的业务类
/// </summary>
public void HandleProcess(IClient client)
{
_airportTicketProcess = client.CreateProcess();
_airportTicketProcess.Process();
}
}

AirportStaffNew类

     public class AirportProcessNew
{
public static void Main()
{ AirportStaffNew staff = new AirportStaffNew();
staff.HandleProcess(new ReFoundTicketClient());
staff.HandleProcess(new ReferTicketClient()); Console.ReadKey();
}
}

AirportProcessNew类

这样AirportStaffNew依赖于接口,更方便扩展。

    优点:系统稳定,降低耦合度。

    建议:依赖于抽象是一个通用的规则,特殊情况也会依赖于细节,必须权衡好抽象和具体的取舍。

四、接口隔离原则

核心:使用多个小的专门接口,不建议使用大的总接口。缩写ISP

这个比较好理解,我就直接上设计。

实际应用如下:

这个接口虽然功能都能实现要求,但是比较臃肿,更好地设计如下:

优点:有效的将抽象和细节分开。
       建议:将功能相近的接口合并,可能造成接口污染,最好使用内聚的接口。

五、Liskov替换原则

又称里氏替换原则,是实现开放封闭原则的具体规范,这个大家只需要做一个了解。

核心:子类必须能够替换其基类。缩写LSP

它主要在继承机制中约束规范,子类必须能够替换其基类才能保证系统在运行期内识别子类,这是保证复用的基础。LSP要求基类是virtual方法那么子类中就必须重写该方法,如果子类有基类没有的方法或者成员都违反LSP原则。

优点:增强系统的扩展性,基于多态的机制能够减少代码冗余。
      建议:子类必须满足基类和客户端对其的行为约定,子类的异常必须控制在基类可以预计的范围内。

六、合成/聚合复用原则

新对象中聚合已有的对象使之成为新对象的成员,少继承、多聚合。缩写CARP。

 七、迪米特法则

     最少知道原则,软件实体应该尽可能少的和其他软件实体发生相互作用。缩写LoD。

以上是我对.NET面向对象设计原则的总结,欢迎纠错。

.net面向对象设计原则的更多相关文章

  1. Java程序员应该了解的10个面向对象设计原则

    面向对象设计原则: 是OOPS(Object-Oriented Programming System,面向对象的程序设计系统)编程的核心,但大多数Java程序员追逐像Singleton.Decorat ...

  2. UML类图与面向对象设计原则

    1. 引言     从大一开始学习编程,到如今也已经有两年了.从最初学习的Html,Js,JaveSe,再到JavaEE,Android,自己也能写一些玩具.学习过程中也无意识的了解了一些所谓的设计模 ...

  3. 【OOAD】面向对象设计原则概述

    软件的可维护性和可复用性 知名软件大师Robert C.Martin认为一个可维护性(Maintainability) 较低的软件设计,通常由于如下4个原因造成: 过于僵硬(Rigidity)  ...

  4. C++ 设计模式2 (面向对象设计原则)

    1. 变化是复用的天敌! 面向对象设计的最大优势在于 : 抵御变化 2. 重新认识面向对象 理解隔离变化: 从宏观层面来看,面向对象的构建方式更能适应软件的变化, 能将变化所带来的影响减为最小. 各司 ...

  5. 面向对象设计原则OO

    面向对象设计原则是OOPS(Object-Oriented Programming System,面向对象的程序设计系统)编程的核心,但大多数Java程序员追逐像Singleton.Decorator ...

  6. (转)Java程序员应该了解的10个面向对象设计原则

    面向对象设计原则是OOPS(Object-Oriented Programming System,面向对象的程序设计系统)编程的核心,但大多数Java程序员追逐像Singleton.Decorator ...

  7. Java程序员应当知道的10个面向对象设计原则

    面向对象设计原则是OOPS编程的核心, 但我见过的大多数Java程序员热心于像Singleton (单例) . Decorator(装饰器).Observer(观察者) 等设计模式,而没有把足够多的注 ...

  8. 【面向对象设计原则】之里氏替换原则(LSP)

    里氏代换原则由2008年图灵奖得主.美国第一位计算机科学女博士Barbara Liskov教授和卡内基·梅隆大学Jeannette Wing 教授于1994年提出,所以使用的是这位女博士的性命名的一个 ...

  9. 【面向对象设计原则】之依赖倒置原则(DIP)

    依赖倒转原则(Dependency Inversion  Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象.换言之,要针对抽象(接口)编程,而不是针对实现细节编程. 开闭原则( ...

  10. 【面向对象设计原则】之接口隔离原则(ISP)

    接口隔离原则(Interface  Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口. 从接口隔离原则的定义可以看 ...

随机推荐

  1. 搭建SpringCloud-Eureka 注册中心以及服务提供与调用

    纸上得来终觉浅,绝知此事要躬行啊~果然看着很easy,自己搞起来就是各种坑~各位看官,容我慢慢道来~ 关于springcloud是什么我就不废话了~ Eureka  Eureka(原来以为是缩写,原来 ...

  2. 为什么面试你要25K,HR只给你20K?

    周末了,我们来聊个轻松的话题,关于涨薪,哈哈~ 前阵子,栈长给大家分享了<为什么公司宁愿 25K 重新招人,也不给你加到 20K?>,今天我们来聊一个差不多的话题: 为什么面试你要25K, ...

  3. Element-ui使用技巧

    使用第三方字体包 把下载后的*.zip字体包放到项目中在main.js中引用. import "@/assets/font/iconfont.css"; 注意一定要放到elemen ...

  4. synchronized关键字简介 多线程中篇(十一)

    前面说过,Java对象都有与之关联的一个内部锁和监视器 内部锁是一种排它锁,能够保障原子性.可见性.有序性 从Java语言层面上说,内部锁使用synchronized关键字实现 synchronize ...

  5. IdentityModel 中文文档(v1.0.0) 目录

    欢迎使用IdentityModel文档! 第一部分 协议客户端库 第1章 发现端点(Discovery Endpoint) 第2章 授权端点(Authorize Endpoint) 第3章 结束会话端 ...

  6. 14 ,CSS 文字与文本

    1.CSS 中长度与颜色 2.CSS 中的文字属性 3.CSS 中的文本属性 14.1 CSS 中长度与颜色 长度单位 说明 in 英寸 cm 公分 mm 公里 cm 以目前字体高度为单位 ex 以小 ...

  7. 供应链管理为什么要上企业自主可控的免费开源ERP Odoo

    引言 今天的很多企业,无论是制造业,还是商贸行业,如果说没有针对供应链管理的信息系统,那可能是真的冤枉他们了:采购.仓存.销售.存货核算这些模块,早早的买来,早早的用上了,但也早早的被下了结论:食之无 ...

  8. Flutter 异常处理之图片篇

    背景 说到异常处理,你可能直接会认为不就是 try-catch 的事情,至于写一篇文章单独来说明吗? 如果你是这么想的,那么本篇说不定会给你惊喜哦~ 而且本篇聚焦在图片的异常处理. 场景 学以致用,有 ...

  9. 【原】Oracle EBS 11无法打开Form及Form显示乱码的解决

    问题:Oracle EBS 11无法打开Form及Form显示乱码 解决: 1.尝试使用jre1.5或1.6安装目录下jre/bin/server目录里的jvm.dll替换JInitiator安装目录 ...

  10. SQLServer之集合

    集合的定义 集合是由一个或多个元素构成的整体,在SQLServer中的表就代表着事实集合,而其中的查询就是在集合的基础上生成的结果集.SQL Server的集合包括交集(INTERSECT).并集(U ...