稳定的框架来源于好的设计,好的设计才能出好的作品,掌握面向对象基本原则才会使我们的设计灵活、合理、不僵化,今天就来谈一谈我们.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. java对象与Json字符串之间的转化(fastjson)

    1. 首先引入jar包 在pom.xml文件里加入下面依赖: <dependency> <groupId>com.alibaba</groupId> <art ...

  2. [AST实战]从零开始写一个wepy转VUE的工具

    为什么需要 wepy 转 VUE "转转二手"是我司用 wepy 开发的功能与 APP 相似度非常高的小程序,实现了大量的功能性页面,而新业务 H5 项目在开发过程中有时也经常需要 ...

  3. 从壹开始前后端 [vue后台] 之一 || 权限后台系统 1.0 正式上线

    缘起 哈喽各位小伙伴周三好,春节已经过去好多天了,群里小伙伴也各种催搞了,新年也接了新项目,比较忙,不过还是终于赶上这个二月的尾巴写了这篇文章,也把 vue 权限后台上线了(项目地址:http://1 ...

  4. Qt之加减乘除四则运算-支持负数

    一.效果展示 如图1所示,是简单的四则运算测试效果,第一列为原始表达式,第二列为转换后的后缀表达式,冒号后为结果.表达式支持负数和空格,图中是使用了5组测试数据,测试结果可能不全,如大家发现算法有问题 ...

  5. Scrapy爬虫遇到 ‘Forbidden by robots.txt’的问题

    今天在爬知乎精华时,出现了‘Forbidden by robots.txt’的问题 了解到到scrapy在爬取设定的url之前,它会先向服务器根目录请求一个txt文件,这个文件规定了爬取范围 scra ...

  6. HTML阻止iframe跳转页面并使用iframe在页面内嵌微信网页版

    昨天看到这篇文章[置顶]开源组件NanUI一周年 - 使用HTML/CSS/JS来构建.Net Winform应用程序界面 就想弄一个winform结合html5的一个小东西,突有兴致,想在里面嵌套一 ...

  7. .netcore2.1使用swagger显示接口说明文档

    项目之前开发完接口后,我们还需要写接口说明文档,现在有了swagger方便了很多,可以网页版直接测试,当然了也减少了我们的工作量. 使用swagger生成接口说明文档,大致需要2个步骤 1.从“管理 ...

  8. 【学习笔记Part 2● MySQL】

    数据库 为什么要用数据库 如何去存放数据?生活中有各种各样的数据.比如说人的姓名.年龄.成绩等.平时我们记录这些信息都是记在大脑中.人的记忆力有限,不可能什么都记住.所以后来人们把数据记录在石头上–& ...

  9. [目录]搭建一个简单的WebGIS应用程序

    “如果一件事情超过自己的能力,自己很难达到,那就像是婴儿跳高,不但没有好处,反而拔苗助长”. 4月份时报名参加了2018年ESRI杯GIS应用开发比赛,到前几天提交了作品.作品很简单,没有那么多复杂深 ...

  10. 【原】无脑操作:TypeScript环境搭建

    概述:本文描述TypeScript环境搭建,以及基于VSCode的自动编译设置和调试设置.网络上很多相应文章的方式过时了或者无法试验成功. ------------------------------ ...