复合模式就是将前面所学的模式结合起来一起解决问题。复合模式在一个解决方案中通常结合两个或多个模式以解决问题。
下面我们用一个例子来说明复合模式的应用
1、我们要让一群鸭子叫,因为叫是鸭子的行为,而且不同鸭子的叫声也不一样,所以我们创建一个公共接口Quackable
package duck;
/**
* 鸭子将实现这个接口,然后实现各自不同的叫声
*/
public interface Quackable
{
public void quack();
}
2、某些鸭子实现了Quackable接口
package duck;
/**
* 绿头鸭
*/
public class MallardDuck implements Quackable {
@Override
public void quack() {
System.out.println("呱呱呱");
}
}
package duck;
/**
* 红头鸭
*/
public class RedheadDuck implements Quackable {
@Override
public void quack() {
System.out.println("呱呱呱");
}
}
package duck;
/**
* 橡皮鸭
*/
public class RubberDuck implements Quackable {
@Override
public void quack() {
System.out.println("吱吱吱");
}
}
3、模拟器模拟鸭子叫
import duck.MallardDuck;
import duck.Quackable;
import duck.RedheadDuck;
import duck.RubberDuck;
import goose.Goose;
import goose.GooseAdapter;
public class Test
{
public static void main(String[] args) {
Test test = new Test();
test.go();
}
void go()
{
Quackable mallardDuck = new MallardDuck();
Quackable redheadDuck = new RedheadDuck();
Quackable rubberDuck = new RubberDuck();
simulate(mallardDuck);
simulate(redheadDuck);
simulate(rubberDuck);
}
void simulate(Quackable quackable)
{
quackable.quack();
}
}
呱呱呱
呱呱呱
吱吱吱
到目前为止一切顺利。以上是一个非常简单的例子,但是没有用到任何模式。下面我们会逐步增加一些功能
我们希望加入一些其他动物,有鸭子的地方就会有鹅,所以我们添加一个鹅类,鹅是咯咯叫
package goose;
/**
* 鹅类
*/
public class Goose
{
public void honk()
{
System.out.println("咯咯咯");
}
}
我们增加了一个鹅类,他不是鸭子类型,所以也不是Quackable类型,也无法传入simulate(Quackable quackable),
我们希望将鹅变成鸭子类型,所以可以用适配器模式,将它适配成鸭子
package goose;
import duck.Quackable;
/**
* 利用适配器,我们可以将鹅适配成鸭子
*/
public class GooseAdapter implements Quackable {
Goose goose;
/**
* 构造器传入需要适配的对象
* @param goose
*/
public GooseAdapter(Goose goose) {
this.goose = goose;
}
/**
* 当调用quack方法时,会被委托到鹅的honk方法
*/
@Override
public void quack() {
goose.honk();
}
}
现在我们的模拟器可以使用鹅了,我们创建鹅对象,将它包装进适配器,以便实现Quackable接口,这样,我们就可以继续了
import duck.MallardDuck;
import duck.Quackable;
import duck.RedheadDuck;
import duck.RubberDuck;
import goose.Goose;
import goose.GooseAdapter;
public class Test
{
public static void main(String[] args) {
Test test = new Test();
test.go();
}
void go()
{
Quackable mallardDuck = new MallardDuck();
Quackable redheadDuck = new RedheadDuck();
Quackable rubberDuck = new RubberDuck();
//将鹅包装进适配器,就可以让鹅像鸭子一样,拥有鸭子的行为
Quackable goose = new GooseAdapter(new Goose());
simulate(mallardDuck);
simulate(redheadDuck);
simulate(rubberDuck);
//因为适配器是包装了鹅,并且实现了Quackable接口,所以能传入
simulate(goose);
}
void simulate(Quackable quackable)
{
quackable.quack();
}
}
simulate会调用许多对象的quack()方法,其中也包括适配器的quack()方法
呱呱呱
呱呱呱
吱吱吱
咯咯咯
下面我们将有一个新的需求,统计鸭子叫声的次数。怎么才能办到呢?我们可以创建一个装饰者,通过把鸭子包装进装饰者对象,给鸭子赋予新的行为(计算次数的行为),这样做的好处就是不用修改鸭子内部的代码。这就是装饰者模式
下面我们开始创建一个装饰者
package decorator;
import duck.Quackable;
/**
* 统计鸭子叫声的装饰者
* 和适配器一样它要实现目标接口
*/
public class QuackCounter implements Quackable
{
Quackable duck; //被装饰的对象
static int count; //我们用静态变量来跟踪所有呱呱叫次数
//将被装饰的对象传入构造器中,并记录在实例变量中
public QuackCounter(Quackable duck) {
this.duck = duck;
}
/**
* 当quack()被调用时,叫声次数加一
*/
@Override
public void quack() {
duck.quack();
count++;
}
/**
* 返回所有在Quackable中发生的呱呱叫次数
* @return
*/
public static int getCount()
{
return count;
}
}
模拟器
import decorator.QuackCounter;
import duck.MallardDuck;
import duck.Quackable;
import duck.RedheadDuck;
import duck.RubberDuck;
import goose.Goose;
import goose.GooseAdapter;
public class Test
{
public static void main(String[] args) {
Test test = new Test();
test.go();
}
void go()
{
//每创建一个Quackable,就用一个新的装饰者包装它,我们只统计鸭子的叫声,所以不用装饰鹅
Quackable mallardDuck = new QuackCounter(new MallardDuck());
Quackable redheadDuck = new QuackCounter(new RedheadDuck());
Quackable rubberDuck = new QuackCounter(new RubberDuck());
//将鹅包装进适配器,就可以让鹅像鸭子一样,拥有鸭子的行为
Quackable goose = new GooseAdapter(new Goose());
simulate(mallardDuck);
simulate(redheadDuck);
simulate(rubberDuck);
//因为适配器是包装了鹅,并且实现了Quackable接口,所以能传入
simulate(goose);
//我们查看鸭子呱呱叫了几次
System.out.println("鸭子总共叫了" + QuackCounter.getCount()+"次");
}
/**
* 这里没有任何变动,被装饰和被适配的对象,还是Quackable类
* @param quackable
*/
void simulate(Quackable quackable)
{
quackable.quack();
}
}
呱呱呱
呱呱呱
吱吱吱
咯咯咯
鸭子总共叫了3次
这个鸭叫计数器很棒,但是我们发现很多鸭子没有被计算进去,因为这些鸭子没有被包装,所以它们没有计数效果,这就是装饰模式的问题!而且每次创建对象都要装饰一边,如果忘记装饰了,就没有统计效果了。所以我们打算将创建和装饰的部分封装起来,这就需要用到工厂模式
所以接下来我们需要创建一个工厂,它专门负责创建装饰过的鸭子,由于此工厂需要创建不同类型的鸭子产品家族,所以要用到抽象工厂模式
首先创建一个抽象工厂
package factory;
import duck.Quackable;
/**
* 我们定义一个抽象工厂,他们的子类会创建不同的家族
*/
public abstract class AbstractDuckFactory
{
public abstract Quackable createMallardDuck();
public abstract Quackable createRedheadDuck();
public abstract Quackable createRubberDuck();
}
创建我们真正需要的工厂,他们负责创建被装饰过的鸭子
package factory;
import decorator.QuackCounter;
import duck.MallardDuck;
import duck.Quackable;
import duck.RedheadDuck;
import duck.RubberDuck;
/**
* CountingDuckFactory扩展自抽象工厂,创建被装饰过的鸭子,它们具有统计的行为
*/
public class CountingDuckFactory extends AbstractDuckFactory {
@Override
public Quackable createMallardDuck() {
return new QuackCounter(new MallardDuck());
}
@Override
public Quackable createRedheadDuck() {
return new QuackCounter(new RedheadDuck());
}
@Override
public Quackable createRubberDuck() {
return new QuackCounter(new RubberDuck());
}
}
再来修改一下模拟器
import decorator.QuackCounter;
import duck.MallardDuck;
import duck.Quackable;
import duck.RedheadDuck;
import duck.RubberDuck;
import factory.AbstractDuckFactory;
import factory.CountingDuckFactory;
import goose.Goose;
import goose.GooseAdapter;
public class Test
{
public static void main(String[] args) {
Test test = new Test();
AbstractDuckFactory countingDuckFactory = new CountingDuckFactory();
test.go(countingDuckFactory);
}
void go(AbstractDuckFactory countingDuckFactory)
{
//每创建一个Quackable,就用一个新的装饰者包装它,我们只统计鸭子的叫声,所以不用装饰鹅
// Quackable mallardDuck = new QuackCounter(new MallardDuck());
// Quackable redheadDuck = new QuackCounter(new RedheadDuck());
// Quackable rubberDuck = new QuackCounter(new RubberDuck());
//现在可以利用工厂来创建鸭子对象了
Quackable mallardDuck = countingDuckFactory.createMallardDuck();
Quackable redheadDuck = countingDuckFactory.createRedheadDuck();
Quackable rubberDuck = countingDuckFactory.createRubberDuck();
//将鹅包装进适配器,就可以让鹅像鸭子一样,拥有鸭子的行为
Quackable goose = new GooseAdapter(new Goose());
simulate(mallardDuck);
simulate(redheadDuck);
simulate(rubberDuck);
//因为适配器是包装了鹅,并且实现了Quackable接口,所以能传入
simulate(goose);
//我们查看鸭子呱呱叫了几次
System.out.println("鸭子总共叫了" + QuackCounter.getCount()+"次");
}
/**
* 这里没有任何变动,被装饰和被适配的对象,还是Quackable类
* @param quackable
*/
void simulate(Quackable quackable)
{
quackable.quack();
}
}
和上次运行结果一样,这次我们能确定所有鸭子都被装饰过了,因为我们使用的是工厂模式,不需要自己创建对象
呱呱呱
呱呱呱
吱吱吱
咯咯咯
鸭子总共叫了3次
这次巡逻员又提出了一个问题,我们为什么要个别管理鸭子呢?如果鸭子对像很多,就乱了
所以我们需要将这些鸭子视为一个集合,我们下一次命令,就能让所有鸭子听命行事,这就太方便了。
我们需要像对待单个对象一样对待集合,这就要用到组合模式。下面我们将创建一个组合类,它需要和节点元素一样实现相同的接口
package compose;
import duck.Quackable;
import java.util.ArrayList;
import java.util.Iterator;
/**
* 创建鸭子组合类,操作这个组合就像和操作节点元素一样,所以它也和节点元素一样实现相同的接口
*/
public class Flock implements Quackable{
ArrayList quackers = new ArrayList();
/**
* 将节点元素记录到属于Flock的集合中
* @param quacker
*/
public void add(Quackable quacker)
{
quackers.add(quacker);
}
/**
* Flock也是Quackable,所以也具备quack()这个方法,此方法会对整个集合产生作用,
* 遍历ArrayList上的每一个元素上的quack()方法
*/
@Override
public void quack() {
Iterator<Quackable> iterator = quackers.iterator();
while (iterator.hasNext())
{
Quackable quacker = iterator.next();
quacker.quack();
}
}
}
这次不用一个一个传入节点元素了,直接传入组合到simulate()方法里
import compose.Flock;
import decorator.QuackCounter;
import duck.MallardDuck;
import duck.Quackable;
import duck.RedheadDuck;
import duck.RubberDuck;
import factory.AbstractDuckFactory;
import factory.CountingDuckFactory;
import goose.Goose;
import goose.GooseAdapter;
public class Test
{
public static void main(String[] args) {
Test test = new Test();
AbstractDuckFactory countingDuckFactory = new CountingDuckFactory();
test.go(countingDuckFactory);
}
void go(AbstractDuckFactory countingDuckFactory)
{
//每创建一个Quackable,就用一个新的装饰者包装它,我们只统计鸭子的叫声,所以不用装饰鹅
// Quackable mallardDuck = new QuackCounter(new MallardDuck());
// Quackable redheadDuck = new QuackCounter(new RedheadDuck());
// Quackable rubberDuck = new QuackCounter(new RubberDuck());
//现在可以利用工厂来创建鸭子对象了
Quackable mallardDuck = countingDuckFactory.createMallardDuck();
Quackable redheadDuck = countingDuckFactory.createRedheadDuck();
Quackable rubberDuck = countingDuckFactory.createRubberDuck();
//将鹅包装进适配器,就可以让鹅像鸭子一样,拥有鸭子的行为
Quackable goose = new GooseAdapter(new Goose());
//创建组合元素Flock,然后把Quackable集合都放进去
Flock flock = new Flock();
flock.add(mallardDuck);
flock.add(redheadDuck);
flock.add(rubberDuck);
flock.add(goose);
//因为组合元素和节点元素都实现了共同接口,操作组合等同于操作节点元素
System.out.println("开始测试主群");
simulate(flock);
//我们查看鸭子呱呱叫了几次
System.out.println("鸭子总共叫了" + QuackCounter.getCount()+"次");
}
/**
* 这里没有任何变动,被装饰和被适配的对象,还是Quackable类
* @param quackable
*/
void simulate(Quackable quackable)
{
quackable.quack();
}
}
完成了同样的效果 ,在组合对象里的遍历,我们也用到了迭代器模式
开始测试主群
呱呱呱
呱呱呱
吱吱吱
咯咯咯
鸭子总共叫了3次
总结:我们做了什么?
有一只鹅出现了,它希望自己像一个Quackable,所以我们利用适配器模式,将鹅是配成Quackable类型,现在你就可以利用鹅的适配器quack()方法让鹅咯咯咯叫
然后呱呱叫学者决定要计算呱呱叫的次数,多以我们用装饰者模式,添加了一个名为QuackCounting的装饰者。它用来追踪quack()被调用的次数。并将调用委托给他所装饰的对象
但是呱呱叫学家担心它们忘了加上Quackcounter装饰者。所以我们使用抽象工厂模式创建鸭子,放他们需要鸭子,就直接跟工厂要,工厂就会给他们装饰过的鸭子。
又是鸭子又是鹅,我们有管理上的困扰,所以我们需要组合模式,将许多quackable集成一个群,我们创建一个组合对象,他也实现了元素对象的接口quackable,使得用户操作它就像和操作元素一样。所以现在呱呱叫学家可以直接操作一个集合了,当需要遍历的时候,用迭代器模式就可以了