title: Java:面向抽象编程实现OCP
copyright: true
tags: 单例模式
categories:
- JavaSE
- OCP
cover: 'https://typora-1304191864.cos.ap-shanghai.myqcloud.com/typora/qiniu1384.png'
abbrlink: 2d10a40e
date: 2021-02-16 23:04:50
涉及技术基础点:
- 反射
- 泛型
- 抽象
- 工厂模式
- interface
一、代码优化原则
- 代码复杂性的根本
代码编写的出发点:不啰嗦 自描述性的代码 可维护性 好代码
总结,所有软件的复杂性,都是为了可维护性。
-
开闭原则 OCP
为什么java写出来的代码是可维护的,一个非常重要的原则就是开闭原则,也就是OCP。可能会听过里式替换原则,还有迪米特法则,这些都不是最重要的,最重要的是开闭原则,这是代码可维护的基础。其它的原则都是开闭原则的子原则,其他的原则都是为了实现开闭原则。
除了里式替换原则,迪米特法则,还有控制反转,依赖注入,其实也是为了实现开闭原则。
OCP的英文:Open Closed Principle。
不管是软件,还是函数,还是类,都要对扩展是开放的,对修改是封闭的。
当要修改的时候,我们通过新增一业务模块或类,来代替原来的类
注意: 不要面对一个具体的类去编程,要面向抽象。
示例:
public class A{
private C c ;
public void print(){
C c = new C();
c.text1();
System.out.println("This is Class A");
}
- 为什么要面向抽象编程?
interface abstract
使用抽象接口,可以避免因新增实现类而改动控制代码。
因为如果面向实体类去新建对象,并调用方法,在后续维护(指出现新需求)的时候,改动一个类,会影响到其他依赖此类的类做出改动 new语句以及调用方法的语句,这将是灾难性的修改。
- 如何能够忽略掉一个具体类,而面向抽象编程?
就是用的interface(接口)
IOC DI 的过程:
- interface
- 设计模式:工厂模式
- IOC,DI
目的:
=》面向对象 =》OCP =》可维护的代码
二、代码优化之路(从强耦合到稳定)
创建 Diana、Irelia、Camille三个类,分别有多个相同的方法可供调用,例子:
public class Irelia {
public void g(){
System.out.println("Irelia Q");
}
public void w(){
System.out.println("Irelia W");
}
public void e(){
System.out.println("Irelia E");
}
public void r() {
System.out.println("Irelia R");
}
}
- 案例代码一:
多个类创建多个对象来调用各自的方法,耦合度高,当有新的类加入时,需要更改main中的代码;
public static void main(String[] args) {
String name = Main.getPlayerInput();
switch (name) {
case "Diana":
Diana diana = new Diana();
diana.r();
case "Irelia":
Irelia irelia = new Irelia();
irelia.r();
case "Camille":
Camille camille = new Camille();
camille.r();
}
}
当前的案例中,每个方法的调用需要分别使用所属类的实例去调用,代码的耦合度太高,变更太频繁;
2.案例代码二:
使用 interface 来统一调用方法,这里创建 ISkill 接口,Diana、Irelia、Camille实现该接口;
public static void main(String[] args) throws Exception {
String name = Main.getPlayerInput();
ISkill iSkill;
switch (name) {
case "Diana":
iSkill = new Diana();
break;
case "Irelia":
iSkill = new Irelia();
break;
case "Camille":
iSkill = new Camille();
break;
default:
throw new Exception();
}
iSkill.r();
}
从以上的代码中,可以看出,确实在原有代码上进行了优化。
但是:
-
单纯的interface可以统一方法的调用,但是他不能统一对象的实例化
-
面向对象在做两件事情
a)实例化对象
b)调用方法(完成业务逻辑) -
只有一段代码中没有new的出现,才能保证代码的相对稳定,才能逐步实现OCP
-
上面这句话的只是表象,实质是一段代码如果要保持稳定,就不应该负责对象的实例化
-
对象实例化的过程不可能消除掉
-
把对象实例化的过程,转移到其他的代码片段里
方法统一的意义在哪?
1、某方法被大量调用时很繁琐,方法统一后一行代码搞定;
2、事项功能的单一性,便于后面的提取封装
多个类实现同一个接口,通过反射与多态的方式进行向上转型,从而可以使用一个统一的出口进行方法的调用。代码的耦合度较低,只需要更改要实例化的对象,对于具体调用方法的实例本身是不变的。
- 案例代码三:
在案例代码二的基础上,将主要进行 new 的switch-case 代码进行抽取,通过 Factory 的方式来生成具体的实例:
public class HeroFactory {
public static ISkill getHero(String name) throws Exception {
ISkill iSkill;
switch (name) {
case "Diana":
iSkill = new Diana();
break;
case "Irelia":
iSkill = new Irelia();
break;
case "Camille":
iSkill = new Camille();
break;
default:
throw new Exception();
}
return iSkill;
}
}
Main.java
public static void main(String[] args) throws Exception {
String name = Main.getPlayerInput();
ISkill iSkill = HeroFactory.getHero(name);
iSkill.r();
}
工厂类的作用就是负责创建类的实例,而不影响主业务代码,主业务代码甚至不需要知道工厂类的内部实现,满足了功能也实现了代码分离
当前方式的优化:
- 代码中总会存在不稳定,所以我们要做的就是隔离不稳定,保证其他的代码是稳定的。
- 把散落在各处的不稳定性全部抽取和集中起来管理,使得绝大多数代码保持稳定,这就是IOC的意义。
弊端:
当前的代码中存在的不稳定是变化,工厂模式类 HeroFactory 依旧存在 new 这一个过程。
- 案例代码四:
基于反射 new 对象,消除变化
public class HeroFactory {
public static ISkill getHero(String name) throws Exception {
ISkill iSkill;
//反射
//元素
// C# 、Python
// 类 是对象的抽象
// 对象 类 元素
// reflect.hero.Irelia
String classStr = "reflect.hero." + name;
Class<?> cla = Class.forName(name);
Object obj = cla.newInstance();
return (ISkill)obj;
}
}
反射机制,一般情况,我们可以通过用户输入的名字去new一个对象,但是,如果用反射机制,可以根据用户输入的名字,去创建一个对象,注意:使用反射机制接收到的参数是要求是完整的包路径。
知识点:
java9以上废弃了newInstance,由clazz.getDeclaredConstructor().newInstance()代替
结束: 到这一步,代码基本上就消除了变化。
现在的写法有一定的缺点:
每一次调用都需要重新反射生成对象,性能不好;
而spring是可以把生成的对象加到缓存里面,就不再重复生成;
Spring的配置文件变更属不属于违反OCP原则:
配置文件是属于系统外部的,而不是代码本身的,不违反OCP原则。
面向对象中变化的应对方案?有两种
1.制定一个interface,然后用多个类实现同一个interface ---策略模式
2.一个类,属性 配置类中配置变化的属性值--- 修改数据库链接、端口号 。
更改配置文件,在配置文件中更改,比如修改数据库链接、端口号 支付账号等。
前者更灵活,后者属性值是固定的,扩展性没有第一种强