一 Single Responsibility Principle——单一职责原则

核心思想: 一个类应该只有一个引起它变化的原因.

假设存在这样的设计. Rectangle类具有两个方法,一个方法是计算矩形的面积 , 另一个方法是把矩形绘制在屏幕上.

CaculateArea方法只会进行简单的数学运算,而Draw方法则调用GUI组件实现绘制矩形的功能. 显然,这个类就包含了两个不同的职责了. 那这样又会带来什么问题呢? 考虑这样一个场景:现在有一个几何学应用程序调用了这一个类,已便实现计算面积的功能,在这个程序中不需要用到绘制矩形的功能. 问题一:部署几何应用程序需要把GUI组件一同部署,而且这个组件根本没有使用到.问题二:对Rectangle类的改变,比如Draw方法改用另外一套GUI组件,必须对几何应用程序进行一次重新部署.

可见,一个类如果承担的职责过多,就等于把职责耦合在一起了,容易导致脆弱的设计,带来额外的麻烦. 在实际开发中, 业务规则的处理和数据持久化一般是不同时存在同一个类中的,业务规则往往会频繁地变化,而持久化的方式却不会经常性地变化.如果这两个职责混合在同一个类中,业务规则频繁变化导致类的修改,只调用持久化方法的类也必须跟着重新编译,部署的次数常常会超过我们希望的次数. 对业务规则和持久化任务的职责分离就是遵循单一职责原则的体现.

对上述Recangle类可进行这样的修改:

二 Open Closed Principle——开放封闭原则

核心思想:对扩展开放,对修改封闭.

"需求总是变化的." 拥抱变化似乎就是软件开发的真理之一. 经常会有这样令人沮丧的情景出现:新的需求来了,对不起,我的代码设计必须大幅度推倒重来. 设计的坏味道让我们深受其害,那么怎样的设计才能面对需求的改变却可以保持相对稳定呢?

针对这样的问题,OCP给了我们如下的建议:在发生变化的时候,不要修改类的源代码,要通过添加新代码来增强现有类的行为.

对扩展开放,对修改封闭,这两个特征似乎就是相互矛盾的. 通常观念来讲,扩展不就是修改源代码吗?怎么可能在不改动源代码的情况下去更改它的行为呢?

答案就是抽象(Interface 和 抽象基类).实现OCP的核心思想就是对抽象编程. 让类依赖于固定的抽象,对修改就是封闭的; 而通过面向对象的继承和多态机制,通过覆写方法改变固有行为,实现新的扩展方法,对于扩展就是开放的.

来看一个例子. 实现一个能够根据客户端的调用要求绘制圆形和长方形的应用程序. 初始设计如下:

View Code

