时隔多年,对《Head First 设计模式》一书的重新阅读和学习已接近尾声,这一章是关于如何将之前介绍的设计模式进行组合使用的,可以当做一种回顾与复习。
所谓的复合模式,就是对多种基本的设计模式进行组合使用,以解决某种会重复出现的一般性问题。
下面遵循原书中的例子,通过对一个“鸭子系统”进行一系列改进以展现如何将众多设计模式组合在一起使用,并协同工作。当然,就像上面说的,这种实现不能称之为复合模式,仅可以看做是一种教学案例。
进击的鸭子
老实说无论是EA还是VSC,对创建大量框架代码而言,对Python的支持远不如Java,所以下面的示例将使用Java完成,但是除了语言细节,大部分设计是通用的,感兴趣的童鞋可以自行编写Python版本的示例。
总觉得这个系列文章标题有种内容欺诈的感觉。
我们的鸭子系统最初长这样:
相当简单,只有三种鸭子,并都有共同的呱呱叫接口Quack
。
完整代码见,这里只展示测试代码:
package pattern12.duck_sys_v1;
import pattern12.duck_sys_v1.duck.MallardDuck;
import pattern12.duck_sys_v1.duck.ReadHeadDuck;
import pattern12.duck_sys_v1.duck.ToyDuck;
public class Main {
public static void main(String[] args) {
Quack duck1 = new MallardDuck();
Quack duck2 = new ReadHeadDuck();
Quack duck3 = new ToyDuck();
duckQuack(duck1);
duckQuack(duck2);
duckQuack(duck3);
}
private static void duckQuack(Quack duck) {
duck.quake();
}
}
// MallarDuck quake!!!
// ReadHeadDuck quake!!!
// ToyDuck quake!!!
现在我们要加入天鹅类型,并想像使用鸭子那样使用天鹅类型,要如何做?
加入天鹅
对,可以使用适配器模式:
可以看到,虽然天鹅Goose
的方法是hook
,并没有呱呱叫的方法,也没有实现接口Quack
,但是我们可以使用适配器模式创建一个适配器GooseAdapter
,通过这个适配器,我们可以让天鹅“看起来像”一个鸭子。
适配器:
package pattern12.duck_sys_v2;
public class GooseAdapter implements Quack {
private Goose goose;
public GooseAdapter(Goose goose) {
this.goose = goose;
}
public void quake() {
this.goose.honk();
}
}
测试:
Quack duck4 = new GooseAdapter(new Goose());
duckQuack(duck1);
duckQuack(duck2);
duckQuack(duck3);
duckQuack(duck4);
完整代码见。
统计叫声
假设现在我们需要对鸭子的叫声进行统计,每次鸣叫都记一次,最后输出鸣叫总次数。
如果不对目前的设计进行重大修改,要如何实现?
对,使用装饰器模式。
我们可以设计一个用于计数鸣叫次数的装饰器:
通过使用装饰器QuackCounter
,我们可以“装饰”Quack
类型的对象,并且在被调用quake
方法的时候进行次数统计,当然最后为了输出统计结果,可能还需要一个获取统计结果的方法。
其次需要注意的是QuackCounter
中用于统计的计数counter
应该是一个类变量(静态变量),因为我们要对需要统计的所有鸭子对象使用这个装饰器,但是统计的时候需要一个“全局变量”来计数,所以这里不能是实例变量。
鸣叫计数装饰器:
package pattern12.duck_sys_v3;
public class QuackCounter implements Quack {
private static int counter = 0;
private Quack quack;
public QuackCounter(Quack quack) {
this.quack = quack;
}
public void quake() {
this.quack.quake();
QuackCounter.counter++;
}
public static int getCounter() {
return QuackCounter.counter;
}
}
测试:
package pattern12.duck_sys_v3;
import pattern12.duck_sys_v3.duck.MallardDuck;
import pattern12.duck_sys_v3.duck.ReadHeadDuck;
import pattern12.duck_sys_v3.duck.ToyDuck;
public class Main {
public static void main(String[] args) {
Quack duck1 = getQuackCounter(new MallardDuck());
Quack duck2 = getQuackCounter(new ReadHeadDuck());
Quack duck3 = getQuackCounter(new ToyDuck());
Quack duck4 = new GooseAdapter(new Goose());
duckQuack(duck1);
duckQuack(duck2);
duckQuack(duck3);
duckQuack(duck4);
System.out.println("quack times is " + QuackCounter.getCounter());
}
private static void duckQuack(Quack duck) {
duck.quake();
}
private static QuackCounter getQuackCounter(Quack quack) {
return new QuackCounter(quack);
}
}
// MallarDuck quake!!!
// ReadHeadDuck quake!!!
// ToyDuck quake!!!
// Goose honk!!!
// quack times is 3
这里只统计了鸭子的叫声,并没有统计天鹅。
完整代码见。
鸭子工厂
从上边的测试代码可以看到,虽然我们有了鸣叫统计装饰器,可以很方便地统计鸣叫次数,但是其代价是我们必须在所有创建鸭子的地方使用装饰器来“装饰”,如果有个地方你忘记了,那结果就是错误的。
所以更好的做法是将鸭子的创建行为集中起来,然后统一调用,这样就不会出现类似的错误。
这正是工厂模式的用途。
因为我们这里需要创建一组鸭子,所以应当使用抽象工厂。
为了简洁,这里没有关联工厂类到具体类型的依赖关系。
创建抽象工厂:
package pattern12.duck_sys_v4.factory;
import pattern12.duck_sys_v4.Quack;
public interface AbsDuckFactory {
public Quack createMallardDuck();
public Quack createReadHeadDuck();
public Quack createToyDuck();
}
创建具体工厂:
package pattern12.duck_sys_v4.factory;
import pattern12.duck_sys_v4.Quack;
import pattern12.duck_sys_v4.QuackCounter;
import pattern12.duck_sys_v4.duck.MallardDuck;
import pattern12.duck_sys_v4.duck.ReadHeadDuck;
import pattern12.duck_sys_v4.duck.ToyDuck;
public class CounterDuckFactory implements AbsDuckFactory {
public Quack createMallardDuck() {
return new QuackCounter(new MallardDuck());
}
public Quack createReadHeadDuck() {
return new QuackCounter(new ReadHeadDuck());
}
public Quack createToyDuck() {
return new QuackCounter(new ToyDuck());
}
}
package pattern12.duck_sys_v4.factory;
import pattern12.duck_sys_v4.Quack;
import pattern12.duck_sys_v4.duck.MallardDuck;
import pattern12.duck_sys_v4.duck.ReadHeadDuck;
import pattern12.duck_sys_v4.duck.ToyDuck;
public class NormalDuckFactory implements AbsDuckFactory {
public Quack createMallardDuck() {
return new MallardDuck();
}
public Quack createReadHeadDuck() {
return new ReadHeadDuck();
}
public Quack createToyDuck() {
return new ToyDuck();
}
}
进行测试:
package pattern12.duck_sys_v4;
import pattern12.duck_sys_v4.factory.AbsDuckFactory;
import pattern12.duck_sys_v4.factory.CounterDuckFactory;
import pattern12.duck_sys_v4.factory.NormalDuckFactory;
public class Main {
public static void main(String[] args) {
AbsDuckFactory duckFactory = new NormalDuckFactory();
testDucks(duckFactory);
duckFactory = new CounterDuckFactory();
testDucks(duckFactory);
}
private static void testDucks(AbsDuckFactory duckFactory) {
Quack duck1 = duckFactory.createMallardDuck();
Quack duck2 = duckFactory.createReadHeadDuck();
Quack duck3 = duckFactory.createToyDuck();
Quack duck4 = new GooseAdapter(new Goose());
duckQuack(duck1);
duckQuack(duck2);
duckQuack(duck3);
duckQuack(duck4);
System.out.println("quack times is " + QuackCounter.getCounter());
}
private static void duckQuack(Quack duck) {
duck.quake();
}
}
// MallarDuck quake!!!
// ReadHeadDuck quake!!!
// ToyDuck quake!!!
// Goose honk!!!
// quack times is 0
// MallarDuck quake!!!
// ReadHeadDuck quake!!!
// ToyDuck quake!!!
// Goose honk!!!
// quack times is 3
测试代码先测试了使用普通工厂创建的鸭子类型,再测试了鸣叫计数器工厂产生的鸭子类型。
组建鸭群
我们现在有了一堆鸭子,但是我们可能不想一只只进行管理,希望组建一个鸭群,而这个鸭群里可以包含子鸭群,然后对鸭群进行统一管理。
这正是我们之前介绍过的组合模式。
这里并没有使用中创建的树形结构通用组件,因为实际操作中我发现那样需要修改大量代码,并且我们这里也不需要为鸭群实现一个迭代器。所以这里简单地创建了一个鸭群类Ducks
来充当树形结构的中间节点,通过它我们可以创建鸭群,并且可以调用quake
方法来实现鸭群的呱呱叫。
创建鸭群:
package pattern12.duck_sys_v5;
import java.util.ArrayList;
import java.util.List;
public class Ducks implements Quack {
private List<Quack> quacks = new ArrayList<Quack>();
public Ducks() {
}
public void addQuack(Quack quack) {
this.quacks.add(quack);
}
@Override
public void quake() {
for (Quack quack : quacks) {
quack.quake();
}
}
}
测试:
package pattern12.duck_sys_v5;
import pattern12.duck_sys_v5.factory.AbsDuckFactory;
import pattern12.duck_sys_v5.factory.NormalDuckFactory;
public class Main {
public static void main(String[] args) {
AbsDuckFactory duckFactory = new NormalDuckFactory();
Ducks ducks = getDucks(duckFactory);
ducks.quake();
}
private static Ducks getDucks(AbsDuckFactory duckFactory) {
Quack duck1 = duckFactory.createMallardDuck();
Quack duck2 = duckFactory.createReadHeadDuck();
Quack duck3 = duckFactory.createToyDuck();
Ducks ducks = new Ducks();
ducks.addQuack(duck1);
ducks.addQuack(duck2);
ducks.addQuack(duck3);
Ducks mallarDucks = new Ducks();
Quack mDuck1 = duckFactory.createMallardDuck();
Quack mDuck2 = duckFactory.createMallardDuck();
Quack mDuck3 = duckFactory.createMallardDuck();
mallarDucks.addQuack(mDuck1);
mallarDucks.addQuack(mDuck2);
mallarDucks.addQuack(mDuck3);
ducks.addQuack(mallarDucks);
return ducks;
}
}
// MallarDuck quake!!!
// ReadHeadDuck quake!!!
// ToyDuck quake!!!
// MallarDuck quake!!!
// MallarDuck quake!!!
观察鸭群
现在我们有了运行良好的鸭群了,假设我们需要对鸭群进行观察,就像蹲点守候的野生摄像师那样,一旦有鸭子鸣叫了,我们希望第一时间知道。
这不正是观察者模式嘛。
Let's go.
像经典的观察者模式那样,我们创建两个接口Observer
和Subject
,分别表示观察者和被观察的主题,然后创建一个观察鸭子的观察者类DuckObserver
,最后让所有的鸭子和鸭群都实现Subject
接口就完成了。
这样我们就可以把观察这注册到鸭子或鸭群,只要相应的鸭子触发notify
,观察者就能收到通知。
当然具体实现的时候可能会创建一些辅助类以减少代码量,增强代码复用,这个在具体实现中说明。
先创建相关接口:
package pattern12.duck_sys_v6.observer;
import pattern12.duck_sys_v6.Quack;
public interface Observer {
public void notifyObsrver(Quack quack);
}
package pattern12.duck_sys_v6.observer;
public interface Subject {
public void registe(Observer observer);
}
创建鸭子观察者:
package pattern12.duck_sys_v6.observer;
import pattern12.duck_sys_v6.Quack;
public class DuckObserver implements Observer {
@Override
public void notifyObsrver(Quack quack) {
System.out.println("observed " + quack.getClass().getSimpleName() + " was quacked");
}
}
创建一个帮助鸭子实现主题接口的帮助类:
package pattern12.duck_sys_v6.observer;
import java.util.ArrayList;
import java.util.List;
import pattern12.duck_sys_v6.Quack;
public class DuckSubjectHelper implements Subject, Observer {
private List<Observer> observers = new ArrayList<Observer>();
public DuckSubjectHelper() {
}
@Override
public void notifyObsrver(Quack quack) {
for (Observer observer : observers) {
observer.notifyObsrver(quack);
}
}
@Override
public void registe(Observer observer) {
observers.add(observer);
}
}
让鸭子类实现主题接口:
package pattern12.duck_sys_v6.duck;
import pattern12.duck_sys_v6.Quack;
import pattern12.duck_sys_v6.observer.DuckSubjectHelper;
import pattern12.duck_sys_v6.observer.Observer;
import pattern12.duck_sys_v6.observer.Subject;
/**
* @author 70748
* @version 1.0
* @created 11-7��-2021 11:53:32
*/
public class MallardDuck implements Quack, Subject {
private DuckSubjectHelper duckSubjectHelper = new DuckSubjectHelper();
public MallardDuck() {
}
public void quake() {
System.out.println("MallarDuck quake!!!");
duckSubjectHelper.notifyObsrver(this);
}
@Override
public void registe(Observer observer) {
duckSubjectHelper.registe(observer);
}
}// end MallardDuck
让鸭群类实现主题接口:
package pattern12.duck_sys_v6;
import java.util.ArrayList;
import java.util.List;
import pattern12.duck_sys_v6.observer.DuckSubjectHelper;
import pattern12.duck_sys_v6.observer.Observer;
import pattern12.duck_sys_v6.observer.Subject;
public class Ducks implements Quack, Subject {
private List<Quack> quacks = new ArrayList<Quack>();
private DuckSubjectHelper duckSubjectHelper = new DuckSubjectHelper();
public Ducks() {
}
public void addQuack(Quack quack) {
this.quacks.add(quack);
}
@Override
public void quake() {
for (Quack quack : quacks) {
quack.quake();
}
duckSubjectHelper.notifyObsrver(this);
}
@Override
public void registe(Observer observer) {
duckSubjectHelper.registe(observer);
}
}
测试:
package pattern12.duck_sys_v6;
import pattern12.duck_sys_v6.duck.MallardDuck;
import pattern12.duck_sys_v6.factory.AbsDuckFactory;
import pattern12.duck_sys_v6.factory.NormalDuckFactory;
import pattern12.duck_sys_v6.observer.DuckObserver;
public class Main {
public static void main(String[] args) {
DuckObserver duckObserver = new DuckObserver();
AbsDuckFactory duckFactory = new NormalDuckFactory();
Ducks ducks = getDucks(duckFactory, duckObserver);
ducks.quake();
MallardDuck mallardDuck = new MallardDuck();
mallardDuck.registe(duckObserver);
mallardDuck.quake();
}
private static Ducks getDucks(AbsDuckFactory duckFactory, DuckObserver duckObserver) {
Quack duck1 = duckFactory.createMallardDuck();
Quack duck2 = duckFactory.createReadHeadDuck();
Quack duck3 = duckFactory.createToyDuck();
Ducks ducks = new Ducks();
ducks.addQuack(duck1);
ducks.addQuack(duck2);
ducks.addQuack(duck3);
Ducks mallarDucks = new Ducks();
Quack mDuck1 = duckFactory.createMallardDuck();
Quack mDuck2 = duckFactory.createMallardDuck();
Quack mDuck3 = duckFactory.createMallardDuck();
mallarDucks.addQuack(mDuck1);
mallarDucks.addQuack(mDuck2);
mallarDucks.addQuack(mDuck3);
ducks.addQuack(mallarDucks);
mallarDucks.registe(duckObserver);
return ducks;
}
}
// MallarDuck quake!!!
// ReadHeadDuck quake!!!
// ToyDuck quake!!!
// MallarDuck quake!!!
// MallarDuck quake!!!
// MallarDuck quake!!!
// observed Ducks was quacked
// MallarDuck quake!!!
// observed MallardDuck was quacked
可以看到,我们可以订阅一只鸭子或者鸭群,只要订阅的对象鸣叫了就会得到通知。
完整代码见。
好了,关于鸭子的故事到此为止了,通过这个示例我们看到了如何在一个系统中使用多种设计模式进行协作,以实现目标。
但是需要强调的是我们不能为了使用设计模式而使用设计模式,哪些地方适合使用设计模式往往需要长时间的经验积累,和反复重构中的摸索,并不是像我们上面的示例中那样逐步套用设计模式而拼凑而成。
当然这不是能一蹴而就的,多看,多写,多想,多半就会慢慢练就。
符合模式是个篇幅相对长的论点,所以这里拆分成上下两篇文章进行说明,下篇会说明一个经典的复合模式:MVC。
谢谢阅读。
文章评论