23种设计模式总结与分析override2
接下来的几个设计模式在原理理解上有些难度,笔者也是润色琢磨了好久,如有错误,希望各位读者多多指正🤭
7 Chain of Responsibility(责任链)模式
我们可以模拟这样一个场景,创建了一个论坛,我们允许用户在论坛中发表文章,文章必须要经过后台进行信息处理(审核、筛选)才可以发表入库,这种系统最简单直接的做法就是定义一个MsgHandler直接处理文章,但面对以后各种各样不同类型题材的文章,一个类肯定不行,后面写类要写到吐,所以想要拓展性好一些,要定义不同的filter,将这些Filter们构成链条(技巧,链式编程)FilterChain,链条自己也是一个Filter,由FilterChain中的某一个Filter决定链条是否继续
Msg要经过各种不同的Filter过滤后才能存入DB
package com.dp.cor;
import lombok.Data;
import lombok.ToString;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
Msg msg = new Msg();
msg.setMsg("大家好:),<script>,欢迎访问 xxx.com ,大家都是996 ");
FilterChain fc = new FilterChain();
fc.add(new HTMLFilter())
.add(new SensitiveFilter());
FilterChain fc2 = new FilterChain();
fc2.add(new FaceFilter()).add(new URLFilter());
fc.add(fc2);
fc.doFilter(msg);
System.out.println(msg);
}
}
@Data
@ToString
class Msg {
String name;
String msg;
}
interface Filter {
boolean doFilter(Msg m);
}
class HTMLFilter implements Filter {
@Override
public boolean doFilter(Msg m) {
String r = m.getMsg();
r = r.replace('<', '[');
r = r.replace('>', ']');
m.setMsg(r);
return true;
}
}
class SensitiveFilter implements Filter {
@Override
public boolean doFilter(Msg m) {
String r = m.getMsg();
r = r.replaceAll("996", "955");
m.setMsg(r);
return false;
}
}
class FaceFilter implements Filter {
@Override
public boolean doFilter(Msg m) {
String r = m.getMsg();
r = r.replace(":)", "^V^");
m.setMsg(r);
return true;
}
}
class URLFilter implements Filter {
@Override
public boolean doFilter(Msg m) {
String r = m.getMsg();
r = r.replace("mashibing.com", "http://www.mashibing.com");
m.setMsg(r);
return true;
}
}
class FilterChain implements Filter {
private List<Filter> filters = new ArrayList<>();
public FilterChain add(Filter f) {
filters.add(f);
return this;
}
public boolean doFilter(Msg m) {
for(Filter f : filters) {
if(!f.doFilter(m)) return false;
}
return true;
}
}
在Filter接口中定义doFilter,需要过滤的内容返回False,不需要过滤返回Ture继续执行。
继续拓展下去,Java的拦截器Interceptor也需要链式处理,一个request请求可以通过一系列的过滤器过滤到DB(后台服务),然后再返回Response,Response也可以经过一系列的处理
代码如下:
package com.dp.cor.servlet;
import java.util.ArrayList;
import java.util.List;
public class Servlet_Main {
public static void main(String[] args) {
Request request = new Request();
request.str = "大家好:),<script>,欢迎访问 xxx.com ,大家都是996 ";
Response response = new Response();
response.str = "response";
FilterChain chain = new FilterChain();
chain.add(new HTMLFilter()).add(new SensitiveFilter()).add(new HTMLFilter()).add(new PositiveFilter());
chain.doFilter(request, response);
System.out.println(request.str);
System.out.println(response.str);
}
}
interface Filter {
void doFilter(Request request, Response response, FilterChain chain);
}
class HTMLFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
request.str = request.str.replaceAll("<", "[").replaceAll(">", "]") + "HTMLFilter()";
chain.doFilter(request, response);
response.str += "--HTMLFilter()";
}
}
class Request {
String str;
}
class Response {
String str;
}
class SensitiveFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
request.str = request.str.replaceAll("996", "955") + " SensitiveFilter()";
chain.doFilter(request, response);
response.str += "--SensitiveFilter()";
}
}
class PositiveFilter implements Filter{
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
request.str = request.str.replaceAll("995", "994") + " PositiveFilter()";
chain.doFilter(request, response);
response.str += "--responseFilter";
}
}
class FilterChain {
List<Filter> filters = new ArrayList<>();
int index = 0;
public FilterChain add(Filter f) {
filters.add(f);
return this;
}
public void doFilter(Request request, Response response) {
if(index == filters.size()) return;
Filter f = filters.get(index);
index ++;
f.doFilter(request, response, this);
}
}
我们可以自由添加链条的长度,请求链和响应链处理的流程是一致的,先处理链条中的请求链,返回请求是先处理请求链的最后一个,然后一个一个处理。责任链在实际的项目中使用也是比较多的。这样一个项目:界面上有一个用户注册功能,注册用户分两种,一种是 VIP 用户,也就是在该单位办理过业务的,一种是普通用户,一个用户的注册要填写一堆信息,VIP 用户只比普通用户多了一个输入项:VIP 序列号,注册后还需要激活, VIP 和普通用户的激活流程也是不同的,VIP 是自动发送邮件到用户的邮箱中就算激活了,普通用户要发送短信才能激活,为什么呢?获得手机号码以后好发广告短信呀!这个功能项目组就采用了责任链模式,甭管从前台传递过来的是 VIP 用户信息还是普通用户信息,统一传递到一个处理入口,通过责任链来完成任务的处理,类图如下
8 Observer(观察者)模式
在实际项目中,系统的主线程或者重要线程往往希望在任何情况任何场景下做出的操作都是原子性的,即使出错了,也能够及时回滚错误,并通知系统采取相应处理逻辑。比如你到 ATM 机器上取钱,如果你多次输错密码,卡就会被 ATM吞掉,吞卡动作发生的时候,会触发哪些事件呢?第一摄像头连续快拍,第二,通知监控系统,吞卡发生;第三,初始化 ATM 机屏幕,返回最初状态,你不能因为就吞了一张卡,整个 ATM (系统)都不能用了吧。所以往往有些事件\对象我们就需要时刻监控(观察)。
先举一个再简单不过的栗子,小宝宝是脆弱的,他需要父母无微不至地照顾才能健康茁壮地成长,所以需要家长无时不刻地观察守护,所以我们可创建一个宝宝类,其中需要父母的观察
package com.jingger.dp.observer.v4;
/**
* 加入多个观察者
*/
class Child {
public boolean cry = false;
private Dad dad = new Dad();
private Mum mum = new Mum();
private Dog dog = new Dog();
public boolean isCry() {
return cry;
}
public void wakeUp() {
cry = true;
dad.feed();
dog.wang();
mum.hug();
}
}
class Dad {
public void feed() {
System.out.println("dad feeding...");
}
}
class Mum {
public void hug() {
System.out.println("mum hugging...");
}
}
class Dog {
public void wang() {
System.out.println("dog wang...");
}
}
public class Main {
public static void main(String[] args) {
Child c = new Child();
//do sth
c.wakeUp();
}
}
这个方法里面我们把观察者的类(Mum、Dad和Dog)都写在了Baby类里,这样对程序极为不友好,不利于拓展类,耦合度也比较高,这时候我们就需要接口来实现,并把监控baby起床的具体事件封装成类
package com.jingger.dp.observer.v6;
import java.util.ArrayList;
import java.util.List;
/**
* 有很多时候,观察者需要根据事件的具体情况来进行处理
*/
class Child {
private boolean cry = false;
private List<Observer> observers = new ArrayList<>();
{
observers.add(new Dad());
observers.add(new Mum());
observers.add(new Dog());
}
public boolean isCry() {
return cry;
}
public void wakeUp() {
cry = true;
wakeUpEvent event = new wakeUpEvent(System.currentTimeMillis(), "bed");
for(Observer o : observers) {
o.actionOnWakeUp(event);
}
}
}
//事件类 fire Event
class wakeUpEvent{
long timestamp;
String loc;
public wakeUpEvent(long timestamp, String loc) {
this.timestamp = timestamp;
this.loc = loc;
}
}
interface Observer {
void actionOnWakeUp(wakeUpEvent event);
}
class Dad implements Observer {
public void feed() {
System.out.println("dad feeding...");
}
@Override
public void actionOnWakeUp(wakeUpEvent event) {
feed();
}
}
class Mum implements Observer {
public void hug() {
System.out.println("mum hugging...");
}
@Override
public void actionOnWakeUp(wakeUpEvent event) {
hug();
}
}
class Dog implements Observer {
public void wang() {
System.out.println("dog wang...");
}
@Override
public void actionOnWakeUp(wakeUpEvent event) {
wang();
}
}
public class Main {
public static void main(String[] args) {
Child c = new Child();
//do sth
c.wakeUp();
}
}
当Baby哭的时候,程序的一个线程开始,由此引发(观察者的)一连串事件,可见观察者模式和责任链模式有许多异曲同工的地方。很多时候观察者,需要根据事件的具体情况来进行处理,我们处理事件的时候,就需要事件源对象(产生事件的对象)source
class wakeUpEvent{
long timestamp;
String loc;
// 源对象 被观察对象
Child source;
public wakeUpEvent(long timestamp, String loc, Child source) {
this.timestamp = timestamp;
this.loc = loc;
this.source = source;
}
}
当然该事件也可以继承成为一个体系
abstract class Event<T> {
abstract T getSource();
}
class wakeUpEvent extends Event<Child>{
long timestamp;
String loc;
Child source;
public wakeUpEvent(long timestamp, String loc, Child source) {
this.timestamp = timestamp;
this.loc = loc;
this.source = source;
}
@Override
Child getSource() {
return source;
}
}
观察者的类图关系如下:
Spring中常用的listener、JavaScript常用的钩子函数(hook)、Python中的Callback函数还有发布订阅的模式都可以属于观察者
9 Composite(组合)模式
上学的时候都学过“数据结构”,其中有一种结构叫做树形结构,最典型的树形结构就是二叉树。左子树,右子树,什么先序遍历后序遍历什么,重点就是二叉树的的遍历,其中最重要的就是二叉树的构建和遍历。现在看来,树状结果在实际项目应用的确实非常广泛。(Mysql的底层结构B*树)。可以举一个简单的栗子,公司的人事管理就是一个典型的树状结构。
从最高的老大,往下一层一层的管理,最后到我们这层小兵,很典型的树状结构。然后再加上一点学术术语(计算机)总经理叫做根节点,类似研发部经理有分支的节点叫做树枝节点(分支),类似员工 A 的无分支的节点叫做树叶节点(叶子结点),由此可以想到类图
来看这个实现:
import java.util.ArrayList;
/**
* I'm glad to share my knowledge with you all.
* 定义一个根节点,就为总经理服务
*/
public interface IRoot {
//得到总经理的信息
public String getInfo();
//总经理下边要有小兵,那要能增加小兵,比如研发部总经理,这是个树枝节点
public void add(IBranch branch);
//那要能增加树叶节点
public void add(ILeaf leaf);
//既然能增加,那要还要能够遍历,不可能总经理不知道他手下有哪些人
public ArrayList getSubordinateInfo();
}
这个根节点就是我们的总经理 CEO,然后看实现类:
import java.util.ArrayList;
/**
* I'm glad to share my knowledge with you all.
* 根节点的实现类
*/
@SuppressWarnings("all")
public class Root implements IRoot {
//保存根节点下的树枝节点和树叶节点,Subordinate的意思是下级
private ArrayList subordinateList = new ArrayList();
//根节点的名称
private String name = "";
//根节点的职位
private String position = "";
//根节点的薪水
private int salary = 0;
//通过构造函数传递进来总经理的信息
public Root(String name,String position,int salary){
this.name = name;
this.position = position;
this.salary = salary;
}
//增加树枝节点
public void add(IBranch branch) {
this.subordinateList.add(branch);
}
//增加叶子节点,比如秘书,直接隶属于总经理
public void add(ILeaf leaf) {
this.subordinateList.add(leaf);
}
//得到自己的信息
public String getInfo() {
String info = "";
info = "名称:"+ this.name;
info = info + "\t职位:" + this.position;
info = info + "\t薪水: " + this.salary;
return info;
}
//得到下级的信息
public ArrayList getSubordinateInfo() {
return this.subordinateList;
}
}
由此递推,分支节点的结构与根节点相似,就是添加的节点要么是自己(分支节点),要么是叶子节点。最后看叶子节点,也就是员工的接口:
/**
* I'm glad to share my knowledge with you all.
* 叶子节点,也就是最小的小兵了,只能自己干活,不能指派别人了
*/
public interface ILeaf {
//获得自己的信息呀
public String getInfo();
}
下面是叶子节点的实现类:
package com.jingger.common;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 最小的叶子节点
*/
@SuppressWarnings("all")
public class Leaf implements ILeaf {
//叶子叫什么名字
private String name = "";
//叶子的职位
private String position = "";
//叶子的薪水
private int salary=0;
//通过构造函数传递信息
public Leaf(String name,String position,int salary){
this.name = name;
this.position = position;
this.salary = salary;
}
//最小的小兵只能获得自己的信息了
public String getInfo() {
String info = "";
info = "名称:" + this.name;
info = info + "\t职位:"+ this.position;
info = info + "\t薪水:"+this.salary;
return info;
}
}
然后的工作就是组装成一个树状结构和遍历这个树状结构,看 Client.java 程序:
import java.util.ArrayList;
/**
* Client的作用是组装这棵树,并遍历一遍
*/
@SuppressWarnings("all")
public class client {
public static void main(String[] args) {
//首先产生了一个根节点
IRoot ceo = new Root("王大麻子","总经理",100000);
//产生三个部门经理,也就是树枝节点
IBranch developDep = new Branch("刘xx","研发部门经理",10000);
IBranch salesDep = new Branch("马xx","销售部门经理",20000);
IBranch financeDep = new Branch("赵三xx","财务部经理",30000);
//再把三个小组长产生出来
IBranch firstDevGroup = new Branch("杨三xx","开发一组组长",5000);
IBranch secondDevGroup = new Branch("吴xx","开发二组组长",6000);
//剩下的及时我们这些小兵了,就是路人甲,路人乙
ILeaf a = new Leaf("a","开发人员",2000);
ILeaf b = new Leaf("b","开发人员",2000);
ILeaf c = new Leaf("c","开发人员",2000);
ILeaf d = new Leaf("d","开发人员",2000);
ILeaf e = new Leaf("e","开发人员",2000);
ILeaf f = new Leaf("f","开发人员",2000);
ILeaf g = new Leaf("g","开发人员",2000);
ILeaf h = new Leaf("h","销售人员",5000);
ILeaf i = new Leaf("i","销售人员",4000);
ILeaf j = new Leaf("j","财务人员",5000);
ILeaf k = new Leaf("k","CEO秘书",8000);
ILeaf zhengLaoLiu = new Leaf("郑老六","研发部副总",20000);
//该产生的人都产生出来了,然后我们怎么组装这棵树
//首先是定义总经理下有三个部门经理
ceo.add(developDep);
ceo.add(salesDep);
ceo.add(financeDep);
//总经理下还有一个秘书
ceo.add(k);
//定义研发部门 下的结构
developDep.add(firstDevGroup);
developDep.add(secondDevGroup);
//研发部经理下还有一个副总
developDep.add(zhengLaoLiu);
//看看开发两个开发小组下有什么
firstDevGroup.add(a);
firstDevGroup.add(b);
firstDevGroup.add(c);
secondDevGroup.add(d);
secondDevGroup.add(e);
secondDevGroup.add(f);
//再看销售部下的人员情况
salesDep.add(h);
salesDep.add(i);
//最后一个财务
financeDep.add(j);
//树状结构写完毕,然后我们打印出来
System.out.println(ceo.getInfo());
//打印出来整个树形
getAllSubordinateInfo(ceo.getSubordinateInfo());
}
//遍历所有的树枝节点,打印出信息
private static void getAllSubordinateInfo(ArrayList subordinateList){
int length = subordinateList.size();
for(int m=0;m<length;m++){ //定义一个ArrayList长度,不要在for循环中每次计算
Object s = subordinateList.get(m);
if(s instanceof Leaf){ //是个叶子节点,也就是员工
ILeaf employee = (ILeaf)s;
System.out.println(((Leaf) s).getInfo());
}else{
IBranch branch = (IBranch)s;
System.out.println(branch.getInfo());
//再递归调用
getAllSubordinateInfo(branch.getSubordinateInfo());
}
}
}
}
这个程序比较长,如果项目出现这样的程序,肯定要被拉出来review的。你写一大坨的程
序给谁呀,以后还要维护的,程序是要短小精悍!getInfo 每个接口都有为什么不能抽象出来?Root 类和 Branch 类有什么差别?为什么要定义成两个接口两个类?如果我要加一个任职期限,你是不是每个类都需要修改?如果我要后序遍历(从员工找到他的上级领导)能做吗?😵晕了~
所以问题我们一个一个解决把IBranch 和 IRoot 合并成一个接口
这个类图还是有点问题的,接口的作用是什么?定义共性,那 ILeaf 和 IBranch 是不是也有共性呢?有 getInfo(),我们是不是要把这个共性也已经封装起来呢?好,我们再修改一下类图:
类图上有两个接口,ICorp 是公司所有人员的信息的接口类,不管你是经理还是员工,你都有名字,职位,薪水。一个非常清理的树状人员资源管理图出现了,那我们的程序是否还可以优化?可以!你看 Leaf和 Branch中都有 getInfo 信息。好,我们抽象一下:
减少很多工作量了,接口没有了,改成抽象类了,IBranch 接口也没有了,直接把方法放到了实现类中了。我们来看抽象类:
package com.mashibing.dp.composite;
import java.util.ArrayList;
/**
* 定义一个公司的人员的抽象类
*/
@SuppressWarnings("all")
public abstract class Corp {
//公司每个人都有名称
private String name = "";
//公司每个人都职位
private String position = "";
//公司每个人都有薪水
private int salary =0;
/*通过接口的方式传递,我们改变一下习惯,传递进来的参数名以下划线开始
* 这个在一些开源项目中非常常见,一般构造函数都是这么定义的
*/
public Corp(String _name,String _position,int _salary){
this.name = _name;
this.position = _position;
this.salary = _salary;
}
//获得员工信息
public String getInfo(){
String info = "";
info = "姓名:" + this.name;
info = info + "\t职位:"+ this.position;
info = info + "\t薪水:" + this.salary;
return info;
}
}
/*
*普通员工很简单,就写一个构造函数就可以了
*/
class _Leaf extends Corp {
//就写一个构造函数,这个是必须的
public _Leaf(String _name,String _position,int _salary){
super(_name,_position,_salary);
}
}
/*
* 节点类,也简单了很多
*/
class _Branch extends Corp {
//领导下边有那些下级领导和小兵
ArrayList<Corp> subordinateList = new ArrayList<Corp>();
//构造函数是必须的了
public _Branch(String _name,String _position,int _salary){
super(_name,_position,_salary);
}
//增加一个下属,可能是小头目,也可能是个小兵
public void addSubordinate(Corp corp) {
this.subordinateList.add(corp);
}
//我有哪些下属
public ArrayList<Corp> getSubordinate() {
return this.subordinateList;
}
}
/*
* 组装树形结构,并展示出来
*/
@SuppressWarnings("all")
class _Client {
public static void main(String[] args) {
//首先是组装一个组织结构出来
_Branch ceo = compositeCorpTree();
//首先把CEO的信息打印出来:
System.out.println(ceo.getInfo());
//然后是所有员工信息
System.out.println(getTreeInfo(ceo));
}
//把整个树组装出来
public static _Branch compositeCorpTree(){
//首先产生总经理CEO
_Branch root = new _Branch("王大XX","总经理",100000);
//把三个部门经理产生出来
_Branch developDep = new _Branch("刘大XX","研发部门经理",10000);
_Branch salesDep = new _Branch("马二XX","销售部门经理",20000);
_Branch financeDep = new _Branch("赵三XX","财务部经理",30000);
//再把三个小组长产生出来
_Branch firstDevGroup = new _Branch("杨三XX","开发一组组长",5000);
_Branch secondDevGroup = new _Branch("吴大XX","开发二组组长",6000);
//把所有的小兵都产生出来
_Leaf a = new _Leaf("a","开发人员",2000);
_Leaf b = new _Leaf("b","开发人员",2000);
_Leaf c = new _Leaf("c","开发人员",2000);
_Leaf d = new _Leaf("d","开发人员",2000);
_Leaf e = new _Leaf("e","开发人员",2000);
_Leaf f = new _Leaf("f","开发人员",2000);
_Leaf g = new _Leaf("g","开发人员",2000);
_Leaf h = new _Leaf("h","销售人员",5000);
_Leaf i = new _Leaf("i","销售人员",4000);
_Leaf j = new _Leaf("j","财务人员",5000);
_Leaf k = new _Leaf("k","CEO秘书",8000);
_Leaf zhengLaoLiu = new _Leaf("郑老六","研发部副经理",20000);
//开始组装
//CEO下有三个部门经理和一个秘书
root.addSubordinate(k);
root.addSubordinate(developDep);
root.addSubordinate(salesDep);
root.addSubordinate(financeDep);
//研发部经理
developDep.addSubordinate(zhengLaoLiu);
developDep.addSubordinate(firstDevGroup);
developDep.addSubordinate(secondDevGroup);
//看看开发两个开发小组下有什么
firstDevGroup.addSubordinate(a);
firstDevGroup.addSubordinate(b);
firstDevGroup.addSubordinate(c);
secondDevGroup.addSubordinate(d);
secondDevGroup.addSubordinate(e);
secondDevGroup.addSubordinate(f);
//再看销售部下的人员情况
salesDep.addSubordinate(h);
salesDep.addSubordinate(i);
//最后一个财务
financeDep.addSubordinate(j);
return root;
}
//遍历整棵树,只要给我根节点,我就能遍历出所有的节点
public static String getTreeInfo(_Branch root) {
ArrayList<Corp> subordinateList = root.getSubordinate();
String info = "";
for (Corp s : subordinateList) {
if (s instanceof _Leaf) { //是员工就直接获得信息
info = info + s.getInfo() + "\n";
} else { //是个小头目
info = info + s.getInfo() + "\n" + getTreeInfo((_Branch) s);
}
}
return info;
}
}
就是把用到 ICorp 接口的地方修改为 Corp 抽象类就成了,其他保持不变,运行结果还是保持一样。
表中已经定义个一个树形结构,我们要做的就是从数据库中读取出来,然后展现到前台上,这个读取就用个 for 循环加上递归是不是就可以把一棵树建立起来?我们程序中其实还包涵了数据的读取和加工,用了数据库后,数据和逻辑已经在表中定义好了,我们直接读取放到树上就可以了,这个还是比较容易做了的。
上面我们讲到的就是组合模式(也叫合成模式),有时又叫做部分-整体模式(Part-Whole),主要是用来描述整体与部分的关系,用的最多的地方就是树形结构
抽象构件角色(Component):定义参加组合的对象的共有方法和属性,可以定义一些默认的行为或属性;比如我们例子中的 getInfo 就封装到了抽象类中。
叶子构件(Leaf):叶子对象,其下再也没有其他的分支。
树枝构件(Composite):树枝对象,它的作用是组合树枝节点和叶子节点
组合模式的优点有哪些呢?第一个优点只要是树形结构,就要考虑使用组合模式,这个一定记住,只要是要体现局部和整体的关系的时候,而且这种关系还可能比较深,考虑一下组合模式吧。组合模式有一个非常明显的缺点。直接使用了实现类!这个在面向接口编程上是很不恰当的,这个在使用的时候要考虑清楚!
我们在上面也还提到了一个问题,就是树的遍历问题,从上到下遍历没有问题,但是我要是从下往上遍历呢(后序遍历、中序遍历)?比如在人力资源这颗树上,我从中抽取一个用户,要找到它的上级有哪些,下级有哪些,怎么处理?
@SuppressWarnings("all")
public abstract class Corp {
//公司每个人都有名称
private String name = "";
//公司每个人都职位
private String position = "";
//公司每个人都有薪水
private int salary =0;
//父节点是谁
private Corp parent = null;
/*通过接口的方式传递,我们改变一下习惯,传递进来的参数名以下划线开始
* 这个在一些开源项目中非常常见,一般构造函数都是这么定义的
*/
public Corp(String _name,String _position,int _salary){
this.name = _name;
this.position = _position;
this.salary = _salary;
}
//获得员工信息
public String getInfo(){
String info = "";
info = "姓名:" + this.name;
info = info + "\t职位:"+ this.position;
info = info + "\t薪水:" + this.salary;
return info;
}
public Corp getParent(){
return this.parent;
}
protected void setParent(Corp _parant){
this.parent = _parant;
}
}
只要增加两个方法就可以了,一个是设置父节点是谁,一个是查找父节点是谁。有了这个 parent 属性,什么后序遍历(从下往上找)、中序遍历(从中间某个环节往上或往下遍历)都解决了,最后再提一个问题,树叶节点和树枝节点是有顺序的,你不能乱排的,怎么办?比如我们上面的例子,研发一组下边有三个成员,这三个成员是要进行排序的呀,该怎么处理呢?欢迎各位在评论区讨论留言
10 Flyweight(享元)模式
先来看类图
享元的意思就是共享元数据,不用再新建对象了,想用数据就往FlyweightPool里面拿就行了。
现在有个场景,我们的武器需要子弹进行攻击,那怎么造子弹呢?需要用的时候就new一个?那太low了吧😂
package com.jingger.dp.flyweight;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
class Bullet{
public UUID id = UUID.randomUUID();
boolean living = true;
@Override
public String toString() {
return "Bullet{" +
"id=" + id +
'}';
}
}
public class BulletPool {
List<Bullet> bullets = new ArrayList<>();
{
for(int i=0; i<5; i++) bullets.add(new Bullet());
}
public Bullet getBullet() {
for(int i=0; i<bullets.size(); i++) {
Bullet b = bullets.get(i);
if(!b.living) return b;
}
return new Bullet();
}
public static void main(String[] args) {
BulletPool bp = new BulletPool();
for(int i=0; i<10; i++) {
Bullet b = bp.getBullet();
System.out.println(b);
}
}
}
我们就可以建造一个子弹池,需要用的时候从池子里面拿就行了
不需要再新建一个对象就可以创造一个新的子弹了
Java中的String类就是一个典型的享元
public class TestString {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
String s4 = new String("abc");
System.out.println(s1 == s2); //true
System.out.println(s1 == s3); //false
System.out.println(s3 == s4);
System.out.println(s3.intern() == s1);
System.out.println(s3.intern() == s4.intern());
}
}
可以看到经过测试,s1和s2是一个对象,因为它们都是从一个池子里拿出来的,虽然值不同,但没有开辟新的内存空间,所以还是一个对象。
享元模式其实就是池化思想的表达应用(连接池、线程池)。在我们平时项目中应用的也非常多!
11 Proxy(代理)模式
静态代理
什么是代理模式呢?我现在很忙,有很多事情要做,没空理你要找我就先找我的代理人吧。代理人呢,总要知道什么事情能做什么不能做吧。是两个人具备同一个接口,代理人虽然不能干活,但是被代理的人能干活呀。代理在生活中很常见,比如我们想买一件产品,比如联想电脑(虽然我举联想的栗子,但是联想你懂的 美帝良心😂)我们要买联想电脑肯定不是取联想总部去买,而是去它的代理经销商那购买,什么直营店,旗舰店还有私营店都属于代理商。
有这样一个场景,做一个小游戏,其中我想记录坦克的移动时间。最简单的方法就是修改代码,在坦克生成的时候记录一个时间,在移动结束后记录一个时间。但是如果这是我们无法改变方法的源代码呢?这时候就要使用代理了。类图如下
public class Vehicle implements Movable {
/**
* 模拟车辆移动了一段儿时间
*/
@Override
public void move() {
System.out.println("Vehicle moving claclacla...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new VehicleTimeProxy(new Tank()).move();
}
}
class VehicleTimeProxy implements Movable {
Vehicle vehicle;
public VehicleTimeProxy(Vehicle vehicle) {
this.vehicle = vehicle;
this.
}
@Override
public void move() {
long start = System.currentTimeMillis();
tank.move();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
interface Movable {
void move();
}
看运行结果,可以看到通过VehicleTimeProxy代理类实现了移动时间的记录。
这种方法我们只代理了一种类型,如果我们想要代理各种类型呢,如何实现代理的各种组合呢,使用继承?Decorator模式?更加深入,我们可以把代理的对象改成代理的行为类型(Movable类型)
public class Vehicle implements Movable {
/**
* 模拟车辆移动了一段儿时间
*/
@Override
public void move() {
System.out.println("Vehicle moving claclacla...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class VehicleTimeProxy implements Movable {
Movable m;
public VehicleTimeProxy(Movable m) {
this.m = m;
}
@Override
public void move() {
long start = System.currentTimeMillis();
m.move();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
class VehicleLogProxy implements Movable {
Movable m;
public VehicleTimeProxy(Movable m) {
this.m = m;
}
@Override
public void move() {
System.out.println("start moving...");
m.move();
System.out.println("stopped!");
}
}
interface Movable {
void move();
}
可以看到可以实现多个类型的代理,既可以记录移动时间,也可以记录日志信息。从类图结构上来看,静态代理和装饰模式真的很像,异曲同工。都是多个类去实现一个接口,然后一个代理类(装饰类)调用接口对象完成代理\装饰的事件。
动态代理
上面的代理中我们可以看到无论是LogProxy还是TimeProxy都只能代理Vehicle这一个对象,如果我们想要让LogProxy还是TimeProxy可以重用,也就是可以代理多个对象呢?不仅可以代理Vehicle还可以代理boat还可以代理airplane,可以代理任何其他可以代理的类型 Object,这样也叫做动态代理该怎么实现呢?这就需要动态生成,抽离代理与被代理对象,使用jdk的动态代理。
运用Java中的Proxy类反射机制,动态生成对应想要的类,代码如下
class Vehicle implements Movable {
/**
* 模拟车辆移动了一段儿时间
*/
@Override
public void move() {
System.out.println("Vehicle moving claclacla...");
try {
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(5000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
//reflection 通过二进制字节码分析类的属性和方法
Movable m = (Movable)Proxy.newProxyInstance(Vehicle.class.getClassLoader(),
new Class[]{Movable.class}, //tank.class.getInterfaces()
new LogHander(vehicle)
);
m.move();
}
}
class LogHander implements InvocationHandler {
Vehicle vehicle;
public LogHander(Vehicle vehicle) {
this.vehicle = vehicle;
}
//getClass.getMethods[]
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method " + method.getName() + " start..");
Object o = method.invoke(vehicle, args);
System.out.println("method " + method.getName() + " end!");
return o;
}
}
interface Movable {
void move();
}
其中能实现动态代理最重要的方法就是newProxyInstance。可以看到newProxyInstance()方法有三个入参,第一个参数是被代理类的类加载器,第二个参数是一个接口(类)数组(一个类可以实现多个接口),第三个参数是InvocationHandler,意思是被代理对象被调用时该怎么进行处理
从结果中我可以看出,我们没有调用LogHander类中的invoke方法,只是调用了move方法,**为什么能调用invoke方法呢?**我们在主方法中加入下面代码
public static void main(String[] args) {
Tank tank = new Tank();
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles","true");
Movable m = (Movable)Proxy.newProxyInstance(Tank.class.getClassLoader(),
new Class[]{Movable.class}, //tank.class.getInterfaces()
new TimeProxy(tank)
);
m.move();
}
运行之后,在根目录下会生成一个新的文件夹,文件夹下动态生成了一个新的类,这个类就是被代理的类
可以看到这个动态类是被反编译的,实际我们在调用newProxyInstance方法时动态类是在内存中生成的,该类继承了JDK的Proxy类实现了Movable接口,在调用move方法时,在动态类中调用的是super.h(InvocationHandler).invoke()方法,也就是第三个入参(new TimeProxy(Tank))指定的TimeProxy类的invoke方法
invoke方法的第一个参数Object proxy 指的就是代理的对象(LogProxy/TimeProxy),第二个参数Method 指的就是Move方法,Move方法被谁调用呢? method.invoke(tank, args),被坦克类所调用。
调用过程如下图所示:
JDK动态代理的生成过程是通过ASM来实现的,可以实现直接操作二进制码(修改class类文件)。 Java也是一门动态语言,可以直接在代码运行的时候动态修改方法和可以修改的属性。要想了解ASM的童鞋可以自行搜索学习
通过cglib实现动态代理
上面的JDK中的Proxy代理动态代理用的就是ASM。而ASM也是封装的接口调用的,万一版本更新了,接口没有相应地更新不就用不了了。所以这里有一个更为强大的动态代理,就是cglib
import com.mashibing.dp.ASM.TimeProxy;
import com.mashibing.dp.factorymethod.Moveable;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Random;
/**
* CGLIB实现动态代理不需要接口
*/
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
//父类设成坦克
enhancer.setSuperclass(Tank.class);
//回调 InvocationHandler
enhancer.setCallback(new TimeMethodInterceptor());
Tank tank = (Tank)enhancer.create();
tank.move();
}
}
class TimeMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(o.getClass().getSuperclass().getName());
System.out.println("before");
Object result = null;
result = methodProxy.invokeSuper(o, objects);
System.out.println("after");
return result;
}
}
class Tank {
public void move() {
System.out.println("Tank moving claclacla...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
一样可以代理坦克类,可以看到生成的被代理类是(原被代理类)坦克类的子类
动态代理在实际生产项目中也有许多的应用。**SpringAOP(面向切面编程)**就是最典型的代理应用。
加入我们正在开发一个项目。其中的订单模块、库存模块以及商品模块等等这些都已经开发好了。突然有一个需求就是在其中所有的模块中加上一个鉴权的功能,只有一定的权限才能访问操作相应模块。该怎么做呢?我们总不能一个一个模块第修改代码吧。这时候动态代理就派上用场了。在这几个模块的维度上,面向鉴权功能这个方向,切入到整个项目中来,不影响整个项目的结构和进展,并且随时调用实现想要的功能。
也正是Spring的IOC和AOP。BEAN工厂加上灵活的装配再加上动态操作行为的拼接,成就了Spring在Java框架中的一哥地位。