一、开闭原则
定义:一个软件模块如类、模块和函数应该对扩展开放,对修改关闭。
开闭原则强调了用抽象构建框架、用实现扩展细节。通常为了满足开闭原则,我们应该尽可能的面向抽象编程。
优点:提高软件系统的可复用性及可维护性。
具体例子:现在我们有一个课程接口,它有很多具体课程的实现类,我们可以通过具体的实现类来获取具体课程价格。
public interface Course {
int getPrice();
String getBookName();
}
实现类
public class JavaCourse implements Course{
private int mPrice;
private String mBookName;
public JavaCourse() {};
public JavaCourse(int price,String bookName) {
mPrice=price;
mBookName=bookName;
}
@Override
public int getPrice() {
// TODO Auto-generated method stub
return mPrice;
}
@Override
public String getBookName() {
// TODO Auto-generated method stub
return mBookName;
}
}
很简单,调用JavaCourse 对象的getPrice方法我们就可以获得java课的价格,现在有一个需求:元旦快到了,我们的java课打6折,我们怎么写?
1、给Course接口增加一个用于返回打折后价格的方法吗(discountPrice)?,这显然违背了对修改封闭 ,接口应该是稳定的,最好不要修改它,因为一旦修改了接口,所有的实现类都要修改。
2、直接修改JavaCourse中的getPrice方法或者给JavaCourse增加一个discountPrice方法?如果增加一个discountPrice方法那这个类就有两个获取价格的方法了,而且下次打4折、打7折是不是都要对类进行修改。这也不符合开闭原则。
3、定义继承至JavaCourse专门用来处理打折的类:
public class JavaDiscountCourse extends JavaCourse{
public JavaDiscountCourse(int price,String bookName) {
super(price,bookName);
}
public int getDiscountPrice() {
return (int) (getPrice()*0.6);
}
}
这样我们就不用修改原有的类了,正如对开闭原则的定义对扩展开放,对修改关闭。通过继承现有类来扩展功能而不是修改原有类。开闭原则是面向对象编程最基础的设计原则,它指导我们如何建立一个稳定灵活的系统。
二、依赖倒置原则
定义:高层模块不应该依赖底层模块,两者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。
依赖可以理解为一种需要,A类需要用到B类,那么A就依赖B类。通常高层模块就是调用端,而底层模块就是被调用端的具体实现,高层模块和底层模块的这种定义是相对的。如果高层模块和底层模块之间的这种依赖通过具体实现发生,他们就会高度耦合。而依赖倒置原则指导我们类与类之间不应该直接依赖细节(具体的实现类),而是依赖抽象。所谓的抽象可以是接口,也可以是抽象类。
举个例子:现在我们有一个DaoService类它持有一个OracleDao对象,通过它可以操作Oracle数据库。那么DaoService就是高层模块,OracleDao就是底层模块。
public class OracleDao {
public void select() {
System.out.println("查询Oracle数据库");
}
}
public class DaoService {
private OracleDao dao;
//注入
public void setDao(OracleDao dao) {
this.dao = dao;
}
public void work() {
dao.select();
}
}
可以看到这里依赖关系是通过细节发生的,OracleDao是具体的实现类。当我们调用DaoService。
public class Test {
public static void main(String[] args) {
DaoService serviec=new DaoService();
serviec.setDao(new OracleDao());
serviec.work();
}
}
如果现在因为某些原因,我们需要从MySQL数据库查询数据,我们就需要修改DaoService类。
public class DaoService {
private MySQLDao dao;
//注入
public void setDao(MySQLDao dao) {
this.dao = dao;
}
public void work() {
dao.select();
}
}
以后操作其他数据库是不是还是需要修改DaoService类?依赖倒置的好处这时候就体现出来,再来回顾下定义:高层模块不应该依赖底层模块,两者都应该依赖其抽象。
因此我们创建一个接口,让所有的具体Dao类实现它。
//抽象
public interface Dao {
void select();
}
//实现
public class OracleDao implements Dao{
@Override
public void select() {
System.out.println("查询Oracle数据库");
}
}
public class DaoService {
//依赖关系通过抽象发生
private Dao dao;
//注入
public void setDao(Dao dao) {
this.dao = dao;
}
public void work() {
dao.select();
}
}
现在如果在替换数据库我们就不用再修改DaoService类,只需要创建一个新的类实现Dao接口。
public static void main(String[] args) {
DaoService serviec=new DaoService();
serviec.setDao(new OracleDao());
serviec.work();
serviec.setDao(new MySQLDao());
serviec.work();
}
三、接口隔离原则
定义:用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。
补充:
- 一个类对一个类的依赖应该建立在最小的接口上。
- 建立单一的接口,不要建立庞大臃肿的接口。
- 尽可能细化接口,接口中的饿方法尽量少。
- 注意适度原则,一定要适度。
优点:符合高内聚低耦合的设计思想,使类可读性更好、更易维护和扩展。
遵循接口隔离原则一定要把握好一个度,接口中方法少固然使得程序更加灵活,但同样会导致接口过多,增加系统的复杂性。
下面看一个例子:
//接口
public interface Animal {
void eat();
void run();
void fly();
void swim();
}
//实现类
public class Fish implements Animal{
@Override
public void eat() {
// TODO Auto-generated method stub
}
@Override
public void run() {
// TODO Auto-generated method stub
}
@Override
public void fly() {
// TODO Auto-generated method stub
}
@Override
public void swim() {
// TODO Auto-generated method stub
}
}
定义了一个动物接口,然后创建了一个鱼实现这个接口。这个接口就不符合接口隔离原则,建立单一的接口,不要建立庞大臃肿的接口。这个接口比较大,它的实现类鱼其实能用到的只有eat()和swim(),鱼是不会run和fly的,即使我们实现了也没有。因此我们应该将接口细化。
public interface Animal {
void eat();
}
public interface IFlyable {
void fly();
}
public interface ISwim {
void swim();
}
public interface IRunable {
void run();
}
实现类
public class Fish implements Animal,ISwim{
@Override
public void swim() {
// TODO Auto-generated method stub
}
@Override
public void eat() {
// TODO Auto-generated method stub
}
}
对接口的适当拆分可以增加程序的灵活性。
四、迪米特法则
定义:一个对象应该对其他对象保持最少的了解。又叫最少知道原则。
优点:降低类与类之间的耦合。
迪米特法则强调了两点:
- 在设计类时,每个类都应该降低成员的访问权限,不需要让别的类知道的方法和属性就不要公开。
- 应该和朋友说话,不和陌生人说话。
第一点很好理解,对类中信息适当的隐藏有利于复用,只给依赖者提供简单的使用方法,具体方法里面怎么做的,依赖者不关心,这样可以降低类与类之间的耦合,类之间的耦合越低,越有利于复用。一个处于弱耦合的类被修改,不会对关系的类造成波及。
第二点怎么理解呢?迪米特法则告诉我们应该和直接朋友说话,不和陌生人说话,哪些是朋友呢?成员变量、方法的参数、方法返回值中的类都是直接朋友,而出现在方法体内部的局部变量不属于朋友。
举例子:
公司的boss有一天忽然找到人事经理,希望它帮忙统计下公司有多少员工。
//boss类
public class Boss {
public void numberOfEmployees(PersonnelManager personnelManager){
List<Employee> employeeList=new ArrayList<>();
employeeList.add(new Employee("张三"));
employeeList.add(new Employee("李四"));
employeeList.add(new Employee("小王"));
int num=personnelManager.numberOfEmployees(employeeList);
}
}
//部门经理
public class PersonnelManager {
public int numberOfEmployees(List<Employee> list){
int num=(list==null?0:list.size());
System.out.println("公司总共有:"+num+"人");
return num;
}
}
调用:
public static void main(String[] args) {
Boss boss=new Boss();
boss.numberOfEmployees(new PersonnelManager());
}
重点看下Boss这个类,它的朋友是PersonnelManager 类,而方法体中的Employee不属于它的朋友,因此Boss不应该和它交流,这就是和直接朋友说话,不和陌生人说话。
修改后如下:
public class Boss {
public void numberOfEmployees(PersonnelManager personnelManager){
int num=personnelManager.numberOfEmployees();
}
}
//部门经理
public class PersonnelManager {
public int numberOfEmployees(){
List<Employee> employeeList=new ArrayList<>();
employeeList.add(new Employee("张三"));
employeeList.add(new Employee("李四"));
employeeList.add(new Employee("小王"));
int num=employeeList.size();
System.out.println("公司总共有:"+num+"人");
return num;
}
}
未完待续~