装饰者模式与工厂模式

设计模式(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) 关系,也就是一个类中有另外一个类的引用。为什么要少用继承,多用组合呢?

  1. 继承可能会导致类的无限膨胀。
  2. 继承关系是一种耦合度非常高的关系,因为一旦父类行为发生变化,子类也将受到影响。
  3. 而组合关系耦合度就没有那么高,尤其在组合“接口”时候,程序会变得更加灵活。

注意: 这里讲的接口都是指的抽象类或接口。

//芝士
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) 呢? 比如: 顾客要一份芝士火腿茗茶。

  1. 创建一个茶(Tea)对象
  2. 使用火腿(Ham)对它装饰
  3. 使用芝士(Cheese)对它装饰

那么如何计算最终价格,可以一层一层的加上去。来看图
在这里插入图片描述
装饰者模式的特点

  • 装饰者与被装饰者要有相同的超类
  • 可以使用一个或多个装饰者装饰一个对象
  • 由于装饰者和被装饰者相同的超类,所以在使用被装饰者的地方都可以使用装饰者替代
  • 装饰者可以在调用被装饰者的方法之前或之后加上自己行为,已达到特定的目的
  • 可以在运行时,不限量使用你喜欢的装饰者来装饰对象

装饰者模式的定义
装饰者模式: 动态给对象增加功能,若要扩展功能,装饰者提供了比继承更有弹性的方案。
在这里插入图片描述
接下来装饰饮料
在这里插入图片描述
使用装饰者完成代码
从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();
	}

}

装饰者的缺陷

  1. 可能会产生大量的小类,增加了系统复杂性
  2. 采用装饰这模式的时候,实例化“组件”的时候,可能还需要实例化“装饰者”天晓得实例化多少个!你的代码就特别依赖这些装饰类,从导致程序不容易维护,但是这个通过工厂模式加以解决!

工厂模式

简单工厂

再回头看一下之前的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());
	}

}

认识工厂方法

所有的工厂模式都是用来封装对象的创建。工厂方法模式,通过工厂的子类来决定创建的对象是什么,来达到将对象的创建过程封装的目的,看看类图
在这里插入图片描述

定义工厂方法模式

工厂方法: 定义要给创建对象的接口,由子类决定实例化的那个具体类的实例。工厂方法把对象的创建推迟到了子类。

工厂方法类图结构
在这里插入图片描述

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值