面试官问你如何进行程序设计?——设计模式之七大原则——接口隔离、合成复用、迪米特法则以及C++设计实现
设计模式的设计原则之2.0
- 七大原则
- 5、接口隔离(InterfaceSegregation Principle,ISP)
- 5.1、背景
- 5.2、定义
- 5.3、特征
- 5.4、应用
- 6、迪米特原则(Law of Demeter,LoD)
- 6.1、背景
- 6.2、定义
- 6.3、特征
- 6.4、应用
- 7、合成复用原则(Composite Reuse Principle, CRP)
- 7.1、背景
- 7.2、定义
- 7.3、特征(为什么使用合成/聚合复用,而不使用继承复用?)
- 7.4、应用
- 参考
七大原则
设计原则名称 | 作用 |
---|---|
单一职责原则 | 类的职责要单一,不能将太多的职责放在一个类中 |
开闭原则 | 软件实体对扩展是开放的,但对修改是关闭的,即在不修改一个软件实体的基础上去扩展其功能 |
里氏代换原则 | 在软件系统中,一个可以接受基类对象的地方必然可以接受一个子类对象 |
依赖倒转原则 | 要针对抽象层编程,而不要针对具体类编程(抽象不应该依赖细节,细节应该依赖抽象) |
接口隔离原则 | 使用多个专门的接口来取代一个统一的接口 |
合成复用原则 | 在系统中应该尽量多使用组合和聚合关联关系,尽量少使用甚至不使用继承关系 |
迪米特法则 | 一个软件实体对其他实体的引用越少越好,或者说如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,而是通过引入一个第三者发生间接交互 |
前四种原则——》》》单一职责、里氏代换、开闭原则、依赖倒转
5、接口隔离(InterfaceSegregation Principle,ISP)
5.1、背景
我们生活中用到的智能手机和智能手表,它们有类似的接口功能,也有不同的功能,比如
- 手机(phone):看时间、看天气、听音乐、看视频
- 智能手表(smartwatch):看时间、看天气、测心率
一个接口中封装了太对的方法,导致
phone
和Smartwatch
这两个类中必须实现一些无用的方法。这样的接口稳定性较差,如果
phone
需要增加一个方法的话,那么`Smartwatch这个实现类中也要相应的实现这个方法(当然方法体内是空的,但是必须要实现的)。编码混乱,导致修改时难度增加(需要自己去区分开哪些是这个类中的方法,哪些是另外的一个类中的方法,这样额外增加了工作量)。
应该怎么设计?
将公共的部分抽取出来单独放在一个接口中,自己独有的行为放在相应的接口中,通过独有的这个接口去继承公共的接口,这样的话,就能很好的起到接口的隔离的作用。这个地方我只是举了这样的一个例子,公共的部分是show
,那么在实际的使用中,可能是别的相关的功能等。那样的话需要自己去对他们进行抽取。
5.2、定义
客户端不应该依赖它不需要的接口;类间的依赖关系应该建立在最小的接口上。
- 不要强迫客户使用它们不用的方法,如果强迫用户使用它们不使用的方法,那么这些客户就会面临由于这些不使用的方法的改变所带来的改变。
- 接口属于客户,不属于它所在的类层次结构
- 不要在一个接口里面放很多的方法,这样会显得这个类很臃肿。
- 接口应该尽量细化,一个接口对应一个功能模块,同时接口里面的方法应该尽可能的少,使接口更加灵活轻便
- 何为最小的接口,即能够满足项目需求的相似功能作为一个接口,这样设计主要就是为了“高内聚”
注意:接口隔离和单一职责的区分
从功能上来看,接口隔离和单一职责两个原则具有一定的相似性。其实如果我们仔细想想还是有区别的。
(1)从原则约束的侧重点来说,接口隔离原则更关注的是接口依赖程度的隔离,更加关注接口的“高内聚”;而单一职责原则更加注重的是接口职责的划分。
(2)从接口的细化程度来说,单一职责原则对接口的划分更加精细,而接口隔离原则注重的是相同功能的接口的隔离。接口隔离里面的最小接口有时可以是多个单一职责的公共接口。
(3)单一职责原则更加偏向对业务的约束,接口隔离原则更加偏向设计架构的约束。这个应该好理解,职责是根据业务功能来划分的,所以单一原则更加偏向业务;而接口隔离更多是为了“高内聚”,偏向架构的设计。
5.3、特征
接口隔离原则是为了约束接口、降低类对接口的依赖性,遵循接口隔离原则有以下 5 个优点:
- 1、将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
- 2、接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。
- 3、如果接口的粒度大小定义合理,能够保证系统的稳定性;但是,如果定义过小,则会造成接口数量过多,使设计复杂化;如果定义太大,灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险。
- 4、使用多个专门的接口还能够体现对象的层次,因为可以通过接口的继承,实现对总接口的定义。
- 5、能减少项目工程中的代码冗余。过大的大接口里面通常放置许多不用的方法,当实现这个接口的时候,被迫设计冗余的代码。
5.4、应用
#include <iostream>using namespace std;class CommonInter {public:CommonInter() {}virtual void showTime() = 0;virtual void showWeather() = 0;
};class phoneInter :public CommonInter
{public:phoneInter() {}virtual void showTime() { cout << "showTime......" << endl;}virtual void showWeather() { cout << "showWeather......." << endl; }void listenMusic(){cout << "listenMusic......" << endl;}void watchVideo(){cout << "watchVideo......" << endl;}
};class watchInter :public CommonInter
{public:watchInter() {}virtual void showTime(){cout << "showTime......" << endl;}virtual void showWeather(){cout << "showWeather......." << endl;}void checkHeartbeat(){cout << "checkHeartbeat......" << endl;}};class phone
{private:phoneInter inter;
public:void use1(){inter.showTime();}void use2(){inter.listenMusic();}
};class watch
{private:watchInter inter;
public:void use1(){inter.showTime();}void use2(){inter.checkHeartbeat();}
};int main(int argc, char *argv[])
{phone p;p.use2();watch w;w.use2();return 0;
}
6、迪米特原则(Law of Demeter,LoD)
也被称为最少知识原则(Least knowledge Principle,LKP)
6.1、背景
只与你的直接朋友交谈,不跟“陌生人”说话。(Talk only to your immediate friends and not to
strangers)
明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则。
在迪米特法则中,对于一个对象,其朋友包括以下几类:
- (1) 当前对象本身(this);
- (2) 以参数形式传入到当前对象方法中的对象;
- (3) 当前对象的成员对象;
- (4) 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
- (5) 当前对象所创建的对象。
任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”,否则就是“陌生人”。
6.2、定义
如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
- 从依赖者的角度来说,只依赖应该依赖的对象。
- 从被依赖者的角度说,只暴露应该暴露的方法。
- 不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达
- 那么当其中某一个模块发生修改时,就会尽量少地影响其他模块
6.3、特征
迪米特法则要求限制软件实体之间通信的宽度和深度,正确使用迪米特法则将有以下两个优点。
- 降低了类之间的耦合度,提高了模块的相对独立性。
- 由于亲合度降低,从而提高了类的可复用率和系统的扩展性。
但是,过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。
6.4、应用
在运用迪米特法则时要注意以下 6 点。
- 在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
- 在类的结构设计上,尽量降低类成员的访问权限。
- 在类的设计上,优先考虑将一个类设置成不变类。
- 在对其他类的引用上,将引用其他对象的次数降到最低。
- 不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。
- 谨慎使用序列化(Serializable)功能。
- 举个例子来说,如果你使用 RMI 的方式传递一个对象 VO( Value Object),这个对象就必须使用 Serializable 接口,也就是把你的这个对象进行序列化,然后进行网络传输。突然有一天,客户端的 VO 对象修改了一个 属性的访问权限,从 private 变更为 public 了,如果服务器上没有做出响应的变更的话,就会报序列化失败。 这个应该属于项目管理范畴,一个类或接口客户端变更了,而服务端没有变更,那像话吗?!
#include <iostream>
#include<string>using namespace std;//明星
class Star
{private:string _name;
public:Star(string name):_name(name) {}string getName(){return _name;}
};//粉丝
class Fans
{private:string _name;
public:Fans(string name) :_name(name) {}string getName(){return _name;}
};//媒体公司
class Company
{private:string _name;
public:Company(string name) :_name(name) {}string getName(){return _name;}
};//经纪人
class Agent
{private:Star _myStar;Fans _myFans;Company _myCompany;
public:Agent(Star myStar, Fans myFans, Company myCompany) :_myStar(myStar), _myFans(myFans), _myCompany(myCompany){ }void setStar(Star myStar){_myStar = myStar;}void setFans(Fans myFans){_myFans = myFans;}void setCompany(Company myCompany){_myCompany = myCompany;}void meeting(){cout << _myFans.getName() << "与明星" << _myStar.getName() << "见面了。" << endl;;}void business(){cout << _myCompany.getName() << "与明星" << _myStar.getName() << "洽淡业务。" << endl;}
};int main(int argc, char *argv[])
{Star s ("KOBE");Fans f ("KOBE_FANS");Company c("CBA");Agent agent(s,f,c);agent.meeting();agent.business();Star s1("Lebron James");agent.setStar(s1);agent.business();return 0;
}
7、合成复用原则(Composite Reuse Principle, CRP)
C++类与类之间的存在的几种关系以及UML类图简单说明(依赖、关联、聚合、组合、泛化(继承)、实现)
7.1、背景
汽车按“动力源”划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这两种分类,其组合就很多。下图是用继承关系
实现的汽车分类的类图。
从上图 可以看出用继承关系实现会产生很多子类,而且增加新的“动力源”或者增加新的“颜色”都要修改源代码,这违背了开闭原则,显然不可取。
但如果改用组合关系实现就能很好地解决以上问题,其类图如下图所示。
1、聚合关系(Aggregation)
UML:带空心菱形的实线来表示,菱形指向整体
聚合(Aggregation)关系是关联关系的一种,是强关联关系,是整体和部分之间的关系,是 has-a 的关系。
整体与部分之间是可分离的,它们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。
- 例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。
2、组合关系(Composition)
UML :组合关系用带实心菱形的实线来表示,菱形指向整体
- 组合也是关联关系的一种特例,它体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合。它同样体现整体与部分间的关系,但此时整体与部分是不可分的。
- 在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。
- 例如,头和嘴的关系,没有了头,嘴也就不存在了。
7.2、定义
合成复用原则又叫组合/聚合复用原则。它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
- 合成/聚合复用原则是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的。
7.3、特征(为什么使用合成/聚合复用,而不使用继承复用?)
1、采用组合或聚合复用时
可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能
- 1、优点
- 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
- 新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。
- 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。
- 2、缺点:
- 就是系统中会有较多的对象需要管理。
2、继承复用
继承复用通过扩展一个已有对象的实现来得到新的功能,基类明显的捕获共同的属性和方法,而子类通过增加新的属性和方法来扩展超类的实现。继承是类型的复用。
- 1、优点
- (1) 新的实现较为容易,因为超类的大部分功能可以通过继承关系自动进入子类。
- (2) 修改或扩展继承而来的实现较为容易。
- 2、缺点
- (1) 继承复用破坏包装,因为继承将超类的实现细节暴露给了子类。因为超类的内部细节常常对子类是透明的,因此这种复用是透明的复用,又叫“白箱”复用。
- (2) 如果超类的实现改变了,那么子类的实现也不得不发生改变。因此,当一个基类发生了改变时,这种改变会传导到一级又一级的子类,使得设计师不得不相应的改变这些子类,以适应超类的变化。
- (3) 从超类继承而来的实现是静态的,不可能在运行时间内发生变化,因此没有足够的灵活性。
7.4、应用
#include<iostream>
#include<vector>
#include<string>
#include<iterator>
using namespace std;
/*
合成复用原则:
继承和组合优先使用组合,避免继承带来的麻烦
人开不同的车,不必人去继承车类,而使用组合,
把车组合进人里面进行调用
*/
class AbstractCar
{public:virtual void run()=0;
};
class BMW : public AbstractCar
{public:virtual void run(){cout << "BMW is run."<<endl;}
};
class Fort : public AbstractCar
{public:virtual void run(){cout << "Fort is run."<<endl;}
};
class People
{public:void setCar(AbstractCar* car){this->car=car;}void Drive(){car->run();delete car;}
private:AbstractCar * car;
};void test()
{People* p=new People();p->setCar(new BMW());p->Drive();p->setCar(new Fort());p->Drive();
}
int main()
{test();return 0;
}
参考
1、https://zhuanlan.zhihu.com/p/24246822
2、http://c.biancheng.net/view/1330.html
面试官问你如何进行程序设计?——设计模式之七大原则——接口隔离、合成复用、迪米特法则以及C++设计实现相关推荐
- 【设计模式】软件设计七大原则 ( 接口隔离原则 | 代码示例 )
文章目录 一.接口隔离原则简介 二.接口隔离原则代码示例 ( 反面示例 ) 1.接口定义 ( 接口臃肿 ) 2.实现类 1 3.实现类 2 三.接口隔离原则代码示例 ( 推荐用法 ) 1.接口 1 2 ...
- 面试官问你MyBatis中有哪些设计模式,把这篇文章发给他
戳蓝字"CSDN云计算"关注我们哦! 作者 | 疯狂的蚂蚁 来源 | https://dwz.cn/KFgol1De 之前总结过一篇Spring中用到了哪些设计模式:<面试官 ...
- 设计模式7大原则——接口隔离原则解析(含代码示例)
一.定义 客户端不应该依赖它不需要的接口,即 一个类对另一个类的依赖应该建立在最小的接口 上 . 通俗的来说就是,不要在一个接口里面放很多的方法,这样会显得这个类很臃肿不堪.接口应该尽量细化,一个接口 ...
- python设计模式六大原则_php设计模式的六大原则(六):迪米特法则
class Teacher { //老师对学生发布命令,清一下女生 public function commond(GroupLeader $groupLeader){ //初始化女生 for($i= ...
- php设计模式的六大原则(六):迪米特法则
为什么80%的码农都做不了架构师?>>> <?phpclass Teacher {//老师对学生发布命令,清一下女生public function commond(Gro ...
- Java设计模式七大原则-接口隔离原则
接口隔离原则(Interface Segregation Principle) 基本介绍 1) 客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上 2) 先看一张图: 类A ...
- 设计模式七大原则——接口隔离原则
1.什么是接口隔离原则? 客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口范围上. 2.对应代码 上面这张图呢,就违反了接口隔离原则.它对应的代码如下:
- java执行sql文件_面试官问你MyBatis SQL是如何执行的?把这篇文章甩给他
初识 MyBatis MyBatis 是第一个支持自定义 SQL.存储过程和高级映射的类持久框架.MyBatis 消除了大部分 JDBC 的样板代码.手动设置参数以及检索结果.MyBatis 能够支持 ...
- 字节跳动面试官问我看过哪些源码,然后就没有然后了
最近,我的一位朋友在找工作,已经拿到了美团.快手等公司的Offer,准备选择其中一家入职了. 后来他又接到了字节跳动的电话,通知他去参加三面.从二面到三面之间隔了挺久的,他以为都没戏了,结果就收到了通 ...
最新文章
- PL/SQL导入/导出dmp文件-Oracle表空间不一致
- LLDB+Python脚本:增强LLDB调试
- java8 collect 类型转换_java8新特性之list转换
- PHP内核探索:Zend引擎
- Linux 下比较文件内容并相同部分、不同部分
- 【数据挖掘实例】构建Xgboost模型,在电力用户的95598工单数据中的电费敏感用户预测(高敏用户模型)
- Xamarin效果第八篇之视频监控
- 拍照比剪刀手泄露指纹信息;国内绿 iPhone11 抢断货;PostgreSQL 12 Beta 4 发布​ | 极客头条...
- 《Storm技术内幕与大数据实践》一第1章 绪论
- APK的Mokey测试
- L1-049 天梯赛座位分配(模拟)
- win7自带桌面便签
- 四旋翼无人机的动力学模型
- 一个非常漂亮的简约大气的table
- C++ 实现即时通信软件(直接运行)
- 请仔细核对自己的信息
- php网页显示左中,php的动态页面在ie内核的浏览器面整体偏左的解决方法静
- java 省市联动_省市联动(json)
- Java集合移除某个元素
- docker 安装linux镜像制作,制作ubuntu完整版docker镜像