public class Draw
{public void DrawRectangle(){//绘制长方形}public void DrawCircle(){//绘制圆形}
}public enum Sharp
{/// <summary>/// 长方形/// </summary>Rectangle ,/// <summary>/// 圆形/// </summary>Circle ,
}public class DrawProcess
{private Draw _draw = new Draw();public void Draw(Sharp sharp){switch (sharp){case Sharp.Rectangle:_draw.DrawRectangle();break;case Sharp.Circle:_draw.DrawCircle();break;default:throw new Exception("调用出错!");}}
}//调用代码
DrawProcess draw = new DrawProcess();
draw.Draw(Sharp.Circle);

现在的代码可以正确地运行. 一切似乎都趋近于理想. 然而,需求的变更总是让人防不胜防. 现在程序要求要实现可以绘制正方形. 在原本的代码设计下,必须做如下的改动.

View Code

//在Draw类中添加
public void DrawSquare()
{//绘制正方形
}//在枚举Sharp中添加/// <summary>/// 正方形/// </summary>Square ,//在DrawProcess类的switch判断中添加
case Sharp.Square:_draw.DrawSquare();break;

需求的改动产生了一系列相关模块的改动,设计的坏味道悠然而生. 现在运用OCP, 来看一下如何对代码进行一次重构.

View Code

/// <summary>
/// 绘制接口
/// </summary>
public interface IDraw
{void Draw();
}public class Circle:IDraw
{public void Draw(){//绘制圆形}
}public class Rectangle:IDraw
{public void Draw(){//绘制长方形}
}public class DrawProcess
{private IDraw _draw;public IDraw Draw { set { _draw = value; } }private DrawProcess() { }public DrawProcess(IDraw draw){_draw = draw;}public void DrawSharp(){_draw.Draw();}
} //调用代码
IDraw circle = new Circle();
DrawProcess draw = new DrawProcess(circle);
draw.DrawSharp();

假如现在需要有绘制正方形的功能,则只需添加一个类Square 即可.

View Code

public class Square:IDraw
{public void Draw(){//绘制正方形}
}

只需新增加一个类且对其他的任何模块完全没有影响,OCP出色地完成了任务.

如果一开始就采用第二种代码设计,在需求的暴雨来临时,你会欣喜地发现你已经到家了, 躲过了被淋一身湿的悲剧. 所以在一开始设计的时候,就要时刻地思考,根据对应用领域的理解来判断最有可能变化的种类,然后构造抽象来隔离那些变化. 经验在这个时候会显得非常宝贵,可能会帮上你的大忙.

OCP很美好,然而绝对的对修改关闭是不可能的,都会有无法对之封闭的变化. 同时必须清楚认识到遵循OCP的代价也是昂贵的,创建适当的抽象是要花费开发时间和精力的. 如果滥用抽象的话,无疑引入了更大的复杂性,增加维护难度.

三 Liskov Subsitution Principle——里氏替换原则

核心思想: 子类必须能够替换掉它们的父类型.

考虑如下情况:

View Code

public class ProgrammerToy
{private int _state;public  int State{get { return _state; }}public virtual void SetState(int state){_state = state;}
}public class CustomProgrammerToy:ProgrammerToy
{public override void SetState(int state){//派生类缺乏完整访问能力,即无法访问父类的私有成员_state//因此该类型也许不能完成其父类型能够满足的契约}
} //控制台应用程序代码
class Program
{static void Main(string[] args){ProgrammerToy toy = new CustomProgrammerToy();toy.SetState(5);Console.Write(toy.State.ToString());}
}

从语法的角度来看, 代码没有任何问题. 不过从行为的角度来看 , 二者却存在不同. 在使用CustomProgrammerToy替换父类的时候, 输出的是0而不是5, 与既定的目标相差千里. 所以不是所有的子类都能安全地替换其父类使用.

前面谈到的开发封闭原则和里氏替换原则存在着密切的关系. 实现OCP的核心是对抽象编程, 由于子类型的可替换性才使得使用父类类型的模块在无需修改的情况下就可以扩展, 所以违反了里氏替换原则也必定违反了开放封闭原则.

庆幸的是, 里氏替换原则还是有规律可循的.父类尽可能使用接口或抽象类来实现,同时必须从客户的角度理解,按照客户程序的预期来保证子类和父类在行为上的相容.

四 InterFace Segregation Principle——接口隔离原则

核心思想:使用多个小的专门的接口,而不要使用一个大的总接口.

直接来看一个例子: 假设有一个使用电脑的接口

            

程序员类实现接口IComputerUse, 玩游戏,编程,看电影, 多好的事情.

现在有一个游戏发烧友,他也要使用电脑, 为了重用代码 , 实现OCP, 他也实现接口IComputerUse

看出什么问题了吗? GamePlayer PlayGame无可厚非,WatchMovies小消遣, 但要编程干什么?

这就是胖接口带来的弊端,会导致实现的类必须完全实现接口的所有方法, 而有些方法对客户来说是无任何用处的,在设计上这是一种"浪费". 同时,如果对胖接口进行修改, 比如程序员要使用电脑配置为服务器, 在IComputerUse上添加Server方法, 同样GamePlayer也要修改(这种修改对GamePlayer是毫无作用的),是不是就引入了额外的麻烦?

所以应该避免出现胖接口,要使接口实现高内聚(高内聚是指一个模块中各个部分都是为完成一项具体功能而协同工作,紧密联系,不可分割). 当出现了胖接口,就要考虑重构.优先推荐的方法是使用多重继承分离,即实现小接口.

将IComputerUse拆分为IComputerBeFun和IComputerProgram, Progammer类则同时实现IComputerBeFun和IComputerProgram接口,现在就各取所需了.

与OCP类似, 接口也并非拆分地越小越好, 因为太多的接口会影响程序的可读性和维护性,带来难以琢磨的麻烦. 所以设计接口的时刻要着重考虑高内聚性, 如果接口中的方法都归属于同一个逻辑划分而协同工作,那么这个接口就不应该再拆分.

五 Dependency Inversion Principle——依赖倒置原则

核心思想: 高层模块不应该依赖底层模块,两者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。

当一个类A存在指向另一个具体类B的引用的时候,类A就依赖于类B了。如:

View Code

/// <summary>
/// 商品类
/// </summary>
public class Product
{public int Id { get; set; }
}/// <summary>
/// 商品持久化类
/// </summary>
public class ProductRepository
{public IList<Product> FindAll(){//假设从SQL Server数据库中获取数据return null;}
}/// <summary>
/// 商品服务类
/// </summary>
public class ProductService
{private ProductRepository _productRepository;public IList<Product> GetProducts(){_productRepository = new ProductRepository();return _productRepository.FindAll();}
}

(在前面单一职责原则中有提到,业务逻辑处理和对象持久化分属两个职责,所以应该拆分为两个类。)高层模块ProductService类中引用了底层模块具体类ProductRepository,所以ProductService类就直接依赖于ProductRepository了。那么这样的依赖会带来什么问题呢?

"需求总是那么不期而至"。原本ProductRepository是从SQL Server数据库中读存数据,现在要求从MySQL数据库中读存数据。由于高层模块依赖于底层模块,现在底层模块ProductRepository发生了更改,高层模块ProductService也需要跟着一起修改,回顾之前谈到的设计原则,这是不是就违反了OCP呢?OCP的核心思想是对抽象编程,DIP的思想是依赖于抽象,这也让我们更清楚地认识到,面向对象设计的时候,要综合所有的设计原则考虑。DIP给出了解决方案:在依赖之间定义一个接口,使得高层模块调用接口,而底层模块实现接口,以此来控制耦合关系。(在上面OCP的例子中,也是使用了这一个方法。)所以可以对代码做如下的重构:

View Code

/// <summary>
/// 商品持久化接口
/// </summary>
public interface IProductRepository
{List<Product> FindAll();
}/// <summary>
/// 商品持久化类
/// </summary>
public class ProductRepository:IProductRepository
{public IList<Product> FindAll(){//假设从SQL Server数据库中获取数据return null;}
}/// <summary>
/// 商品服务类
/// </summary>
public class ProductService
{private IProductRepository _productRepository;private ProductService() { }//使用构造函数依赖注入public ProductService(IProductRepository productRepository){_productRepository = productRepository;}public IList<Product> GetProducts(){return _productRepository.FindAll();}
}

现在已对变化进行了抽象隔离,再根据OCP,我相信实现从MySQL数据库中读存数据的需求已经可以被轻松地解决掉了。

原文链接:http://www.cnblogs.com/teroy/archive/2013/04/16/2757029.html

面向对象设计五大原则相关推荐

  1. 深入浅出精讲面向对象设计七大原则,彻底领悟设计背后思想

    深入浅出精讲面向对象设计七大原则,彻底领悟设计背后思想 欢迎阅读 一.面向对象设计原则提出背景 二.面向对象设计七大原则总览 三.单一职责原则(SRP) 3.1定义: 3.2分析: 3.3举例: 四. ...

  2. 面向对象的五大原则与IOC

    面向对象的五大基本原则 单一职责原则(SRP) 开放封闭原则(OCP) 里氏替换原则(LSP) 依赖倒置原则(DIP) 接口隔离原则(ISP) 单一职责原则(SRP) 一个类应该仅有一个引起它变化的原 ...

  3. 【设计模式系列】之OO面向对象设计七大原则

    1  概述 本章叙述面向向对象设计的七大原则,七大原则分为:单一职责原则.开闭原则.里氏替换原则.依赖倒置原则.接口隔离原则.合成/聚合复用原则.迪米特法则. 2  七大OO面向对象设计 2.1 单一 ...

  4. 转载(面向对象设计的原则)

    这是设计模式系列开篇的第一篇文章.也是我学习设计模式过程中的总结.这篇文章主要讲的是面向对象设计中,我们应该遵循的六大原则.只有掌握了这些原则,我们才能更好的理解设计模式. 我们接下来要介绍以下6个内 ...

  5. 面向对象的五大原则是什么

    5大原则 单一职责原则(Single-Resposibility Principle) 其核心思想为:一个类,最好只做一件事,只有一个引起它的变化.单一职责原则可以看做是低耦合.高内聚在面向对象原则上 ...

  6. 实例图解设计模式之面向对象设计七大原则

    文章目录 面向对象设计原则详解 面向对象设计原则概述 常用的面向对象设计原则 单一职责原则(Single Responsibility Principe)SRP ★★★★☆ 开闭原则(Open-Clo ...

  7. 面向对象设计七大原则

    简述 原则都是前人总结出来的经验,遵循这些原则,让我们开发的程序更加健壮易维护.七大原则之间并不是相互孤立的,彼此间存在着一定关联,一个可以是另一个原则的加强或是基础.违反其中的某一个,可能同时违反了 ...

  8. 秒杀系统的设计五大原则

    最近订阅了 极客时间 | 如何设计一个秒杀系统,这里是每章的学习笔记. 链接:如何设计一个秒杀系统 架构原则:4要 1 不要! 1.数据要尽量少. 2.请求数要尽量少. 3.请求路径要短. 4.依赖要 ...

  9. 架构设计五大原则 SOLID五大设计原则

    SRP:单一职责原则 容易误认为是指:一个软件模块都应该只做一件事,实际上并不准确.单一职责原则准确描述应该是:一个软件模块都应该只对一类行为负责. 如果多个特性不同的项目同时依赖同一个底层接口,只要 ...

最新文章

  1. python函数的定义域_python中多线程的变量定义域问题
  2. python视频课程推荐-《Python从小白到大牛》全集视频课程介绍
  3. Python数据分析之初识numpy常见方法使用案例
  4. StyleAI:印象坐标-感情色彩量化/感情近邻关系
  5. 每个tabpage中都有一个dategridview_其实每个人都是一个孩子,仅此而已
  6. Java对象的序列化
  7. Linux内核深入理解中断和异常(2):初步中断处理-中断加载
  8. 西双版纳真的适合养老吗?
  9. 【网络流24题】【LOJ6013】负载平衡(环形纸牌均分,最小费最大流)
  10. php 生成word的三种方式
  11. 精通Hyperledger之Hyperledger composer查询语言(17)
  12. 老毛桃u盘装系统linux,老毛桃如何安装iso系统_老毛桃u盘启动安装iso系统教程
  13. cygwin装linux系统,给cygwin安装命令package
  14. android APK 查看程序MD5
  15. 亲自动手制作来自MyBatis-Spring官网的chm格式帮助文件
  16. 计算机打字在哪点,电脑打字在哪里打开
  17. vue里面watch监听当前路由信息
  18. Android使用exchange日历,使用Exchange服务实现跨平台(PC+Web + 移动端)日历的日程管理...
  19. 机器学习总结(二):梯度消失和梯度爆炸
  20. java计数器map_计数器(Counter)

热门文章

  1. FLUENT求解传热系数surfaceheattransfercoef.的参考值的设置【转载】
  2. mysql数据库精确匹配搜索_mysql 模糊搜索方法
  3. Unreal狼人变身特效解析
  4. 【VC ++ 2010】 C语言 计算机二级编译器 Visual C ++ 2010 Express(中文学习版)的安装与使用
  5. p1口实验_CC2530-GPIO输出控制实验-Led循环闪烁(1)
  6. 设计模式之美-王争-极客时间-返现24元 限时优惠
  7. OpenHarmony#深入浅出学习eTs#(五)eTs语言初识
  8. 机器学习算法Part1 基本数学概念
  9. aspose文件预览,WORD正确,PPT转pdf找不到字体,且中文乱码
  10. 小程序点读第一天uniapp开发