C++ 面向对象五大原则
来源:互联网 发布:模具设计软件哪个好 编辑:程序博客网 时间:2024/06/05 18:11
一,理解设计原则与设计模式
软件设计原则:原则为我们提供指南,它告诉我们什么是对的,什么是错的。它不会告诉我们如何解决问题。它仅仅给出一些准则,以便我们可以设计好的软件,避免不良的设计。
软件设计模式:模式是在软件开发过程中总结得出的一些可重用的解决方案,它能解决一些实际的问题。一些常见的模式,比如工厂模式、单例模式等等。
二,单一功能原则
原理:单一职责原则可以看作是低耦合、高内聚在面向对象原则上的引申,将功能定义为引起变化的原因,功能过多可能引起它变化的原因就越多,这将导致功能依赖,相互之间就产生影响,从而极大的损伤其内聚性和耦合度。因此不要为类实现过多的功能,以保证一个类只有一个引起它变化的原因。
应用实例:做一个数据库管理系统,根据不同的权限对数据库进行数据删改查的操作,下面是一个很low的设计。
class DBManager{private: string userId;public: DBManager(const string &str):userId(str){} void add(){ if(userId == "chongchong"){ //执行往数据库里添加数据的操作 } }};
程序分析:
上面这个是很low的设计,如果验证用户权限的规则或数据库的操作发生改变,那就必须对DBManager类进行修改。权限判断的功能和数据库操作的功能被放在一个类中。我们可以使用Proxy模式,来实现功能的分离,下面这个是比较高大上的设计。
class Protocal{public: virtual void add();};class DBManager : public Protocal{private: string userId;public: DBManager(const string &str):userId(str){} void add(){ //添加一条记录到数据库中 }};class Proxy : public Protocal{private: DBManager &manager;public: Proxy(const DBManager &db):manager(db){} void add(){ //先对用户的权限进行验证,然后再执行往数据库添加数据的操作 manager.add(); }};int main() { DBManager manager("123456"); Proxy delegate(manager); delegate.add(); return 0;}
程序分析:
使用代理模式,实现权限判断与数据库操作功能的分离。DBManager类实现数据库操作,代理Proxy类里面进行权限判断。
三,开放封闭原则
原理: 对扩展是开放的,对修改是封闭的。开放封闭原则主要体现在下面两个方面,一是:对扩展开放意味着,软件有新的需求或变化时,可以对现有的代码进行扩展,以满足新的需求。二是:对修改封闭意味着,类一旦设计完成,就不要对类进行任何的修改。“需求总是变化”、“世界上没有一个软件是不变的”,这些言论是对软件需求最经典的表白。对于软件设计者来说,必须在不需要对原有的系统进行修改的情况下,实现灵活的系统扩展。而如何能做到这一点呢? 要面向接口编程,而不是面向实现编程。实现开放封闭的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以对修改就是封闭的;而通过面向对象的继承和对多态机制,通过接口可以派生出新的类,实现新功能的扩展,所以对于扩展就是开放的,这是实施开放封闭原则的基本思路。
应用实例:我们要设计一款射击游戏,在这个游戏中会出现不同的人物角色以及不同的枪支,人物与枪支是最容易扩展的部分,我们把他们隔离开来,形成统一的接口处理。具体的人物角色与枪支都是依赖于这些接口,此时对接口的修改就是封闭的,而通过继承从抽象类派生出新的类,就是对扩展的开放。下面就是类的设计
class Gun{public: virtual void kill();};class MachineGun : public Gun{public: void kill(){ cout<<"MachineGun."<<endl; }};class Character{public: virtual void shoot(){}};class BadGuy : public Character{private: Gun &weapon;public: BadGuy(Gun &gun):weapon(gun){} void shoot(){ cout<<"BadGuy use "; weapon.kill(); }};int main() { MachineGun machineGun; BadGuy badGuy(machineGun); badGuy.shoot(); return 0;}
输出结果:
BadGuy use MachineGun.Process returned 0 (0x0) execution time : 0.006 sPress any key to continue.
程序分析:
所有的具体类都是依赖于接口,具体类之间没有耦合在一起。例如:所有的Character的派生类,都是与接口Gun耦合在一起,都使用的是接口中提供的方法。
四,替换原则
原理:子类应当可以替换父类,并能出现在父类能出现的任何位置上,主要就是继承的体现。继承是一项非常优秀的语言机制,它可以提高代码复用性与代码的可扩展性。这个原则的核心思想就是,良好的继承定义了一个规范。
应用实例:CS是一款经典的射击游戏,我们描述一下里面枪类的实现。
五,依赖倒转原则
1,原理:抽象不能依赖于具体,具体应该依赖于抽象。就是要面向接口编程,而不是面向实现编程。
2,应用实例:
假设我们现在要做一个电商系统,会遇到这样一个问题:订单入库。假设系统设计初期,用的是SQL Server数据库。通常我们会定义一个SqlServer类,用于数据库的读写。
class SqlServer{public: void add(){ cout<<"往数据库添加一个订单."<<endl; }};
然后我们定义一个Order类,负责订单的逻辑处理。由于订单要入库,需要依赖于数据库的操作。因此在Order类中,我们需要定义SqlServer类的变量并初始化。
class Order{private: SqlServer *p;public: Order(){ p = new SqlServer; } void add(){ //先进行订单的逻辑处理,再把这个订单放到数据库 p->add(); }};
定义上面两个类很容易实现我们想要的功能,但是可能有一天,我们不想使用SQL Server数据库,要使用Oracle数据库,那么我们要重新写一个OracleServer类,然后对Order类进行修改。如果我们的底层数据库换成Mysql,又要进行类似的操作,所以我们做的系统的扩展性不强。主要的原因有两个,一是:Order直接依赖于一个具体的类,二是:Order依赖的对象的创建与绑定是在它的内部实现的。高层模块Order类不应该依赖于低层模块SqlServer,两者应该依赖于抽象。我们可以使用IoC(控制反转)来解决上面的问题。IoC有2种常见的实现方式:依赖注入和服务定位。其中,依赖注入是使用最为广泛,下面我们将深入理解依赖注入(DI)。
3,依赖注入(DI):
控制反转(IoC)一种重要的方式,就是将依赖对象的创建和绑定转移到被依赖对象类的外部来实现。在上述的实例中,Order类所依赖的对象SqlServer的创建和绑定是在Order类内部进行的。事实证明,这种方法并不可取。既然,不能在Order类内部直接绑定依赖关系,那么如何将SqlServer对象的引用传递给Order类使用呢?依赖注入(DI),它提供一种机制,将需要依赖(低层模块)对象的引用传递给被依赖(高层模块)对象。通过DI,我们可以在Order类的外部将SqlServer对象的引用传递给Order类对象。那么具体是如何实现呢?
4,使用构造函数来实现注入:
构造函数函数注入,毫无疑问通过构造函数传递依赖。因此,构造函数的参数必然用来接收一个依赖对象。那么参数的类型是什么呢?具体依赖对象的类型?还是一个抽象类型?根据DIP原则,我们知道高层模块不应该依赖于低层模块,两者应该依赖于抽象。那么构造函数的参数应该是一个抽象类型。首选,我们需要定义SqlServer的抽象类型DataAccess。
class DataAccess{public: virtual void add(){}} ;class SqlServer : public DataAccess{public: void add(){ cout<<"往 SQL 数据库添加一个订单."<<endl; }};class Oracle : public DataAccess{public: void add(){ cout<<"往 Oracle 数据库添加一个订单."<<endl; }};
接下里修改Order类
class Order{private: DataAccess &re;public: Order(DataAccess &re):re(re){} void add(){ //先进行订单的逻辑处理,再把这个订单放到数据库 re.add(); }};下面是main函数
int main() { SqlServer sql; //在外部创建依赖对象 Order order1(sql); //通过构造函数注入依赖 order1.add(); Oracle oracle; //在外部创建依赖对象 Order order2(oracle); //通过构造函数注入依赖 order2.add(); return 0;}
输出结果
往 SQL 数据库添加一个订单.往 Oracle 数据库添加一个订单.Process returned 0 (0x0) execution time : 0.009 sPress any key to continue.
程序分析
显然,我们不需要修改Order类的代码,就完成了Oracle数据库的移植,这无疑体现了IoC的精妙。
六,接口分离原则
1,原理:使用多个专门的接口,而不使用单一的总接口,即类不应该依赖那些它不需要的接口。
2,应用实例:
应当为类提供尽可能小的单独的接口,而不要提供大的总接口。在面向对象编程语言中,实现一个接口就需要实现该接口中定义的所有方法,因此大的总接口使用起来不一定很方便。为了使接口的职责单一,需要将大接口中的方法根据其功能不同分别放在不同的小接口中,以确保每个接口使用起来都较为方便,并都承担某一单一角色。接口应该尽量细化,同时接口中的方法应该尽量少,每个接口中只包含一个客户端(如子模块或业务逻辑类)所需的方法即可。例如:在这里我们定义一个接口Worker,在这个接口中包含两个方法eat()与work(),下面是这个接口的实现
class Worker{public: virtual void eat(); virtual void work();};
现在有两个类实现了这个接口,一是Manager,另一个是ChengXuYuan,下面是这两个类的实现
class Manager : public Worker{public: void eat(){ cout<<"Manager eat."<<endl; } void work(){ cout<<"Manager work."<<endl; }};class ChengXuYuan : public Worker{public: void eat(){ cout<<"ChengXuYuan eat."<<endl; } void work(){ cout<<"ChengXuYuan work."<<endl; }};
那我们现在引入一个Robot,来实现上面的Worker接口,work行为对机器人来说是可以接受的,但是eat行为对机器人来说,就非常的不合理。如果我们直接让Robot实现Worker接口,此时的Robot就被迫使用它用不到的接口,当接口Worker发生变化时,它同样也要跟着改变。我们使用适配器模式解决上面的问题,下面是代码
class EatAdapter : public Worker{ void eat();};class WorkAdapter : public Worker{ void work();};
使用适配器模式,把一个大的接口,分成几个小的接口。
- 面向对象五大原则
- 面向对象五大原则
- 面向对象五大原则
- 面向对象五大原则
- 【面向对象】面向对象五大原则
- 面向对象设计五大原则(1)
- 面向对象设计五大原则(2)
- 面向对象设计五大原则(3)
- 面向对象编程五大原则
- 面向对象编程五大原则
- 面向对象编程五大原则
- 面向对象编程五大原则
- 面向对象编程五大原则
- 面向对象编程五大原则
- 面向对象编程五大原则
- 面向对象编程五大原则【转】
- 面向对象的五大原则
- 面向对象设计的五大原则
- 标杆与软件代码质量
- 搭建私有Docker Registry
- 数据结构学习之路1 顺序存储的线性表
- CentOS7安装最新版git教程
- easyui datagrid属性和方法
- C++ 面向对象五大原则
- 自己写的uvc摄像头驱动程序
- 文章标题
- 手动安装maven包
- INSERT INTO SELECT语句与SELECT INTO FROM语句区别
- FZU
- java 线程池
- 需求分析复习思考题:第一章 《软件需求概述》思考题
- Joomla!网站扫描工具joomscan