您的位置:首页 > 编程语言 > C#

C#设计模式前奏-面向对象设计原则

2016-01-08 15:13 393 查看
在学习设计模式之前,面向对象设计原则是必须要了解的东西。因为大多数设计模式都遵循这些设计原则中的一种或者多种。今天就带大家一起去学习学习七类面向设计原则。首先列出常用的7中面向对象设计原则。下面的这个表格里面的内容有些比较抽象,您可能看不懂,但是没关系,后面我会做详细解释,并且有配套的视频教程。

7种常用的面向对象设计原则

设计原则名称

定义
单一职责原则(SRP)
一个对象应该只包含单一的职责,并且该职责被完全封装在一个类中

开闭原则(OCP)
软件实体应当对扩展开放,对修改关闭

里氏代换原则(LSP)
所有引用基类的地方必须透明的使用其子类的对象

依赖倒转原则(DIP)
高层模块不应该依赖低层模块,他们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象

接口隔离原则(ISP)
客户端不应该依赖那些他不需要的接口

合成复用原则(CRP)
优先使用对象组合,而不是继承来达到复用的目的

迪米特法则(LoD)
每一个软件单位对其他单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位

一、单一职责原则

单一职责原则是最简单的设计原则,它用来控制类的粒度,其实通俗一点说就是控制一个类能承担多大的职责,再者就是一个类应该只包含某一类职责。比如说最常用的SQLHleper类,它承担的职责就是封装和数据库操作有关的操作。下面举一个简单的例子来说。

public class Student
{
public SqlConnection GetConnection()//连接数据库
{
string str = "";
return new SqlConnection(str);
}
public string GetStudentName()//查询学生姓名
{
SqlConnection conn = GetConnection();//连接数据库
//省略查询信息
return "son";
}
public void DelStudent(string studentId )//根据学号删除学生
{
SqlConnection conn = GetConnection();//连接数据库
//省略删除操作
}

}


这段代码我相信很多初学者都非常熟悉。这是一个学生类Student类,里面有三个方法GetSqlconnection(),GetStudentName()和DelStudent(string studentId)。他们的作用分别是连接数据库,查询学生姓名,根据学号删除学生信息。因为查询学生信息和删除学生要链接数据库进行操作,所以对于初学者来说,比较习惯把他们写在一起。但是这个时候我们的写法就违背了单一职责原则。因为这个类既承担了和数据库有关的操作,又承担了和学生信息有关的增删操作.它承担了两类职责。而且一般情况下某个类承担的职责可以直接从类名上体现出来,比如Student类,它的职责就是处理和学生有关的一些操作。

那我们怎么做才能让它符合单一职责原则呢,把不同的职责提取出来放到不同的类中就可以。接下来看下一段代码:

public class Student
{

public string GetStudentName()//查询学生姓名
{
SqlConnection conn = SQLHelper.GetConnection();//连接数据库
//省略查询信息
return "son";
}
public void DelStudent(string studentId )//根据学号删除学生
{
SqlConnection conn = SQLHelper.GetConnection();//连接数据库
//省略删除操作
}
}
public class SQLHelper
{
public static SqlConnection GetConnection()//连接数据库
{
string str = "";
return new SqlConnection(str);
}
}


我们把GetConnection()方法单独拿出来放到SQLHelper类中,在Student类中去调用它就OK。这个时候我们的代码就符合了单一职责原则啦!

最后我把两种写法的类图贴出来方便大家理解:



二、开闭原则

这个原则里面的开指的是对扩展开放,闭指的是对修改关闭。

通俗一点说就是我们在扩展(开)系统的时候尽量不修改(闭)源代码。这几乎也是我们开发软件的终极目标。但实际上在任何一个软件里面,这个开闭原则几乎是不可能百分之百达到。通常我们扩展系统的时候,多多少少都会修改源代码。

这个原则就不给大家举例子了,我相信很容易理解的。

