第三节:接口隔离原则、迪米特法则、组合聚合原则
一. 接口隔离原则
1. 定义
一个类对另一个类的依赖应该建立在最小的接口上,不应该依赖他不需要的接口。
通俗的说:要为每个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
与单一职责原则的区别:
(1). 单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。
(2). 单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。
2. 优点
接口隔离原则是为了约束接口、降低类对接口的依赖性,遵循接口隔离原则有以下 优点:
(1). 将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
(2). 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。
(3). 如果接口的粒度大小定义合理,能够保证系统的稳定性;但是,如果定义过小,则会造成接口数量过多,使设计复杂化;如果定义太大,灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险。
(4). 使用多个专门的接口还能够体现对象的层次,因为可以通过接口的继承,实现对总接口的定义。
(5). 能减少项目工程中的代码冗余。过大的大接口里面通常放置许多不用的方法,当实现这个接口的时候,被迫设计冗余的代码。
3. 实现方法和案例
(1). 实现方法
A. 接口尽量小,但是要有限度。一个接口只服务于一个子模块或业务逻辑。
B. 为依赖接口的类定制服务。只提供调用者需要的方法,屏蔽不需要的方法。
C. 了解环境,拒绝盲从。每个项目或产品都有选定的环境因素,环境不同,接口拆分的标准就不同深入了解业务逻辑。
D. 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
(2). 案例
在成绩管理系统中,会有很多业务,这里拆分出来多个接口,分别是:输入模块接口、统计模块接口、打印模块接口,需要哪个接口就去实现哪个接口。
/// <summary> /// 基础模块接口 /// </summary> public interface IBasicModel { void insert(); void delete(); void modify(); } /// <summary> /// 统计模块接口 /// </summary> public interface ICountModule { void countTotalScore(); void countAverage(); } /// <summary> /// 打印模块接口 /// </summary> public interface IPrintModule { void printStuInfo(); void queryStuInfo(); }
二. 迪米特法则
1. 定义
又叫:最小知道原则。
其含义:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。 通俗的来说:只与你的直接朋友交谈,不跟“陌生人”说话。
目的:降低类之间的耦合度,提高模块的相对独立性。
进一步解析:
(1). 从依赖者的角度来说,只依赖应该依赖的对象。
(2). 从被依赖者的角度说,只暴露应该暴露的方法。
2. 优点
(1). 降低了类之间的耦合度,提高了模块的相对独立性。
(2). 由于亲合度降低,从而提高了类的可复用率和系统的扩展性。
同样也有弊端:
过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。
3. 实现方法和案例
(1). 实现方法
A. 在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
B. 在类的结构设计上,尽量降低类成员的访问权限。
C. 在类的设计上,优先考虑将一个类设置成不变类。
D. 在对其他类的引用上,将引用其他对象的次数降到最低。
E. 不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。
F. 谨慎使用序列化(Serializable)功能。
(2). 案例
明星 ----- 经纪人 ------ 粉丝、媒体; 明星不直接和粉丝、媒体打交道,都是通过经纪人来打交道。
明星和粉丝、媒体类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/// <summary> /// 粉丝类 /// </summary> public class Fans { private String name; public Fans(String name) { this.name = name; } public String getName() { return name; } } /// <summary> /// 媒体公司类 /// </summary> public class Company { private String name; public Company(String name) { this.name = name; } public String getName() { return name; } } /// <summary> /// 明星类 /// </summary> public class Star { private String name; public Star(String name) { this.name = name; } public String getName() { return name; } }
经纪人
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/// <summary> /// 经纪人类 /// </summary> public class Agent { private Star myStar; private Fans myFans; private Company myCompany; public void setStar(Star myStar) { this.myStar = myStar; } public void setFans(Fans myFans) { this.myFans = myFans; } public void setCompany(Company myCompany) { this.myCompany = myCompany; } public void meeting() { Console.WriteLine(myFans.getName() + "与明星" + myStar.getName() + "见面了。"); } public void business() { Console.WriteLine(myCompany.getName() + "与明星" + myStar.getName() + "洽淡业务。"); } }
测试
{ Console.WriteLine("--------------------------迪米特法则--------------------------------"); //明星 ----- 经纪人 ------ 粉丝、媒体; 明星不直接和粉丝、媒体打交道,都是通过经纪人来打交道 Agent agent = new Agent(); agent.setStar(new Star("吕饺子")); agent.setFans(new Fans("粉丝李马茹")); agent.setCompany(new Company("中国传媒有限公司")); agent.meeting(); agent.business(); }
运行结果
三. 组合聚合原则
1. 定义
也叫合成复用原则,指的是软件在复用的时候,优先使用组合和聚合的关系来实现,其次才是继承关系。
PS:如果要使用继承关系,则必须严格遵循里氏替换原则。合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范。
扩展:适配器模式中的类适配模式,是通过继承的方式实现的;对象适配模式,是通过组合的方式实现的。
2. 优点
继承复用存在以下缺点:
(1). 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
(2). 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
(3). 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。
组合聚合复用存在以下优点:
(1). 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
(2). 新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。
(3). 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。
3. 实现方法和案例
(1). 实现方法
组合聚合原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。
(2). 案例
相关类
/// <summary> /// 封装最基本的四则运算方法 /// </summary> public class MyBaseHelp { public int Add(int a,int b) { return a + b; } public int Multiply(int a, int b) { return a * b; } } public class MyChildHelp1 { //复用原则 public MyBaseHelp _baseHelp; public MyChildHelp1() { _baseHelp = new MyBaseHelp(); } /// <summary> /// 特殊计算 /// </summary> /// <param name="a"></param> /// <param name="b"></param> /// <returns></returns> public int SpecialCalculate(int a, int b) { return _baseHelp.Add(a, b) + _baseHelp.Multiply(a, b); } }
测试
Console.WriteLine("--------------------------组合聚合原则--------------------------------"); MyChildHelp1 help1 = new MyChildHelp1(); Console.WriteLine($"特殊运算结果为:{help1.SpecialCalculate(10, 4)}");
运行结果
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。