依赖倒置原则(依赖抽象接口,而不是具体对象)
模块间的依赖通过抽象产生,实现类之间不发生直接依赖,其依赖关系是通过接口或抽象类产生的。依赖倒置原则的目的是通过面向接口的编程来降低类间的耦合性,所以我们在实际编程中只要遵循以下4点,基本就能在项目中满足这个原则。
1.每个类尽量提供接口或抽象类,或者两者都具备。
2.参数类型、引用对象尽量使用接口或抽象类,而不是实现类。
3.任何类都不应该从实现类派生。
4.使用继承时尽量遵循里氏替换原则。
代码示例
业务需求:在云数据库服务部署的过程中,对于公有云环境和私有云环境可能存在施工方式不一样的场景,因此在我们实际的工程项目中,需要用一套代码来兼容两种施工方式并存的场景。
第一版代码:
public class DeployPublicCloudService {
public void preCheck() {
System.out.println("公有云-环境预检查");
}
public void deploy() {
System.out.println("公有云-施工部署");
}
}
public class DeployPrivateCloudService {
public void preCheck() {
System.out.println("私有云-环境预检查");
}
public void deploy() {
System.out.println("私有云-施工部署");
}
}
public class NotifyService {
public void callback(String msg) {
System.out.println(String.format("回调通知%s", msg));
}
}
public class Client {
public static void main(String[] args) {
// 公有云环境施工部署
constructInPublicCloud(new DeployPublicCloudService());
// 分割线
System.out.println("=========================");
// 私有云环境施工部署
constructInPrivateCloud(new DeployPrivateCloudService());
}
private static void constructInPublicCloud(DeployPublicCloudService deployService) {
deployService.preCheck();
deployService.deploy();
NotifyService notifyService = new NotifyService();
notifyService.callback("施工结果");
}
private static void constructInPrivateCloud(DeployPrivateCloudService deployService) {
deployService.preCheck();
deployService.deploy();
NotifyService notifyService = new NotifyService();
notifyService.callback("施工结果");
}
}
执行结果:
公有云-环境预检查
公有云-施工部署
回调通知施工结果
=========================
私有云-环境预检查
私有云-施工部署
回调通知施工结果
顺着以上代码的实现思路,假设现在又需要开发一套混合云的兼容施工方案,那我们的直接想法就是再新增一个部署混合云的服务(DeployMixedCloudService),然后再新增一个constructInMixedCloud方法,也就是说每次来一种新的施工方式,都要新增一个施工方法,而我们发现施工方法里的代码几乎一样,这不仅增加了代码冗余影响阅读,还不利于后期代码的可扩展性维护,比如某天需要修改预检查和部署方法的依赖关系,那么所有的施工方法都得改一遍,风险大大提高了,这显然是不可取的。那有没有一种方式,既能满足业务需求又不需要每次都新增施工方法呢,答案是有的,只要我们遵循依赖倒置原则的四点要求就能做到。下面进行代码演进。
演进一:
public interface IDeployService {
/**
* 环境预检查
*/
void preCheck();
/**
* 施工部署
*/
void deploy();
}
public class DeployPublicCloudService implements IDeployService {
@Override
public void preCheck() {
System.out.println("公有云-环境预检查");
}
@Override
public void deploy() {
System.out.println("公有云-施工部署");
}
}
public class DeployPrivateCloudService implements IDeployService {
@Override
public void preCheck() {
System.out.println("私有云-环境预检查");
}
@Override
public void deploy() {
System.out.println("私有云-施工部署");
}
}
public class NotifyService {
public void callback(String msg) {
System.out.println(String.format("回调通知%s", msg));
}
}
public class Client {
public static void main(String[] args) {
// 公有云环境施工部署
construct(new DeployPublicCloudService());
// 分割线
System.out.println("=========================");
// 私有云环境施工部署
construct(new DeployPrivateCloudService());
}
private static void construct(IDeployService deployService) {
deployService.preCheck();
deployService.deploy();
NotifyService notifyService = new NotifyService();
notifyService.callback("施工结果");
}
}
执行结果:
公有云-环境预检查
公有云-施工部署
回调通知施工结果
=========================
私有云-环境预检查
私有云-施工部署
回调通知施工结果
通过和第一版的代码进行比较,我们发现,新增了一个IDeployService的接口类,同时让DeployPublicCloudService和DeployPrivateCloudService都去实现它,这满足了依赖倒置原则的第一点和第三点要求,即每个类尽量提供接口或抽象类,或者两者都具备。同时我们修改了施工方法的入参,由具体的施工服务类改成由接口类来作为入参的类型,满足了依赖倒置原则的第二点要求,即参数类型、引用对象尽量使用接口或抽象类,而不是实现类。其实,在无形之中,我们也同时满足了第四点的要求,里氏替换原则的一个核心思想就是不要去重写父类非抽象的方法。现在我们每次要新增一种施工方式就不必再去新增一个配套的施工方法了,只需要专注于接口方法的实现即可。