一、开闭原则
概念:软件实体对拓展开放,对修改关闭(在不修改原有代码上添加新的功能)
例子:比如说一个拥有一个‘增’‘删’‘改’的功能模块,现在要增加‘查’的功能,此时只需要去开发一个接口增加该功能即可,不需要改变原有模块的内容(可扩展程度高,可维护性强,使变化的系统有一定的稳定性和延续性)
如何实现该原则:区别哪些是变化的部分,哪些是不变化的部分。对于不变的部分,可以将其加以抽象成不变的接口。对于变化的部分,每一个可变的因素都单独进行封装
反思:商品的鞋类分男鞋和女鞋,需要进行统一的价格管理
此时将鞋子定义为接口IShoe并且定义,分别定义获取鞋名,鞋的类型,鞋的价格三个方法。两个子类MenShoe和GirlShoe来实现它
此时,需求变动。由于新店开张,为了提高人气,店老板对鞋类进行降价促销,男鞋和女鞋的折扣不同。此时应该如何设计比较合理?
方案一:在接口IShoe中增加GetPriceCut()用于处理降价,此时MenShoe类和GirlShoe类也需要修改。此方式不符合开闭原则,并且改动量比较大。
方案二:在MenShoe、GirlShoe中各添加一个方法,相对方案一要好一些,但是还不是最好。
方案三:增加两个新的类继承MenShoe和GirlShoe,一个用于处理男鞋的降价计算,一个用于处理女鞋的降价计算。此方案修改量少,可扩展性强,最符合开闭原则。
个人总结:1、开闭原则是为了保护原有的代码,避免更改原有代码出错。
2、开闭原则可以更好的面对多变的客户需求。(比如反思的例子。如果商家之后又不用打折的功能的话,方案一又需要去改接口和所有的实现类,方案二是需要去改所有增加的打折方法,而方案三则可以直接更改实例化的对象即可)
二、单一职责原则
概念:一个类只需按职责进行功能设计(符合高内聚、低耦合)
防止相同类型的职责分离到不同的类中。即要提高代码的可重用性。
例子:足球场上的裁判执行竞赛规则
违反单一职责:将裁判设置为一个接口,然后用子类实现它。
符合单一职责:将主裁判和助理裁判分别定义成两个接口,在分别用两个子类来实现它们。
单一职责的优势:有助于清晰的理清设计与编码的思路
有助于简化维护、编码、测试的流程
复杂问题简单化,有利于代码的重用
职责之间消除耦合后,有利于系统的扩展
如何实现该原则:
1、从面向对象原理分析:按照对象的属性和运动规律的相似性将相近的对象合并而成。它只实现自身该做的事情。
2、从业务功能角度分析:比如常见的增删改查功能都属于操作职责,故可以封装成一个类。(不过同时还要考虑高内聚,低耦合)
反思:设计一个产品报表,这个产品报表模块的属性包括生产年月、产品名称、产品类型、品牌、产品价格和产地,具体功能只需实现查询。
方案一:将所有属性与功能封装到一个接口中,利用实现类去实现接口。
方案二:把产品属性封装到一个接口,产品业务操作功能封装到另一个接口。
个人总结:1、降低代码的冗余度(比如反思的例子。方案一中,所有实现该接口的类(非抽象类)都必须实现该接口的所有方法,若该类只是作为转移数据的对象,那么‘查询’这个方法就会造成冗余)
2、提高代码的复用率(很显然,如果狗负责看家,猫负责抓老鼠,当需要一只哈士奇时只需要继承狗即可。若是有一只四不像既能看家又能抓老鼠,当你需要一只哈士奇时又需要重新去定义所有狗的共同属性和行为)
三、里氏代换原则
概念:对于每一个类型为T1的对象t1,都有类型为T2的对象t2存在,是的以所有T1定义的所有程序P在所有对象t1都替换成t2时,程序P的行为也没有变化,那么类型T2是类型T1的子类型。(比如把人做的事换做男人来做都没什么问题,那么男人就是人的子类型)
优势:减少重复编码,实现代码的重用性。子类和基类可以相似,也可以有各自不同之处,基类可以提高代码的开放性。
问题:父类方法与属性进行变动时,子类也要随之修改。若没有一个科学的规范,则其结果会导致大量的代码需要重构。因此,我们需要实现里氏代换。
如何实现该原则:可以从“父类不能替换子类,而子类可以替换父类”的思路引导下进行里氏代换原则的实现。
1、正确进行继承设计。所有基类的方法都要在其子类中得到实现或者重写,并且子类不能写出与业务功能实现无关的多余方法或实现。
2、最优的继承层次设计。当其他应用的类调用业务功能类时,应该先调用其业务功能的基类而不应该直接调用业务功能的子类对象。
体现该原则的设计模式有:策略模式、合成模式、代理模式。
反思:设计一个基类和一个子类,子类调用父类
错误写法:向上转型后使用子类定义的属性或者方法,向下转型使用子类定义的属性或方法
正确写法:向上转型或向下转型,都应该只是用父类中定义的属性或方法。
个人总结:里氏代换原则使我们可以定义出更高效、优秀的基类,实现代码的重用性。
四、依赖倒换原则
概念:高层模块不应该依赖于底层模块,二者都应该依赖于抽象。(运用抽象来分析,而不必一开始关注类的细节)
针对接口编程,不要针对实现编程。
如何实现该原则:
1、从实现类和抽象类角度分析:运用具体实现类去继承抽象类时,需要保证引用基类支出可修改成其子类
2、层次关系角度进行分析:定义清晰的层次关系,使每个层次通过接口的方式进行
反思:设计员工管理小模块,包括管理层和业务员以及两名Java程序员
错误写法:定义四个类,管理层类调用高级程序员类使他工作
正确写法:定义两个接口,一个是管理层,一个是编程。三个类,一个是编程类,它实现管理层接口,剩下的两个类是初级程序员和高级程序员类,他们都实现了编程类
代码:
管理层接口
public interface IEmployee {
public String code(ICode code);
}
编程接口
public interface ICode {
public String getRun(String Run);
}
编程类
public class Code implements IEmployee {
private static final String Run = “编码活动”;
@Override
public String code(ICode code) {
System.out.println(code + “程序员开始编码…”);
return code.getRun(Run);
}
}
普通程序员类
public class CommonProgrammer implements ICode {
private static final String Run = “JACK”;
@Override
public String getRun(String Run1) {
String Run2 = “工作”;
System.out.println(Run + ” 普通程序员开始工作”);
return Run2;
}
}
高级程序员类
public class SeniorProgrammer implements ICode {
private static final String Run = “李四”;
@Override
public String getRun(String Run1) {
String Run2 = “工作”;
return Run2;
}
}
测试代码
IEmployee employee = new Code();
ICode code = new SeniorProgrammer();
ICode code1 = new CommonProgrammer();
out.println(” ” + employee.code(code));
out.println(“管理人员派张三高级程序员” + employee.code(code1));
个人总结:依赖倒换原则使代码变得更加灵活,更加适合多变的客户需求。
五、接口隔离原则
概念:不应该强迫客户依赖于他们不用的方法;一个类对另一个类的依赖性应当是建立在最小的接口上。(接口表达要准确,不要创建多余的方法。和单一职责原则有点相似)
例子:将手机定义为接口,所有手机对象都必须实现它所有的方法,容易造成代码冗余。应该将娱乐手机、办公手机分别定义为两个接口,具体手机只需要实现对应的接口即可。
问题:
1、接口中常常定义很多方法,造成代码冗余
2、若有一个职责改变了,就去修改接口,那么这个接口的所有实现类都需要去修改。所以应该运用接口隔离原则,一开始就设计角色独立的接口。
如何实现该原则:
1、从业务逻辑角度考虑接口,将某类功能设计成接口。比如影片中“主角”、”反派”是接口,“主角的演员”、“反派的演员”是接口的实现(编剧本的时候也是如此,不会根据那个具体明星来写剧本,而是根据主角、反派等来写)
2、根据场合和调用者的情况,消除无关的方法,只提供同类型角色的接口。
3、对客户程序进行分离,随着客户程序的分离,对应的接口也随之变化。
反思:一个系统有门户网站和管理系统后台,门户网站只需要查询方法,而后台需要增删改查。
不合适的设计:将增删改查定义成一个接口,门户和后台使用时直接实现该接口。
合理的设计:将查询、和增删改查分别定义成两个接口,门户和后台分别实现这两个接口。
个人总结:
不合适的设计违背了接口隔离原则。因为单一职责原则告诉我们,类间的依赖关系应该建立在最小的接口上。有利于提高类的可维护性和可复用性。
合理的设计中,接口与实现类角色一一对应,符合接口隔离原则,是很好的实现方式。
六、迪米特法则
概念:一个对象应当对其他对象有尽可能少的了解,不必与相识的人直接联系。
例子:房产中介(减少租房者和住房之间的耦合)
狭义的迪米特法则:
“如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的方法的话,可以通过第三者转发这个调用”(比如谈生意时,为了防止被对方所骗,甲乙双方可以通过熟识的第三发进行联系)
广义的迪米特法则:
“一个模块设计得好坏,一个重要标志就是该模块在多大程度上将自己的内部数据与实现的有关细节隐藏起来”。(比如古代代表皇帝出巡的钦差大人与老百姓的关系,百姓直接向钦差反应生活问题,皇帝再通过钦差的报告了解各地的民间百态,以便更好的治理国家)
优势:
1、降低类之间的耦合度。由于类与类之间去除了依赖关系,则各个软件功能模块之间相互独立。
2、遵循迪米特法则进行系统设计,如果系统需要扩展,则更加符合开闭原则对修改的关闭的要求。
3、将系统的内部数据与细节隐藏,从而使各个功能子模块远离耦合,最终达到提高代码的重用性和可维护性。
反思:设计地下党单线联系。为提高保密程度,某男性党员只需与联络人联系,而联络人则与某女性党员直接联系。
纯迪米特法则设计:将男性党员、联络人、女性党员、和完成任务定义成四个类,男性党员类中可获取联络人,联络人类中可获取女性党员。完成任务类中实例化男性党员,然后在男性党员的方法中new一个联系人,再通过男性党员获取该联系人并且调用该类中的联系女党员
与依赖倒换相结合的方法:将女性党员抽象成接口,再和上面一样
个人总结:相信学过Spring的人都知道,这个法则在Spring中就是IoC(控制反转),它将控制权从程序转移到了Spring框架,用框架来管理程序。
最后总结:以上是我个人的感悟,若果有不对的地方,请留言给我,我会改进。谢谢 : )