首先简单的说明一下什么是开闭原则(OCP)
对扩展开放,对修改关闭,这就是开闭原则。
接下来,我们通过LOL这个案例来实现开闭原则
可以看到,在lol01包下,刚开始英雄只有两个,Main方法如下:
public class Main {
public static void main(String[] args) {
String name = Main.getPlayerInput();
switch (name){
case "Diana":
Diana diana = new Diana();
diana.r();
break;
case "Irelia":
Irelia irelia = new Irelia();
irelia.r();
break;
}
}
private static String getPlayerInput(){
System.out.println("Enter a Hero's name:");
Scanner sc = new Scanner(System.in);
return sc.nextLine();
}
}
哈哈,相信上述代码,大家都能看懂,如果新增了个英雄,就在switch里在增加方法(初学者写出来的),比方说新增了个英雄
接下来来改进一下,第二版代码如下:
public class Main {
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();
}
private static String getPlayerInput(){
System.out.println("Enter a Hero's name:");
Scanner sc = new Scanner(System.in);
return sc.nextLine();
}
}
定义了一个ISkill接口,并使所有的英雄类都实现了这个接口
public interface ISkill {
void q();
void w();
void e();
void r();
}
public class Camille implements ISkill {
public void q(){
System.out.println("Camille q");
}
public void w(){
System.out.println("Camille w");
}
public void e(){
System.out.println("Camille e");
}
public void r(){
System.out.println("Camille r");
}
}
第二版代码只是单纯的使用了interface接口,那么和第一版代码比较,有什么优势和缺点
单纯的interface可以统一方法的调用,但是它不能统一对象的实例化(是不是一脸懵逼了)
面向对象的过程一般只做两种事
1.实例化对象
2.调用对象的方法(完成业务逻辑)
3.只有一段代码中没有new的出现,才能保持代码的相对稳定,才能逐步实现OCP
4.实质上,一段代码要想保持稳定,就不应该负责对象的实例化,但在面向对象编程中,对象实例化是不可能被消除的
5.把对象实例化的过程转移到其他代码片段中(这不就导致其他代码块也就不稳定了吗)
第三代码比第二版代码多了一个工厂模式中静态工厂,将swich中的代码移到静态方法中
这时,main中的方法就很简单了
public class Main {
public static void main(String[] args) throws Exception {
String name = Main.getPlayerInput();
ISkill iSkill = HeroFactory.getHero(name);
iSkill.r();
}
private static String getPlayerInput(){
System.out.println("Enter a Hero's name:");
Scanner sc = new Scanner(System.in);
return sc.nextLine();
}
}
想一想,在第三版代码里,对于main方法来说,确实实现了OCP,代码也相对稳定,但对于HeroFactory类来说,当要新增一个类时,不是还要修改代码,new一个对象吗?
代码中总是会存在不稳定,隔离这些不稳定,保证其他代码是稳定的
拿上面的例子来说,所有不稳定的代码片段都被封装在HeroFactory类,那么除HeroFactory类,那么其他类中的代码都是相对稳定的。但是如果main中的方法足够复杂,这时就有许多的factory类,那么,问题来了,变动的factory会有很多,更改起来也很分散。假想一下,如果有一个超级大的工厂,可以把项目中所有的变动都封装在一起,这样是不是就符合代码的相对稳定性(写到这有没有感觉一丝丝熟悉,好像这就是IOC的雏形)
拿HeroFactory工厂类来分析,代码之所以不稳定,表面上看是因为new不同的对象。实际上是因为不同的变化需求。假想一下,如果用户输入的不是一个字符串,而是一个类,那么这段代码是不是就稳定了。那么,第四版代码出来了:
HeroFactory代码如下:
public class HeroFactory {
public static ISkill getHero(String name) throws Exception {
String classStr = "lol4.hero."+name;
// Class.forName("类的全限名")。
Class<?> clazz = Class.forName(classStr);
// T newInstance() ; 创建Class对象关联类的对象,其实底层也是调用无参数构造器,已经被淘汰。
Object obj = clazz.newInstance();
return (ISkill)obj;
}
}
根据用户传过来的字符串进行拼接,获得类的权限名,通过反射获取到这个类。在main方法中进行测试(代码与第三版的一样)
通过工厂模式加反射机制,很好的消除了变化的部分,使得代码相对稳定。