在 面向对象剖析 和 设计模式剖析 两篇文章中提到面向对象设计是解决复杂系统架构设计有效方法,因为面向对象是模拟人类的思维去发现对象和对象之间的关联,并且通过面向对象的技术去实现系统的可扩展性。设计模式的核心是面向对象,它把面向对象的三大特性:封闭、继承、多态,运用得炉火纯青,总结出常用的23种设计模式。
找到变化 封装变化
这是设计模式中,大师提出的一句高度凝练的话,可以说设计模式所做的事情都是围绕它来讲,把它讲清楚了,基本上设计模式也就掌握得差不多。有两个问题随之出现了:如何找到变化、如何封装变化呢?
对于"如何找到变化"这个问题,是依靠人主动地思考,这个业务点在未来有没有可能变化,如果有可能变化,就要想到一个好的设计方式去设计它。
那"如何去封装变化"呢?找到了这个变化,就要把它"封装"起来,封装的意思是对外是透明的,只要按照这个规则做就行,剩下的内容就是讨论如何封装变化。
封装变化
封装的手段有多种,但从本质上来讲,它的原理就是占位符思想。告诉这里是可以变化的,你按照这个规则来做就行了。
变量--最简单的封装变化
最简单的封装变化的手段就是变量,比如文案、站内信、短信、配置,这些都是不能写死的,后面都有可能是变化的。既然这个变化已经找到,那就封装它呗,用一个变量来代替,每次程序都读这个变量,这个变量存储的地方有很多方法:如数据库、Memcache、Redis等等。
接口--基础的封装变化
提到设计模式,最常用的两个原则就是"开闭原则"、"面向抽象设计,不依赖于具体的实现",所以一般是先定义一个接口,然后实现类就可以无限扩展,在引用的时候是依赖于接口(抽象)。这是最基础的封装变化的手段。
比如策略模式:
interface Strategy {
public void doBussice();
}
public class StrategyContext {
private Strategy strategy;
public StrategyContext(Strategy strategy) {
this.strategy = strategy
}
public void doBussice() {
strategy.doBussice();
}
}
这种模式具有通用性,它的思想是类中引用一个接口,至于接口的实现是什么我不关心,实现这个接口就可以达到可扩展性。像适配器、代理,虽然它们解决问题不同,但本质做法是一样的。
这里有两个点:一个是对象类型转换(子类转换成父类),另一个是多态(面向接口编程)。用这种基本的方法是能解决一部分问题,至少可以做到"开闭原则"、"面向抽象编程",但有时这种模式可引起类爆炸,实现的类太多了,不好控制。
继承--另一种基础的封装变化
除了接口可以实现可扩展性,还有另外一种方法可以实现,那就是继承,继承有两种功能:
- 一个是重用性;
- 另一个是可修改父类特性。正由于继承子类可以修改父类的特性(是不是可扩展呢?),所以有了到底是用接口还是继承之争。有人赞同用接口,不喜欢用继承,认为继承有"破坏"的作用在里面。
比如模板模式:
abstract class template {
abstract void printHeader();
abstract void printBody();
abstract void printFooter();
public void print() {
printHeader();
printBody();
printFooter();
}
}
public class templateImplA extends template {
public void printHeader() {
...
}
public void printBody() {
...
}
public void printFooter() {
...
}
}
这样,可以实现多个子类来达到可扩展性。
接口+继承--一种高级的封装变化
上面提到了接口和继承有过之争,它们并不是水火不相容的,接口是一种标准,继承有两重作用(重用性+可扩展性)。在抽象类中是可以引入接口类,然后在子类中可以重写这个接口来达到增加功能的特性,装修模式就是一个典型的例子。
interface Human(){
public void eat();
}
BasicPerson implements Human {
public void eat(){
...
}
}
abstract class Decorate implements Human {
private Human human;
public Decorate(Human human){
this.human = human;
}
public void eat() {
human.eat();
}
}
public class DecorateImpOne extends Decorate {
public DecorateImpOne(Human human) {
super(human);
}
@Override
public void eat() {
super.eat();
addFunction1();
}
private void addFunction1(){
System.out.println("go KTV ....");
}
}
如果你还想增加功能,还可以继承Decorate去实现更多的子类。
实战
顾客点餐。
分析:拿到这个需求时,先分析它里面包含了哪些对象,然后分析这些对象之间的关系是什么。
对象:顾客、服务员、厨师、命令(菜单)
关联关系:服务员接收顾客的命令,因此服务员与命令的关联是包含关系
厨师与命令,厨师是命令的执行者,它们之间是一种包含关系。
interface Command {
void execute();
}
public class DoFoodCommand implements Command {
private Chef chef;
public DoFoodCommand(Chef chef){
this.chef = chef;
}
public void execute() {
chef.doCook();
}
}
public class Waiter {
private Command command;
public Waiter(Command command) {
this.command = command;
}
public void execute() {
if(command != null) {
command.execute();
}
}
}