结构型-装饰者模式(三)

目录

前言

1. 装饰者模式概述

1.1 定义

1.2 应用场景

2. 咖啡店场景引入该设计模式

2.1 场景介绍

2.2 应用继承的思想设计如下

2.3 用装饰器模式设计如何呢

2.4 咖啡店装饰模式样例代码如下

3. 以JDK IO源码来剖析装饰者模式

4. 模式的一些小感触


前言

   设计模式的原理理解起来相对都不难,但是难的是对设计模式应用场景的把握。

   为什么要学习设计模式呢?

   对于优秀的源码、中间件看的似懂非懂,这或许对设计模式的理解度不够有很大的关系。通常来说优秀框架的源码一般类结构、类之间的关系极其复杂,各种类时常调来调去,所以,为了保证代码的扩展性、灵活性、可维护性等,代码中通常会使用到很多设计模式、设计原则或设计思想。因此如果对设计模式、设计思想等有一定的认识,可能会对阅读源码有很大程度的帮忙。

   常说的23种设计模式一般分成创建型、结构型、行为型三类。

   创建型设计模式主要解决“对象的创建”问题,结构型设计模式主要解决“类或对象的组合或组装”问题,那行为型设计模式主要解决的就是“类或对象之间的交互”问题。


1. 装饰者模式概述

1.1 定义

    动态地给一个对象添加一些额外的职责,而不需要改变原先类的结构。

    若要扩展类的功能,装饰者模式比继承更具有弹性。装饰者模式(Decorator)也称为包装器(Wrapper)模式。

    简单来说就是层层包装,从而达到增强功能的目的。

    HeadFirst中形象的表示如下图:

 类似的Java中的IO其实就是一个典型的装饰者模式

LineInputStream in = new LineInputStream(new BufferedInputStream(new FileInputStream("e://test.txt")));

   如上述代码对读取的功能进行缓冲式读取、按行读取等层层包装,增强读文件的功能。这就是装饰模式的要旨。

1.2 应用场景

  1. 扩展一个类的功能。
  2. 动态增加功能,动态撤销。

优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

缺点:多层装饰比较复杂。


2. 咖啡店场景引入该设计模式

2.1 场景介绍

    在购买咖啡的时候,可以要求加入各种调料(蒸奶、豆浆、摩卡...), 然后在计算价格的时候可以按加入的调料收取不同的费用。

2.2 应用继承的思想设计如下

    直接应用继承设计出来的类是多么的复杂(根据数学的组合,比如4种咖啡,4种调料,每种咖啡只加一种调料就有4*4=16种组合了,如每种咖啡加两种调料那就是 4*4*4=64 种组合...),这真的就是类爆炸了,如下图:

2.3 用装饰器模式设计如何呢

   如下图有四种咖啡、四种调料

  • 1. 将四种咖啡设计成具体的组件类(继承于超类Beverage饮料类)
  • 2. 四种调料设计成具体的装饰者(继承自超类CondimentDecorator调味料装饰类,CondimentDecorator超类又继承自Beverage超类)-- 这里可能有点难理解,其实简单来说只是为了能通过多态的方式实现互相调用,见下述代码理解可能会清晰一点。

    当需要添加一种不同咖啡那么只需要线性增加一个咖啡类,或者是添加新配方(加不同调料组合给现成的咖啡)那么只需要动态的运用不同的调料装饰便可,从而实现灵活删除或增加成不同组合。

    通过对比用继承模式实现会出现类爆炸,而用装饰器模式只需要A+B个类。 这也正是为什么说装饰器模式比继承更具有弹性呢。

2.4 咖啡店装饰模式样例代码如下

思路:

  1. 设计一个饮料超类(可为抽象类也可为接口类)
  2. 实际的咖啡实现该饮料超类接口
  3. 设计一个调味超类接口,该超类为了通过多态特性引用饮料类型需要继承自饮料超类(装饰器类和组件类继承同样的父类,这样我们可以对组件类“嵌套”多个装饰器类。)
  4. 实际的调味类继承自该调味超类
  • 1. 组件Beverage 超类
