面向对象七大设计原则
这些原则蕴含在很多设计模式中,它们是从许多设计方案中总结出的指导性原则
在设计模式的学习中,大家经常会看到诸如“XXX模式符合XXX原则”、“XXX模式违反了XXX原则”这样的语句
面向对象七大设计原则
开闭原则
对修改关闭,对扩展开放。一切都是为了保证代码的扩展性和复用性。而开闭原则是基础要求
白话文来说就是:软件系统中包含的各种组件,例如模块(Modules)、类(Classes)以及功能(Functions)等等,应该在不修改现有代码的基础上,去扩展新功能,开闭原则中原有“开”,是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的;开闭原则中“闭”,是指对于代码的修改是封闭的,即不应该修改原有的代码。
当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
-
水果接口
public interface Fruit{ Double getFriutPrice(String fruitName); Double getFruitSize(String fruitName); }
-
水果接口的实现类 ---> 橘子
public class Peach implments Fruit{ private String productArea; private Double price; private Double size; ... 构造方法 set get ... }
-
今年橘子想做促销,把橘子的价格调低销售
-
如果我们直接去修改橘子类的的价格是不适合的
-
我们可以用一个新的促销类来继承Peach
-
重写其price方法,这样就保证了原来调用Peach的地方不受影响。
-
无须修改现有类库的源代码 ,实现了价格的打折处理
-
public class SalesPeach extends Peach{ private static final Double sales_Factor = 0.75d; public Peach(Double price,Double size,String productArea) { super(price,size,productArea); } //促销打7.5折 public Double getPrice(){ return super.getPrice() * sales_Factor; } }
单一职责原则
一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。 它用于控制类的粒度大小
接口隔离原则
使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口
当一个接口太大时,我们需要将它分割成一些更细小的接口,一个客户端不应该依赖它用不到的接口,使用该接口的客户端仅需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。
-
动物接口
public interface Animal { void eat(); void fly(); void swim(); }
-
动物接口实现类 --> 鱼类
public class Fish implements Animal { void eat(){ System.out.print("鱼儿进食"); } void fly(){} void swim(){ System.out.print("鱼儿游泳"); } }
-
动物接口实现类 --> 鸟类
public class Bird implements Animal { void eat(){ System.out.print("鸟儿进食"); } void fly(){ System.out.print("鸟儿飞翔"); } void swim(){} }
-
动物接口实现类 --> 猪类
public class Pig implements Animal { void eat(){ System.out.print("猪儿进食"); } void fly(){} void swim(){} }
上面这种接口和类的关系就看得出来,重接口带来的问题很明显
鱼不会飞
鸟不会游泳
猪不会飞也不会游泳
但是,由于他们实现的接口是一个重接口,包含了动物的很多行为方法,所以每一个动物的实现者都要重写很多自己根本不需要的行为方法,即使为空
此时改进的方法就是,将重借口的功能拆分,根据更高的复用性作为一个标准来拆分,如果以上图为列
-
动物重接口拆分为三个细接口,分别是 eat 、fly、swim
-
鱼想要吃饭和游泳,就去实现eat 和swim,而不需要去实现fly
-
同理,猪只会吃饭,也就只需要实现eat即可
依赖倒置原则
依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。
大学生选课,该学生特别热爱java课程,所以就选了java课程,当他还想选择大数据课程的时候,我们就需要在Student中增加一个方法 void chooseBidDataCourse();这样是非常不稳定的,这样的修改风险也较高。
-
改造重构
public interface Course { void choose(); }
-
Java课程
public class JavaCourse implements Course { void choose() { System.out.println("选择了java课程"); } }
-
大数据课程
public class BigDataCourse implements Course { void choose() { System.out.println("选择了大数据课程。"); } }
-
大学生选课
-
你想选什么课,都只需要调用chooseCource方法即可
-
方法的参数是一个层次很高的接口:Course
-
只要是Course的实现子类或者孙子类都可
-
public class Student{ void chooseCource(Course course) { course.choose(); } }
里式替换原则
如何去编写继承类的代码,子类不要去覆盖父类已经实现的方法。(抽象模板方法)
程序中可以使用父类的地方,一定也可以使用子类,即子类可以替换掉父类而不影响程序的正常运行。
里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在 程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对 象。
-
父类:BaseClass
-
子类:SubClass
-
如果一个方法void test1(BaseClass base)
-
该test1方法也可以接受SubClass 的入参
-
-
如果一个方法void test2(SubClass sub)
-
该test2方法不可以接受BaseClass 的入参,层次等级不够
-
迪米特法则
最少认知原则,只和你的朋友交谈,不要和陌生人说话。
只和你的朋友交谈,不要和陌生人说话,对于一个对象,其朋友包括以下几类:除此之外统一称为陌生人
当前对象本身(this);
以参数形式传入到当前对象方法中的对象;
当前对象的成员对象;
如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
当前对象所创建的对象
比如:项目经理想知道公司产品的测试结果
项目经理直接去访问与他没有直接关系的测试人员,关系复杂,人员依赖调用复杂,耗时1天
项目经理向测试经理要测试结果,测试经理轻车熟路的调动人员分析得到测试结果,耗时2小时
明显看出,直接问测试经理要结果,省时省力
合成复用原则
能用组合关系的情况下,不要使用继承关系。就比如说,如果你想拥有某个对象的功能,不要直接继承它,而是将它作为我的成员变量去使用 ,使用关联复用来取代继承复用
-
获取数据库连接工具类
public class DBConnection{ public String getConnection(){ System.out.println("使用MYSQL数据库创建连接"); return "Mysql"; } }
-
Dao完成数据库操作
public class UserDAO extends DBConnection { public void queryUser() { String connection = super.getConnection(); System.out.println("使用数据库连接进行查询" + connection); } }
-
加入此时我们要更换Mysql为Oracle了怎么办啊
-
难道要在DBConnection中新增连接Oracle的方法
-
然后去所有Dao中更改以往的代码,获取Oracle的的连接吗
-
无论修改DBConnection还是修改UserDAO都是违背开闭原则的
-
-
更改如下
public class OracleConnection extends DBConnection { public String getConnection(){ System.out.println("使用Oralce数据库创建连接"); return "Oracle"; } }
public class UserDAO { public void queryUser(DBConnection connection ) { String connection = connection.getConnection(); System.out.println("使用数据库连接进行查询" + connection); } }
-
根据里氏代换原则,OracleConnection作为子类对象可以覆盖父类DBConnection对象
-
如果以后,我们还想扩展其他的连接,只需要再写一个子类实现DBConnection,然后注入到方法中即可
-
灵活地增加新的数据库连接方式