Head First 设计模式
软件开发中,不变的真理就是Change
。
打开Head First
设计模式,书上的第一个例子就让我真正明白了我们一直说的Java
的接口优点。
我们常说:Java
中类不允许多继承,而采用了更加合理的接口设计。
继承是为了代码复用的,而接口不过是声明方法,那为什么接口能替代多继承呢?这是我一直以来的疑问。今天,终于找到答案。
接口
注:以下代码均省略get
、set
方法。
原设计
/**
* 动物
*/
public class Animal {
private String name; // 名称
public void eat() {}
}
/**
* 鸟
*/
public class Bird extends Animal {
}
/**
* 鱼
*/
public class Fish extends Animal {
}
/**
* 蝙蝠
*/
public class Bat extends Animal {
}
这是我们的原设计,一个动物类,里面有所有动物公有的eat
方法,然后鸟、鱼和蝙蝠都继承自动物,复用了eat
方法,减少了代码量。
需求变更
客户改需求了,我们要让能飞的动物有fly
方法,也就是我们的Bird
和Bat
类生成的对象有要有fly
这个方法。
然后这些飞的行为都是相同的,我们需要实现复用,最不负责任的方式就是在Animal
类中添加fly
方法,让多个类能继承这个方法,实现复用。
/**
* 动物
*/
public class Animal {
private String name; // 名称
public void eat() {
}
public void fly() {
System.out.println("I can fly");
}
}
指正实现方式极其简单,但会让以后该代码的人痛苦万分。他们会去想,我new
一个鱼,怎么给我提示一个fly
方法?
如此,每次修改,都是对原有设计的破坏!
接口
/**
* 接口:能飞的
*/
public interface Volitant {
void fly();
}
/**
* 鸟
*/
public class Bird extends Animal implements Volitant {
@Override
public void fly() {
System.out.println("I can fly");
}
}
/**
* 蝙蝠
*/
public class Bat extends Animal implements Volitant {
@Override
public void fly() {
System.out.println("I can fly");
}
}
建立一个能飞的接口,让这两个需要飞的动物实现该接口,实现了需要飞的动物能飞,不需要飞的动物不能飞。
书中提出了一个思想:把会变化的部分取出并封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部门。
我对这句话的理解就是将经常需要改动的部分抽象出来并封装,如此,用户改动只改动相关部分,而对原设计没有破坏。
但是这样要怎么实现代码复用呢?同样的代码,在鸟和蝙蝠中写了两次。
复用
/**
* 会飞的实现类
*/
public class VolitantImpl implements Volitant {
@Override
public void fly() {
System.out.println("I can fly");
}
}
/**
* 鸟
*/
public class Bird extends Animal implements Volitant {
private Volitant volitant = new VolitantImpl();
@Override
public void fly() {
this.volitant.fly();
}
}
/**
* 蝙蝠
*/
public class Bat extends Animal implements Volitant {
private Volitant volitant = new VolitantImpl();
@Override
public void fly() {
this.volitant.fly();
}
}
写一个类,这个类去实现Volitant
接口,然后在鸟和蝙蝠中引用这个实例化该类对象,调用其fly
方法。如此,实现了代码复用。当然这个实现方法非常的low
,这里直接去依赖实现类了,这就不好了。
控制反转
这是我自己想的解决方法,欢迎批评指正。
/**
* 鸟
*/
public class Bird extends Animal implements Volitant {
@Autowired
private Volitant volitant;
@Override
public void fly() {
this.volitant.fly();
}
}
写了一段时间的Spring
项目了,解除依赖的方法最常用的就是@Autowired
,然后在实现类中添加@Service
注入。
但是建的是普通的Java
项目,我们不能被@Autowired
吊死,这是一种思想,一种控制注入类的思想。
/**
* 鸟
*/
public class Bird extends Animal implements Volitant {
private Volitant volitant;
public void setVolitant(Volitant volitant) {
this.volitant = volitant;
}
@Override
public void fly() {
this.volitant.fly();
}
}
/**
* 蝙蝠
*/
public class Bat extends Animal implements Volitant {
private Volitant volitant;
public void setVolitant(Volitant volitant) {
this.volitant = volitant;
}
@Override
public void fly() {
this.volitant.fly();
}
}
public class Main {
public static void main(String[] args) {
Volitant volitant = new VolitantImpl();
Bird bird = new Bird();
bird.setVolitant(volitant);
bird.fly();
Bat bat = new Bat();
bat.setVolitant(volitant);
bat.fly();
}
}
为两个需要飞的动物添加set
方法,用于注入实现,在main
方法中实例化的时候将需要的实现注入进去。
其实Spring
的注入也与之类似,只是其更加强大,在程序运行时自动扫描需要注入的地方,然后注入实现。此处,不过是手工注入,我们学习到这种思想就是最大的收获。
Java8
其实从Java8
开始,可以在接口中对方法进行默认的实现。
/**
* 接口:能飞的
*/
public interface Volitant {
default void fly() {
System.out.println("I can fly");
}
}
public class Main {
public static void main(String[] args) {
Bird bird = new Bird();
bird.fly();
Bat bat = new Bat();
bat.fly();
}
}
在接口中声明一个默认的方法,如果没有实现,则调用默认方法。
最佳实践
这是一句在腾讯云+社区一篇博客中借来的话。
框架,就是对设计模式的最佳实践。而我们学习一个框架,不是去为了学多么新多么新的技术,而是学习其越来越好的设计模式。一个框架的流行,不是没有原因的。