public abstract class Beverage {
	String description = "Unknown Beverage"; 
	public abstract double cost();
	public String getDescription(){
		return description;
	}
}
  • 2. 具体的饮料类HouseBlend(注:当需要添加其他饮料类,只需按如下方式添加一个对应饮料类便可)
public class HouseBlend extends Beverage {
	public HouseBlend(){
		description = "HouseBlend Coffee, ";
	}

	@Override
	public double cost() {
		return 1.99;
	}
}
  •  3. 装饰者调料超类CondimentDecorator
public abstract class CondimentDecorator extends Beverage {
	Beverage beverage;
	public abstract String getDescription();
}
  • 4. 具体的调料Milk类(注:当需要添加其他调料类,只需按如下方式添加一个对应调料类便可)
public class Milk extends CondimentDecorator {

	public Milk(Beverage beverage){
		this.beverage = beverage;
	}

	@Override
	public double cost() {
		return 0.1 + beverage.cost();
	}

	@Override
	public String getDescription() {
		return beverage.getDescription() + ",Milk ($:0.1)";
	}

}
  •  5. 咖啡店的测试类
public class CoffeeStore {
	public static void main(String[] args) {
		// 一杯纯HouseBlend咖啡
		Beverage beverage = new HouseBlend();
		System.out.println(beverage.description + ", $ "+beverage.cost());
		// 双陪抹茶加牛奶的HouseBlend咖啡
		Beverage beverage1 = new HouseBlend();
		beverage1 = new Milk(beverage1);
		beverage1 = new Mocha(beverage1);
		beverage1 = new Mocha(beverage1);
		System.out.println(beverage1.getDescription() + ", $ "+beverage1.cost());
		
		// 换个类似IO方式的写法
		Beverage beverage2 = new Mocha(new Mocha(new Milk(new HouseBlend())));
		System.out.println(beverage2.getDescription() + ", $ "+beverage2.cost());
	}
}
  • 运行结果如下所示:
HouseBlend Coffee ($:1.99), $ 1.99
HouseBlend Coffee ($:1.99),Milk ($:0.1), Mocha ($:0.2), Mocha ($:0.2), $ 2.49
HouseBlend Coffee ($:1.99),Milk ($:0.1), Mocha ($:0.2), Mocha ($:0.2), $ 2.49

 总结:用上述模式进行设计咖啡店类时,需要做的类个数是A+B,主要是运用了多态与继承以及组合等语法实现了该模式。

 


3. 以JDK IO源码来剖析装饰者模式

   还记得当初第一次接触Java IO 类库感觉挺苦恼的,因为其非常庞大和复杂,有几十个类,负责 IO 数据的读取和写入。现对 Java IO 类做一下分类,从下面两个维度将它划分为四类。具体如下所示:

   针对不同的读取和写入场景,Java IO 又在这四个父类基础之上,扩展出了很多子类。具体如下所示:

  以下代码按行并支持缓存方式读取一个本地文件,这里的代码并没有用上缓存的特性只是以此为例而已

     FileInputStream in = new FileInputStream("e://test.txt");
		BufferedInputStream bufIn = new BufferedInputStream(in);
		LineInputStream lineIn = new LineInputStream(bufIn);
		String str;
		while ((str = lineIn.readLine()) != null) {
			System.out.println(str);
		}

   初学时感觉这么写还真是蛮麻烦的,为啥不直接弄一个类支持按行并支持缓存读取本地文件的类呢?按理来说这样的类实现起来很简单才对,假如有这么一个类LineBufFileInputStream那么要实现按行并支持缓存方式读取一个本地文件代码可为

LineBufFileInputStream in = new LineBufFileInputStream("e://test.txt"); 
  • 注:上述代码看起来似乎实现起来更简单了,但实践上这有点类似咖啡店那个例子,当有很多种读取方式时必定会造成类爆炸。因此,还是按原JDK设计的方式运用类组合的方式实现装饰者模式来实现具体读取功能更具有弹性与可维护性。 
  • 在阅读JDK IO源码时,发现其设计跟咖啡类相当相似,例如InputStream类似于Beverage超类,读与写类似于饮料类,不同的读取方式类似于调料类,针对读与写的组件类可通过组合各种读取方式以增强读写功能。

 

