61儿童节的礼物
马上又到61儿童节了,小曾想给6岁的小侄子买个礼物,往年送的都是积木类型的玩具,今年打算送一盒画笔。
这盒画笔有四种类型:彩铅、蜡笔、水彩笔、油画棒。每种类型都有10只不同颜色的笔。
万物皆对象,小曾想着把这个画笔用程序来描述:
为了方便理解,简化下场景:画笔类型有两种:铅笔、蜡笔,颜色分别有两种:红色、绿色。
涉及角色:抽象画笔、具体画笔
抽象画笔:
/**
* 画笔
* @author zherop
*
*/
public interface Pen {
public void draw(String content);
}
具体画笔:
/**
* 红色铅笔
* @author zherop
*
*/
public class RedPencil implements Pen {
@Override
public void draw(String content) {
System.out.println("红色铅笔:" + content);
}
}
/**
* 绿色铅笔
* @author zherop
*
*/
public class GreenPencil implements Pen {
@Override
public void draw(String content) {
System.out.println("绿色铅笔:" + content);
}
}
/**
* 红色蜡笔
* @author zherop
*
*/
public class RedCrayon implements Pen {
@Override
public void draw(String content) {
System.out.println("红色蜡笔:" + content);
}
}
/**
* 绿色蜡笔
* @author zherop
*
*/
public class GreenCrayon implements Pen {
@Override
public void draw(String content) {
System.out.println("绿色蜡笔:" + content);
}
}
2种类型,2中颜色,需要的子类有2*2=4个。
如果笔的类型新增一种水彩笔的话,那么就需要增加2个子类:红色水彩笔、绿色水彩笔。
如果笔的颜色新增蓝色的话,那么就需要增加2个子类:蓝色铅笔、蓝色蜡笔。
场景类:
public class Client {
public static void main(String[] args) {
// 使用红色铅笔画大地
Pen redPen = new RedPencil();
redPen.draw("大地");
// 使用绿色铅笔画树木
Pen greenPen = new GreenPencil();
greenPen.draw("树木");
}
}
在现实生活中,如果需要增加笔的类型,一般就是再买一盒画笔了。如果要增加颜色,这个可能就不好买了,要么重新买一盒支持更多颜色的商品,要么可以买散装的。
而在程序的世界,如果增加的种类,或者颜色更多,那么子类就会巨多。最终数量=种类*颜色。明显就是可扩展性差,这个忍不了。怎么改进呢?
小曾想了想,笔的变化因素有两种:类型、颜色,如果类型和颜色分开,使用的时候,自由组合,这样问题不就解决了嘛。
抽象画笔:
/**
* 画笔
*
* @author zherop
*
*/
public abstract class Pen {
protected Color color;
public void setColor(Color color) {
this.color = color;
}
public abstract void draw(String content);
}
颜色接口:
/**
* 颜色接口
*
* @author zherop
*
*/
public interface Color {
void bepaint(String penType, String content);
}
具体画笔
/**
* 铅笔
*
* @author zherop
*
*/
public class Pencil extends Pen {
@Override
public void draw(String content) {
String penType = "铅笔";
this.color.bepaint(penType, content);
}
}
/**
* 蜡笔
*
* @author zherop
*
*/
public class Crayon extends Pen {
@Override
public void draw(String content) {
String penType = "蜡笔";
this.color.bepaint(penType, content);
}
}
具体颜色:
/**
* 红色
* @author zherop
*
*/
public class Red implements Color {
@Override
public void bepaint(String penType, String content) {
System.out.println(penType + "+红色:" + content);
}
}
/**
* 绿色
* @author zherop
*
*/
public class Green implements Color {
@Override
public void bepaint(String penType, String content) {
System.out.println(penType + "+绿色:" + content);
}
}
这个时候,类的数量就是:种类+颜色。
如果后续需要增加种类,那么就只需要增加一个类;同样如果要新增颜色,也只需要增加一个类。可扩展性增强很多。
场景类:
/**
* 场景类
* @author zherop
*
*/
public class Client {
public static void main(String[] args) {
Pen pen = new Pencil();
pen.setColor(new Red());
pen.draw("大地");
pen.setColor(new Green());
pen.draw("树木");
}
}
使用铅笔类型的笔,根据需要,先用红色画大地,然后再用绿色画树木。
在现实生活中,不正是使用毛笔,配合颜料来组合使用嘛。那为何小曾不送侄子毛笔+颜料呢?首先小侄子年纪太小,要自己去组合使用,对他来说有点困难,虽然画笔丧失了灵活性,但是使用简单啊。
到这儿故事就讲完了,如果看明白了,那么这一式“桥接模式”就差不多明白咋回事了。
不过看这个名字似乎不太好理解,对吧?
我们再来回顾下故事,首先是一颗继承结构树:
然后演变成两颗继承结构树:
两个继承结构树之间,需要通过组合的方式来产生关联,而这个关联就好比一座桥,桥接模式英文为:Bridge Pattern,名字也是这么来的,只是一般Bridge翻译为桥接。
定义
桥接模式定义:将抽象部分与它的实现部分分离,使它们可以独立地变化。
这个定义还挺抽象的。用上面故事来说明下:
画笔有两个变化因素:种类、颜色。其中种类就是抽象部分,而颜色是实现部分,将这两者分离开来,各自发展,然后使用的时候,根据需要自由组合起来,发挥原有的作用。至于谁是抽象部分、谁又是实现部分,这个主要是看谁是主要的,主要的就是定义中的抽象部分。
涉及角色:
- 抽象类(Abstraction)
定义抽象类的接口,它一般是抽象类,不是接口。其中定义了一个Implementor类型的对象并可以维护该对象。它与Implementor之间有关联关系。比如故事中的Pen。
- 具体抽象类(ConcreteAbstraction)
继承自Abstraction类,实现其定义的接口,并可以调用成员变量Implementor对象的业务方法。比如故事中的Pencil和Crayon。
- 实现类接口(Implementor)
定义实现类的接口,具体实现交给其子类,一般接口只提供基本操作,而在Abstraction定义的接口会做更多复杂的操作,会调用Implementor的方法。比如故事中的Color。
- 具体实现类(ConcreteImplementor)
实现Implementor定义的接口。比如故事中的Red和Green。
类图:
适用场景
- 如果一个系统需要在构件的抽象化角色和具体角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
- 一个类存在两个独立变化的维度,并且这两个维度都需要进行扩展。
扩展
- 可以与适配器模式配合使用。
对于具体实现类,在考虑如何实现实现它的具体逻辑时,如果系统中已经有现成的实现,那么可以通过适配器来进行接口转换,具体实现类本身充当适配器角色,关联其他类,并直接调用该类提供的方法。
完整教程请移步 专栏:设计模式·观想录