设计模式(Design pattern)代表了最佳的实践,是软件开发人员在软件开发过程中面临一般问题的解决方案,是优秀程序猿的经验结晶。
装饰者设计模式动态给一个对象增加额外功能,若要扩展对象的功能,装饰者提供了比继承更具有弹性的方案。
工厂方法设计模式精髓在于封装类中不变的部分,提取其中个性化善变的部分为独立类,降低程序的耦合度,提高了程序扩展性。
喜茶店只供应两种饮料茗茶和咖啡,他们最初系统是这样的。
//饮料接口
public interface Beverage {
/**
* 饮料都要有描述
*
* @return 商品的描述信息
*/
public String desc();
/**
* 饮料都要价钱
*
* @return 饮料的价钱
*/
public double cost();
}
//咖啡
public class Coffee implements Beverage {
@Override
public String desc() {
return "咖啡";
}
@Override
public double cost() {
return 8.5;
}
}
//茶
public class Tea implements Beverage {
@Override
public String desc() {
return "茶";
}
@Override
public double cost() {
return 5.0;
}
}
//喜茶店供应 两种饮料: 茗茶和咖啡
public class TeaStore {
public static void main(String[] args) {
// 顾客来一杯咖啡
Coffee c = new Coffee();
System.out.println(c.desc() + "\t" + c.cost());
// 来一杯茶
Tea t = new Tea();
System.out.println(t.desc() + "\t" + t.cost());
}
}
为了吸引顾客,喜茶店提供了两种配料:芝士(Cheese),火腿(Ham)可以组合搭配每一种饮料。通过继承来实现吗? 来看类图。
这只有两种饮料,两种配料就产生这么多类,每增加一种饮料或配料,类就呈几何级增长。 有的哥们会说这么烂的设计,干嘛不使用组合呢? 接下来组合一下。
继承是 是一个(is a)
关系,而组合是 有一个(has a)
关系,也就是一个类中有另外一个类的引用。为什么要少用继承,多用组合呢?
注意: 这里讲的接口都是指的抽象类或接口。
//芝士
public class Cheese {
public String desc() {
return "芝士";
}
public double cost() {
return 2.5;
}
}
//火腿
public class Ham {
public String desc() {
return "火腿";
}
public double cost() {
return 1.5;
}
}
//咖啡
public class Coffee implements Beverage {
private Cheese cheese;// 芝士
private Ham ham;// 火腿
@Override
public String desc() {
String desc = "咖啡";
if (cheese != null) {
desc = "芝士" + desc;
}
if (ham != null) {
desc = "火腿" + desc;
}
return desc;
}
@Override
public double cost() {
double cost = 8.5;
if (cheese != null) {
cost += cheese.cost();
}
if (ham != null) {
cost += ham.cost();
}
return cost;
}
public void setCheese(Cheese cheese) {
this.cheese = cheese;
}
public void setHam(Ham ham) {
this.ham = ham;
}
}
//喜茶店供应 两种饮料: 茗茶和咖啡
//增加两种种配料: 芝士(Cheese),火腿(Ham)
public class TeaStore {
public static void main(String[] args) {
// 顾客来一杯咖啡
Coffee c = new Coffee();
System.out.println(c.desc() + "\t" + c.cost());
// 来一杯芝士咖啡
Cheese cs1 = new Cheese();
Coffee c1 = new Coffee();
c1.setCheese(cs1);
System.out.println(c1.desc() + "\t" + c1.cost());
// 来一份双芝士的咖啡. 本店不提供!!!
}
}
出现无法实现双份芝士的需求,并且如果增加一种配料必须修改原有的类,饮料类严重依赖具体配料类。
直接使用继承会产生类爆炸,直接组合也无法实现双份芝士的需求,并且让每一种饮料对配料都特别的依赖。
来换一种思路,可不可以使用配料对饮料进行 装饰(decorate)
呢? 比如: 顾客要一份芝士火腿茗茶。
那么如何计算最终价格,可以一层一层的加上去。来看图
装饰者模式的特点
装饰者模式的定义
装饰者模式: 动态给对象增加功能,若要扩展功能,装饰者提供了比继承更有弹性的方案。
接下来装饰饮料
使用装饰者完成代码
从Beverage(饮料)开始,Beverage是一个接口不用动
//饮料接口
public interface Beverage {
/**
* 饮料都要有描述
*
* @return 商品的描述信息
*/
public String desc();
/**
* 饮料都要价钱
*
* @return 饮料的价钱
*/
public double cost();
}
来一个CondimentDecorator(调料装饰者)的抽象类,让他实现Beverage(饮料)接口
public abstract class CondimentDectorator implements Beverage {
// 配料可以装饰任意的饮料,根据针对接口,不针对具体类的原则,这里依赖饮料接口,是一种更为灵活的做法
protected Beverage beverage;
// 配料是用来装饰饮料的,所以使用配料对象的时候,必须传入一个饮料.
public CondimentDectorator(Beverage beverage) {
this.beverage = beverage;
}
}
改造饮料类,去掉饮料类中所有与配料相关的内容
//咖啡
public class Coffee implements Beverage {
@Override
public String desc() {
return "咖啡";
}
@Override
public double cost() {
return 8.5;
}
}
//茶
public class Tea implements Beverage {
@Override
public String desc() {
return "茶";
}
@Override
public double cost() {
return 5.0;
}
}
改造调料类
//芝士
public class Cheese extends CondimentDectorator {
public Cheese(Beverage beverage) {
super(beverage);
}
public String desc() {
return "芝士" + beverage.desc();
}
public double cost() {
return 2.5 + beverage.cost();
}
}
//火腿
public class Ham extends CondimentDectorator {
public Ham(Beverage beverage) {
super(beverage);
}
@Override
public String desc() {
return "火腿" + beverage.desc();
}
@Override
public double cost() {
return 1.5 + beverage.cost();
}
}
开店营业
//喜茶店供应 两种饮料: 茗茶和咖啡
//增加两种种配料: 芝士(Cheese),火腿(Ham)
public class TeaStore {
public static void main(String[] args) {
// 顾客来一杯咖啡
Coffee c = new Coffee();
System.out.println(c.desc() + "\t" + c.cost());
// 来一杯茶
Tea t = new Tea();
System.out.println(t.desc() + "\t" + t.cost());
// 来一杯芝士咖啡
Beverage b1 = new Cheese(new Coffee());
System.out.println(b1.desc() + "\t" + b1.cost());
// 来一份双芝士的咖啡. 本店不提供!!!
Beverage b2 = new Cheese(new Cheese(new Coffee()));
System.out.println(b2.desc() + "\t" + b2.cost());
// 来一份芝士火腿茶
Beverage b3 = new Cheese(new Ham(new Tea()));
System.out.println(b3.desc() + "\t" + b3.cost());
}
}
装饰者在实际开发中有很多的应用场景,比如IO流中应用,Web中解决程序的乱码问题等。接下看一下,IO流中是如何使用装饰者模式的呢?来看类图
接下给InputStream增加一个装饰者,用于把读取数据中的大写转换为小写。
比如:data.txt中内容为 “I Love Java.” 转换后为 “i love java.”
public class LowercastInputStream extends FilterInputStream {
protected LowercastInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int ch = super.read();
// 如果是大写,就转换为小写
if (ch >= 'A' && ch <= 'Z') {
ch = Character.toLowerCase(ch);
}
return ch;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int count = super.read(b, off, len);
for (int i = 0; i < b.length; i++) {
if (b[i] >= 'A' && b[i] <= 'Z') {
b[i] = (byte) Character.toLowerCase(b[i]);
}
}
return count;
}
}
public class Test {
public static void main(String[] args) throws IOException {
LowercastInputStream ls = new LowercastInputStream(new FileInputStream("src/main/resources/data.txt"));
// 一次读一个字节
// int ch;
// while ((ch = ls.read()) != -1) {
// System.out.print((char) ch);
// }
// 一次读一个字符数组
byte[] bytes = new byte[1024];
int len = 0;
while ((len = ls.read(bytes)) != -1) {
String s = new String(bytes, 0, len);
System.out.println(s);
}
ls.close();
}
}
再回头看一下之前的TeaStore类
//喜茶店供应 两种饮料: 茗茶和咖啡
//增加两种种配料: 芝士(Cheese),火腿(Ham)
public class TeaStore {
public static void main(String[] args) {
// 顾客来一杯咖啡
Coffee c = new Coffee();
System.out.println(c.desc() + "\t" + c.cost());
// 来一杯茶
Tea t = new Tea();
System.out.println(t.desc() + "\t" + t.cost());
// 来一杯芝士咖啡
Beverage b1 = new Cheese(new Coffee());
System.out.println(b1.desc() + "\t" + b1.cost());
// 来一份双芝士的咖啡. 本店不提供!!!
Beverage b2 = new Cheese(new Cheese(new Coffee()));
System.out.println(b2.desc() + "\t" + b2.cost());
// 来一份芝士火腿茶
Beverage b3 = new Cheese(new Ham(new Tea()));
System.out.println(b3.desc() + "\t" + b3.cost());
}
}
有没有发现什么问题啊? 是不是出现了很多new啊,new 有问题吗? 凡是new的是不是都是具体的类啊。这样TeaStore是不是依赖每一个具体的饮料类和装饰类啊。 这样一旦我减少或增加一种饮料或配料都要修改这个类,是不是很烦啊。可不可以把这些创建对象的操作封装到一个其他类中,创建完毕后返回一个抽象的接口类型的对象。
定义简单工厂类
我们希望,只要TeaStore(茶店)给工厂下一个订单,工厂就可以把TeaStore需要的饮料生产出来。订单格式为: 芝士火腿咖啡,如果是双芝士,就是芝士芝士咖啡。
public class BeverageFactory {
public static final String TEA = "茶";// 茶
public static final String COFFE = "咖啡";// 咖啡
public static final String CHEESE = "芝士";// 芝士
public static final String HAM = "火腿";// 火腿
// 订单格式如下: 芝士芝士咖啡
public Beverage order(String order) {
// 定义一个变量,用于记录饮料
Beverage beverage;
if (order.contains(TEA)) {
beverage = new Tea();
} else {
beverage = new Coffee();
}
// 只要订单中包含芝士,我就使用芝士对象包装一下
while (order.contains(CHEESE)) {
// 使用芝士对象包装一下
beverage = new Cheese(beverage);
// 去掉本次订单中芝士,因为已经做出来
order = order.replaceFirst(CHEESE, "");
}
// 只要订单中包含获取,我就使用火腿对象包装一下
while (order.contains(HAM)) {
// 使用火腿对象包装一下
beverage = new Ham(beverage);
// 去掉本次订单中火腿,因为已经做出来
order = order.replaceFirst(HAM, "");
}
// 最后返回需要饮料
return beverage;
}
}
在TeaStore中使用简单工厂类
但是我觉得对TeaStore来说还是太麻烦了,我希望TeaStore直接给工厂下一个订单,工厂就能生产出对应产品出来。
//喜茶店供应 两种饮料: 茗茶和咖啡
//增加两种种配料: 芝士(Cheese),火腿(Ham)
public class TeaStore {
public static void main(String[] args) {
// 创建简单工厂对象
BeverageFactory bf = new BeverageFactory();
// 顾客来一杯咖啡
Beverage c = bf.order("咖啡");
System.out.println(c.desc() + "\t" + c.cost());
// 来一杯茶
Beverage t = bf.order("茶");
System.out.println(t.desc() + "\t" + t.cost());
// 来一杯芝士咖啡
Beverage c1 = bf.order("芝士咖啡");
System.out.println(c1.desc() + "\t" + c1.cost());
// 来一份双芝士的咖啡
Beverage c2 = bf.order("芝士芝士咖啡");
System.out.println(c2.desc() + "\t" + c2.cost());
// 来一份芝士火腿茶
Beverage c3 = bf.order("芝士火腿茶");
System.out.println(c3.desc() + "\t" + c3.cost());
}
}
来看一下现在依赖关系图
有的人可能会觉得,你这样搞完只是TeaStore不依赖具体的类了,但是BeverageFactory还是依赖具体的类啊,没错,确实是这样。 但是如果喜茶发展势头良好,开了三十家店呢,这些店是不是依赖工厂就可以了,而不用依赖具体的类。
现在是不是看起来,一切都很美好,但是在进行生产的出现了问题,因为茶的制作流程与咖啡的制作流程有很大的差异。 来看他们的制作流程。
加入制作流程
茶
① 准备茶具,把沸水倒入茶具,使茶具温润洁净
② 取约5克茶叶倒入茶具
③ 往茶具中倒入80度左右的沸水,浸泡3分钟
④ 把茶水倒入茶杯中
咖啡
① 折滤纸,放入杯中
② 湿滤纸,将热水均匀的冲在滤纸上,使滤纸全部湿润,紧贴滤杯壁。
③ 将磨好的咖啡粉倒入滤杯中,轻轻拍平,并在中间位置点一个凹点,作为热水注入点
④ 闷蒸,使用细口壶往凹点注入适量温热水,使咖啡粉充分闷蒸(15s-30s)。
⑤ 注水,以凹点为中心来回画圈直到所需的咖啡量,停止注水.
⑥ 把咖啡倒入咖啡杯中
修改BeverageFactory的createBeverage()方法加入生产流程的代码,加入生产茶和咖啡的制作流程
public class BeverageFactory {
public static final String TEA = "茶";// 茶
public static final String COFFE = "咖啡";// 咖啡
public static final String CHEESE = "芝士";// 芝士
public static final String HAM = "火腿";// 火腿
// 订单格式如下: 芝士芝士咖啡
public Beverage order(String order) {
// 定义一个变量,用于记录饮料
Beverage beverage;
if (order.contains(TEA)) {
System.out.println("执行生产茶的流程...");
System.out.println("此处省略200行代码...");
beverage = new Tea();
} else {
System.out.println("执行生产咖啡的流程...");
System.out.println("此处省略300行代码...");
beverage = new Coffee();
}
// 只要订单中包含芝士,我就使用芝士对象包装一下
while (order.contains(CHEESE)) {
// 使用芝士对象包装一下
beverage = new Cheese(beverage);
// 去掉本次订单中芝士,因为已经做出来
order = order.replaceFirst(CHEESE, "");
}
// 只要订单中包含获取,我就使用火腿对象包装一下
while (order.contains(HAM)) {
// 使用火腿对象包装一下
beverage = new Ham(beverage);
// 去掉本次订单中火腿,因为已经做出来
order = order.replaceFirst(HAM, "");
}
// 最后返回需要饮料
return beverage;
}
}
由于茶和咖啡的制作过程有非常大不同,把他们放到一个类中代码显得比较臃肿,如果将来再增加一个奶茶的饮料,会就必须修改这个类,代码会变得更加臃肿。
找出程序中可能变化的地方,把它单独出来,不要和不变的混在一起。
重新设计工厂,把把生产茶功能放到茶工厂中,把生产咖啡的功能放到咖啡工厂中。
把原有的饮料工厂,改为抽象工厂. 具体生产饮料代码被提取到了子类中
public abstract class BeverageFactory {
public static final String CHEESE = "芝士";// 芝士
public static final String HAM = "火腿";// 火腿
// 订单格式如下: 芝士芝士咖啡
public Beverage order(String order) {
// 定义一个变量,用于记录饮料
Beverage beverage = createBeverage();
// 只要订单中包含芝士,我就使用芝士对象包装一下
while (order.contains(CHEESE)) {
// 使用芝士对象包装一下
beverage = new Cheese(beverage);
// 去掉本次订单中芝士,因为已经做出来
order = order.replaceFirst(CHEESE, "");
}
// 只要订单中包含获取,我就使用火腿对象包装一下
while (order.contains(HAM)) {
// 使用火腿对象包装一下
beverage = new Ham(beverage);
// 去掉本次订单中火腿,因为已经做出来
order = order.replaceFirst(HAM, "");
}
// 最后返回需要饮料
return beverage;
}
// 由于生产茶和咖啡的工艺都比较复杂,所以单独建了工厂进行生产
protected abstract Beverage createBeverage();
}
咖啡工厂专门用于生产咖啡
public class CoffeFactory extends BeverageFactory {
public Beverage createBeverage() {
System.out.println("泡咖啡的流程");
System.out.println("此处省略300行..");
return new Coffee();
}
}
茶工厂专门用于生产茶
public class TeaFactory extends BeverageFactory {
@Override
public Beverage createBeverage() {
System.out.println("泡茶的流程");
System.out.println("此处省略200行...");
return new Tea();
}
}
TeaStore茶店
//喜茶店供应 两种饮料: 茗茶和咖啡
//增加两种种配料: 芝士(Cheese),火腿(Ham)
public class TeaStore {
public static void main(String[] args) {
// 创建具体工厂对象
BeverageFactory cf = new CoffeFactory();
BeverageFactory tf = new TeaFactory();
// 顾客来一杯咖啡
Beverage c = cf.order("咖啡");
System.out.println(c.desc() + "\t" + c.cost());
// 来一杯茶
Beverage t = tf.order("茶");
System.out.println(t.desc() + "\t" + t.cost());
// 来一杯芝士咖啡
Beverage c1 = cf.order("芝士咖啡");
System.out.println(c1.desc() + "\t" + c1.cost());
// 来一份双芝士的咖啡.
Beverage c2 = cf.order("芝士芝士咖啡");
System.out.println(c2.desc() + "\t" + c2.cost());
// 来一份芝士火腿茶
Beverage c3 = tf.order("芝士火腿茶");
System.out.println(c3.desc() + "\t" + c3.cost());
}
}
所有的工厂模式都是用来封装对象的创建。工厂方法模式,通过工厂的子类来决定创建的对象是什么,来达到将对象的创建过程封装的目的,看看类图
工厂方法: 定义要给创建对象的接口,由子类决定实例化的那个具体类的实例。工厂方法把对象的创建推迟到了子类。