模拟IO的设计,新添加一个将输入的内容转换成小写功能类

   将输入的字节转换成小写,不扣细节哈,其实这个类的写法可以模拟现有的BufferedInputStream类的实现

  • 简易代码如下:
public class LowerCaseInputStream extends FilterInputStream {

	public LowerCaseInputStream(InputStream in) {
		super(in);
	}
 
	public int read() throws IOException {
		int c = in.read();
		return (c == -1 ? c : Character.toLowerCase((char)c));
	}
		
	public int read(byte[] b, int offset, int len) throws IOException {
		int result = in.read(b, offset, len);
		for (int i = offset; i < offset+result; i++) {
			b[i] = (byte)Character.toLowerCase((char)b[i]);
		}
		return result;
	}
	
	// 其他方法。。。
}
  • 简易测试代码
public class InputTest {
	public static void main(String[] args) throws IOException {
		String file = "e:/test1.txt";
		int c;
		InputStream in = new LowerCaseInputStream(new BufferedInputStream(new FileInputStream(file)));
		while ((c = in.read()) >= 0) {
			System.out.print((char) c);
		}
		in.close();
	}
}

4. 模式的一些小感触

  • 注:引自HeadFirst原文

   总的来说:装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。

  •   参考文档

  极客时间《设计模式之美》、HeadFirst《设计模式》

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
东南亚位于我国倡导推进的“一带一路”海陆交汇地带,作为当今全球发展最为迅速的地区之一,近年来区域内生产总值实现了显著且稳定的增长。根据东盟主要经济体公布的最新数据,印度尼西亚2023年国内生产总值(GDP)增长5.05%;越南2023年经济增长5.05%;马来西亚2023年经济增速为3.7%;泰国2023年经济增长1.9%;新加坡2023年经济增长1.1%;柬埔寨2023年经济增速预计为5.6%。 东盟国家在“一带一路”沿线国家中的总体GDP经济规模、贸易总额与国外直接投资均为最大,因此有着举足轻重的地位和作用。当前,东盟与中国已互相成为双方最大的交易伙伴。中国-东盟贸易总额已从2013年的443亿元增长至 2023年合计超逾6.4万亿元,占中国外贸总值的15.4%。在过去20余年中,东盟国家不断在全球多变的格局里面临挑战并寻求机遇。2023东盟国家主要经济体受到国内消费、国外投资、货币政策、旅游业复苏、和大宗商品出口价企稳等方面的提振,经济显现出稳步增长态势和强韧性的潜能。 本调研报告旨在深度挖掘东南亚市场的增长潜力与发展机会,分析东南亚市场竞争态势、销售模式、客户偏好、整体市场营商环境,为国内企业出海开展业务提供客观参考意见。 本文核心内容: 市场空间:全球行业市场空间、东南亚市场发展空间。 竞争态势:全球份额,东南亚市场企业份额。 销售模式:东南亚市场销售模式、本地代理商 客户情况:东南亚本地客户及偏好分析 营商环境:东南亚营商环境分析 本文纳入的企业包括国外及印尼本土企业,以及相关上下游企业等,部分名单 QYResearch是全球知名的大咨询公司,行业涵盖各高科技行业产业链细分市场,横跨如半导体产业链(半导体设备及零部件、半导体材料、集成电路、制造、封测、分立器件、传感器、光电器件)、光伏产业链(设备、硅料/硅片、电池片、组件、辅料支架、逆变器、电站终端)、新能源汽车产业链(动力电池及材料、电驱电控、汽车半导体/电子、整车、充电桩)、通信产业链(通信系统设备、终端设备、电子元器件、射频前端、光模块、4G/5G/6G、宽带、IoT、数字经济、AI)、先进材料产业链(金属材料、高分子材料、陶瓷材料、纳米材料等)、机械制造产业链(数控机床、工程机械、电气机械、3C自动化、工业机器人、激光、工控、无人机)、食品药品、医疗器械、农业等。邮箱:market@qyresearch.com

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值