设计模式系列文章之前已经跟大家聊了一大半了,但是都是聊一些比较常见的设计模式,接下来我们主要是聊一些不常见的设计模式。
不常见的设计模式不意味则就可以不用了解,不用知道。
每一种设计模式都是为了处理一种业务场景,或者解决某种问题而存在的,还是那句话,存在即合理。
学习设计模式优点:
- 提升查看框架源码的能力
- 提升自己对复杂业务的逻辑的代码设计能力以及code能力
- 对面试以及后面的职场道路打下扎实的基础
大纲
对象池模式
在面试的时候,经常会有一些面试官会问一些针对线上一些机器出现OOM 时应该怎么去排查
现在比较常见的是第一种查看机器是否配置了自动生成Dump文件
查看方法一般则是进入机器的:/home/www/XXXXX(项目名)/bin 目录下会有一个 setenv.sh 文件
cat一下文件观察是否配置:
- -XX: HeapDumpOnOutOfMemoryError
- -XX:HeapDumpPath=/tmp/heapdump.hprof
第二种就是自己offline出问题的机器自己dump一下文件。
然后开始用一些分析文件工具比如(MAT)等查看问题
频繁的实例化对象,是很耗费资源的,同时也会频繁的触发GC操作,所以在处理一些封装外部资源的对象时就应该合理的去规避这一点。
解决方案这就是重用和共享这些创建成本高昂的对象,这就是对象池模式,也理解为池化技术。
结构图如下:
- ResourcePool(资源池类):用于封装逻辑的类,即用来保存和管理资源列表
- Resource(资源类):用于封装特定的资源类,资源类被资源池类饮用,所以只要资源池没有被重新分配,所以它们就永远不会被回收。
- Client(请求客户端):使用资源的类 以上结构定义来之设计模式之美
通过结构图其实可以理解为,事先在机器内存中开辟一块内存,用来事先存储需要定义好的对象,当有需要的时候从内存获取一个,不用了之后再返回回去,来实现的一个池化技术。
对象池模式举例
假设现在朋友A想买车了,但是现在又没有那么多钱,只能找同学B借钱同时承诺只要两个月后就会还钱。
同学B的存款是固定的,假设这就是资源池,那么朋友A就是请求客户端了。那么代码的实现就如下所示:
代码语言:javascript复制public class Money {
// 状态是否是被借出去
private Integer status;
// 金额单位 W
private Integer money;
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getMoney() {
return money;
}
public void setMoney(Integer money) {
this.money = money;
}
}
定义一个资源类(money),里面有一个状态标志当前对象是否是被占用,这个是重点
代码语言:javascript复制public class ClassmateBPool {
// 对象池大小 可以理解为理想情况下自己最多可以借2W
public static int numObjects = 2;
// 对象池最大大小 可以理解为自己全部家当最多可以借出去钱
public static int maxObjects = 5;
// 存放对象池中对象的向量(Money)
protected Vector<Money> objectsPool = null;
public ClassmateBPool() {
}
/**
* 初始化对象池
*/
public synchronized void createPool() {
// 确保对象池没有创建。如果创建了,保存对象 objectsPool 不会为空
if (objectsPool != null) {
System.out.println("对象池已经创建");
return;
}
// 创建对象池,添加资源
objectsPool = new Vector<Money>();
for (int i = 0; i < numObjects; i ) {
objectsPool.add(create());
}
System.out.println("对象池开始创建");
}
public synchronized Money getMoney() {
int times = 1;
// 基本判断
if (objectsPool == null) {
throw new RuntimeException("未创建对象池");
}
Money money = null;
// 获得一个可用的对象
money = getFreeMoney();
// money==null ,证明当前没有可以借出去的钱了,则开始从另外的银行卡转钱过来
while (money == null) {
// 开始扩充对象池
createNewMoney(2);
// 扩充完了之后再去获取空闲的金额
money = getFreeMoney();
if (money != null) {
break;
}
// 重试次数
times ;
System.out.println("重试次数:" times);
if (times > 3) {
throw new RuntimeException("当前没有空闲的金额");
}
}
System.out.println("借钱" JSON.toJSONString(money));
// 返回获得的可用的对象
return money;
}
/**
* 返回金额
*/
public void returnMoney(Money money) {
// 确保对象池存在,如果对象没有创建(不存在),直接返回
if (objectsPool == null) {
return;
}
Iterator<Money> iterator = objectsPool.iterator();
while (iterator.hasNext()) {
Money returnMoney = iterator.next();
if (money == returnMoney) {
money.setStatus(0);
System.out.println("还钱成功");
break;
}
}
}
/**
*扩充资源池
*/
public void createNewMoney(int increment) {
for (int i = 0; i < increment; i ) {
if (objectsPool.size() > maxObjects) {
return;
}
objectsPool.add(create());
}
}
/**
*查询空闲金额
*/
private Money getFreeMoney() {
Iterator<Money> iterator = objectsPool.iterator();
while (iterator.hasNext()) {
Money money = iterator.next();
// 判断是否是被占用
if (money.getStatus() == 0) {
money.setStatus(1);
return money;
}
}
return null;
}
public Money create() {
Money money = new Money();
money.setMoney(1);
// 0 未被占用,1 占用
money.setStatus(0);
return money;
}
}
创建资源池类,里面包含了 初始化资源池方法createPool,以及获取空闲金额getMoney方法,以及returnMoney归还金额方法。
代码语言:javascript复制public class ClientTest {
public static void main(String[] args) {
ClassmateBPool classmateBPool = new ClassmateBPool();
// 初始化连接池
classmateBPool.createPool();
// 借钱
Money money = classmateBPool.getMoney();
//还钱
classmateBPool.returnMoney(money);
// result:对象池开始创建
// 借钱{"money":1,"status":1}
// 还钱成功
// 模拟10的请求
for (int i = 0; i < 10; i ) {
Money money1 = classmateBPool.getMoney();
}
//result:
//对象池开始创建
//借钱{"money":1,"status":1}
//借钱{"money":1,"status":1}
//借钱{"money":1,"status":1}
//借钱{"money":1,"status":1}
//借钱{"money":1,"status":1}
//借钱{"money":1,"status":1}
//重试次数:2
//重试次数:3
//重试次数:4
//Exception in thread "main" java.lang.RuntimeException:当前没有空闲的金额
// 模拟10的请求,同时在第3次第4次的是时候还钱了
for (int i = 0; i < 10; i ) {
Money money1 = classmateBPool.getMoney();
if (i == 3 || i == 4) {
classmateBPool.returnMoney(money1);
}
}
//result:
// 对象池开始创建
//借钱{"money":1,"status":1}
//借钱{"money":1,"status":1}
//借钱{"money":1,"status":1}
//借钱{"money":1,"status":1}
//还钱成功
//借钱{"money":1,"status":1}
//还钱成功
//借钱{"money":1,"status":1}
//借钱{"money":1,"status":1}
//借钱{"money":1,"status":1}
//重试次数:2
//重试次数:3
//重试次数:4
//Exception in thread "main" java.lang.RuntimeException:当前没有空闲的金额
}
}
最后就是测试demo了,分别用了一次请求,10次请求,以及有归还的场景下。
以上就是对象池模式定义以及举例代码实现
针对这种池化技术比较常见于C3P0、DBCP、Proxool等连接池,但是也有一个通用的工具common-pool包里面的感兴趣的同学可以通过这个demo之后再去看下源码,对比一下自己是否理解对象池模式。
对象池模式的优点: 能够重复使用对象池的对象,较少了对象的创建,回收以及内存等消耗。 缺点: 需要额外的开辟内存空间,而且这个内存大小,以及对象数量不好把控等。
小结
针对Java当前对象的分配操作也不是很慢了,而且这种业务场景我也基本没有遇到所以没办法给大家找到合适的业务代码举例。
大家可以作为了解的形式去理解一下,方便我们去看一些源码或者一些业务场景提供一定的思考。
下面给大家分享第二种不出常见的模式解释器模式
解释器模式
大家在写正则表达式的时候不知道有没有思考过一个问题,Java它是怎么解析我们写的这个表达式语法呢?
不清的同学就看看这接下来的解释器模式
解释器模式定义:
GOF中的定义:解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。
说白了就是定义一种规则,通过这种规则去解析按照这种规则编写的句子。
有点绕?还是看下结构图吧:
Context(环境):用于封装全局信息,所有具体的解释器都是访问Context AbstraceExpression(抽象表达式):抽象接口类,声明解释器的具体方法 TerminalExpression(终结符表达式):一种解释器类,实现与语法的终结符相关操作。 NonTerminalExpression(非终结符表达式):实现语法的不同规则或符号的类。每一种语法都对应着一个这种类 以上结构图定义来之设计模式之美
举例
看上面的结构图以及定义我估计很多同学还是没有理解,那我们还是来个实际一点的例子好了
假设现在要我们实现小学数学里面的加减乘除运算,输入任意的表达式能准确的得到结果
比如:(6➕6)✖️3 或者 2✖️3➕6
当然上面这种列子我们直接输入肯定是不能实现的,得按照需要的语法规则才行所以:
(6➕6)✖️3 输入的语法可以理解为 6 6 ➕ 3 ✖️ 2✖️3➕6 输入的语法可以理解为 2 3 ✖️ 6 ➕
以(6➕6)✖️3为例整个运算过程,如图所示
话不多说 直接开始撸代码了
代码语言:javascript复制// 定义抽象表达式
public interface Expression {
Long interpret();
}
// 乘法表达式
public class MultiplyExpression implements Expression {
Expression left;
Expression right;
public MultiplyExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public Long interpret() {
return left.interpret() * right.interpret();
}
}
// 数字表达式
public class NumberExpression implements Expression {
private Long number;
public NumberExpression(Long number) {
this.number = number;
}
@Override
public Long interpret() {
return number;
}
}
上面很简单就是定义一个抽象表达式接口,声明解释器的具体方法interpret,同时定义不同的语法NonTerminalExpression
代码语言:javascript复制public class Context {
private Deque<Expression> numbers = new LinkedList<>();
public Long ExpressionInterpreter(String expression) {
Long result = null;
for (String ex : expression.split(" ")) {
if (isOperator(ex)) {
Expression exp = null;
if (ex.equals(" ")) {
exp = new AddExpression(numbers.pollFirst(), numbers.pollFirst());
}
if (ex.equals("*")) {
exp = new MultiplyExpression(numbers.pollFirst(), numbers.pollFirst());
}
// 当还有其他的运算符时,接着在这里添加就行了
if (null != exp) {
result = exp.interpret();
numbers.addFirst(new NumberExpression(result));
}
}
if (isNumber(ex)) {
numbers.addLast(new NumberExpression(Long.parseLong(ex)));
}
}
return result;
}
// 判断是否是运算符号,这里举例只用了➕和✖️,可以扩展更多的其它运算符
private boolean isOperator(String ex) {
return ex.equals(" ") || ex.equals("*");
}
// 判断是否是数字
private boolean isNumber(String ex) {
return ex.chars().allMatch(Character::isDigit);
}
}
定义环境具体的运算规则,需要注意的是后续如果有更多的运算符号只需接着在后面添加就可以了。
这我们是用的Deque作为整个运算的顺序链表,可以用其他的方式比如Stack等。
代码语言:javascript复制public class ExpressionPatternDemo {
public static void main(String[] args) {
Context context = new Context();
Long result = context.ExpressionInterpreter("6 6 3 *");
System.out.println(result);
// result : 36
Context contextTwo = new Context();
Long resultTwo = contextTwo.ExpressionInterpreter("2 3 * 6 ");
System.out.println(resultTwo);
// result : 12
}
}
最后就是来看我们的测试demo了。
按照我们输入的语法规则,解释出我们想要的结果,这就是解释器模式。
因为解释器模式我们本身接触很少,大家作为一个了解就可以了,更多的是运用在表达式,或者规则引擎等地方。
感兴趣的伙伴可以再去看看Pattern.compile的源码,本质也是用的解释器模式
总结
针对这些不怎么常见,或者在业务代码中不怎么常见的模式只能是跟大家分享一下它的原理以及应用场景,大家可以作为了解的形式来理解它,方便在以后遇到的问题,能有一个好的思路。
学习的越多,了解的越多对我们本身是没有坏处的。
本次的分享到这里就结束了,我是敖丙,你知道的越多,你不知道的越多,我们下期见!