为了满足开闭原则,我们就要对系统进行抽象化设计,抽象化是开闭原则的关键所在。在直接一点说就是我们要针对抽象编程,而不是针对实现编程。

开闭原则一直以来都是评判一个软件灵活性和可扩展性的一个重要依据。

三、里氏代换原则

这个原则的定义是:所有引用基类的地方必须能透明的使用使用子类对象。这句话可能比较抽象,我的理解就是,在程序中使用基类进行定义,在运行时确定其子类类型。

假如说一个网站上的会员有普通会员,VIP会员,同时VIP的等级也可能有差距。现在我们要实现一个功能,想不同类型的会员发

送不同的系统邮件。

如果用比较原始的方法,我们可能会这么实现:



这个时候我们在邮件发送类里面写了三个方法,分别是给三种不同类型的会员发送系统邮件,如果这个时候我们要新增一种会员

类型,那就必须要修改邮件发送类EmailSender,这点其实违背了上面的开闭原则(对修改关闭,而我们修改了EmailSender)。那如何解决这个问题呢,里氏替换原则在这里就起了作用。这三种会员类型我们可以给他抽象出一个父类Member,SendEmail类直接针对Member编程就可以。因为Member可以替换子类(Member m=new CommonMember)。大家先看下面的类图:



如果这个时候再增加会员类型,那只需要再增加一个类(假设这个类是VIP3Member)让他继承Member即可,并不需要修改EmailSender类中的源代码。这不就做到了在不修改源代码的前提下扩展了系统吗。这样迎合了里氏替换原则的原始定义。在程序里面我使用的Member,但在运行的时候我并不是给Member类发送邮件,而是给它的子类发送邮件。

四、依赖倒转原则

简单来说,这个原则就是要求我们做到一点:针对接口编程,不要针对实现编程。

假如说,有一个狗Dog类,现在要使用这个类,一般的做法就是直接New一个Dog对象的实例(Dog d=new Dog()),这个时候我们就是在针对具体的类编程也就是针对实现编程,这个时候程序和Dog这个类之间就是紧耦合。那如何让他针对抽象编程呢?

给他定义一个抽象父类或者接口Animal,当使用这个类的时候我们可以这么写(Animal d=new Dog()),这个时候我们是针对Animal编程也就是针对抽象编程。同时实现了程序本身和Dog这个类的解耦,他们之间已经成了松耦合。

OK,说到这里,简单总结一下开闭原则,里氏代换原则,依赖倒转原则之间的关系:开闭原则是目标,里氏代换原则是基础,而依赖倒转原则是手段。他们相辅相成,相互补充。共同使我们的软件具有良好的扩展性。

五、接口隔离原则

这个原则和第一个单一职责原则有点类似。接口隔离原则是用来控制接口的粒度的,单一职责原则是用来控制类的粒度的。接口

职责原则指的是接口不能太大,如果接口里面的方法很多,我么可以考虑将这个接口分成一些更细小接口,一个接口只让它承担一

类职责或者一类角色即可。这个原则在这里就不多解释了。

六、合成复用原则

这个原则要求我们尽量用组合关系达到复用,而不是使用继承。继承复用有一定的局限性,比如说他会把基类的实现暴露给子

类,如果基类发生改变那么它的所有子类都要发生改变。而组合复用恰恰解决了这些问题,相对于继承来说,组合复用的耦合度较

低。而且可以在运行时动态编程。这个原则我可能也理解的不透彻,就只能说到这里了。

七、迪米特法则

这个原则的定义是每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。

通俗一点理解吧,我们开发的软件会包含很多模块,他们相互直接也有一定的联系。儿迪米特法则要求:各个模块之间能没有联系的话尽量没有联系,能不发生交互的话就不要让它们之间发生交互。这样当其中一个模块发生修改是,可以尽量少的影响到其他模块。

下一篇中讲给大家讲解简单工厂模式。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: