设计原则
开闭原则:对扩展开放,对修改关闭
里氏替换原则:尽量不重写父类的非抽象方法
迪米特法则:不要和陌生人说话
依赖倒置原则:面向接口、面向抽象编程。spring容器和Bean的自动注入就是最生动的实践
单一职责原则:一个人只做一件事,别乱套了
接口隔离原则:和单一职责类似
合成复用原则:尽量先通过组合等来实现,而非通过继承来实现 优先考虑has A而非is A
设计模式
(1)单例模式 饿汉式 懒汉式 DCL双重检测锁 volatile保证可见性 防止指令重排 MESI StoreBuffer Invalidate Queue
(2)简单工厂
(3)工厂方法 产品一个接口多个实现,工厂一个接口对应产品每个实现有一个实现,先new工厂,再getInstance
(4)抽象工厂 多种产品,每个产品一个接口 一个工厂接口,生产所有产品 每个品牌实现一个工厂,生产全部种类的产品
其实你会发现抽象工厂比工厂方法模式只是工厂接口多一些生产不同产品的方法
(5)建造者模式 Product类 AbstartBuilder ConcreteBuilder Director ,builder完成每一步,Director决定顺序。常用链式编程。
director可省略,调用者可直接根据用不同顺序、填入不同参数调用ConcreteBuilder的方法,链式编程返回结果
(6)原型模式 实现cloneable接口 问题:浅拷贝 如何深拷贝->把引用的对象递归地都进行拷贝 有工具类可用 还可以先序列化,再反序列化,也可实现深拷贝
复杂对象不new了,直接clone,省事儿。spring中默认单例模式,但也可手动指定prototype模式
(7) 适配器模式
Computer surf(USBInterface usb) 可以理解为新系统中规范都是要实现USBInterface接口
一个被适配的类Adaptee request()方式{sout("正在上网)} 本质上可以实现surf的功能,但并未实现USBInterface接口
这是就得出现一个适配器类Adapter,它一方面必须实现USBInterface接口;另一方面它可以继承(is)Adaptee类或者持有(have)Adaptee对象,
然后实现USBInterface中的surf方法,在具体实现中再通过super.request()或者adaptee.request()的方式实现功能
(8)桥接模式 两个维度 比如品牌种类一个维度 产品种类一个维度 Brand接口 info()方法 AbstartComputor实现Brand接口,并且内部持有Brand对象 LapTop DeskTop Pad分别继承AbstartComputor
比传统的多层继承更灵活
(9)静态代理
典型案例 JDK中的Thread对象 实现Runable接口 ,并且持有一个Runable对象(target)
Thread里面的run方法,if(runnable != null){runnable.run()}
要想运行,要么重写run方法,要么传入Runable对象,否则什么都不会做
调用是调用start() 方法 然后会通过start0()调用本地方法开启线程执行任务
(10)动态代理
cglib/jdk/javasist等
典型案例 mybatis自动代理Mapper spring的AOP底层也是自动代理
(11)装饰者模式
典型的例子 springsession 因为session都是通过request.getSession()获取到的,于是设置一个拦截器,放行的时候放入RequestWrapper对象
装饰者模式跟动态代理显然不一样
但是跟静态代理有相似之处
相同之处:都能实现对原有代码的增强、前后处理等,代码实现上很类似
不同之处:
1、静态代理模式的中心是代理人(Thread),你传入的Runable对象是被代理的,可以有很多种,你来什么我都能代理,
代理人做的事是固定的(核心就是用start0方法执行你给我的东西)
2、装饰者模式的中心是被装饰者(HttpRequest),后续的Wrapper对象只是对它的增强,并且这个装饰者是可以嵌套的。
被装饰者做的事是给定的,但每个装饰者做的事都是不固定的
(12)享元模式
典型例子是常量池、比如String的常量池,-128-127的Integer的缓存等
典型例子 比如原神里的树 很多,但种类就只有那么几种
TreeNode 持有 Tree ,除此之外还有int x和int y(坐标) ,TreeFactory中通过ConcurrentHashMap持有几种树 (String name,Tree tree)
TreeFactory中有一个getTree()方法,如果map中有就直接返回,没有就new并放到map中再返回。
Tee里面的属性都用final修饰,因此是线程安全的
优点:在系统中存在大量相同对象时可以节省大量的内存资源
(13)门面模式/外观模式 Facade
外观(Facade)角色 SubSystem角色 Client角色
client调用Facade中的方法,Facade再调动subSystem中的方法
其实就是一个封装,提供一个统一的访问点
优点:封装就是优点
缺点:你并没有屏蔽subsystem,client可能会绕过Facade直接调用subsystem中的方法,产生意料之外的结果
(14)组合模式
典型例子 逐级汇总数据
count()接口(Counter)
City实现count()
Composite也实现实现count(),并且持有Counter的实现类 一个List
Composite的count方法,返回起持有的所有Counter的count()结果的和
这个Composite本身也可以作为Counter再被上级汇总
(15)模板方法模式
定义方法的骨架,而将具体实现留到子类中
典型的例子:HttpServlet(抽象类)本身实现了Servlet接口,实现了其service(ServletRequest requet,ServletResponse response)方法,
service方法又调用了this.service(HttpServletRequest requet,HttpServletResponse response)方法,在此方法中先通过req.getMethod获取Http
方法,如GET、POST、PUT、HEAD等,然后调用相应的doGet(),doPost()方法。doGet等方法中直接返回错误代码405,错误信息http.method_get_not_support
要想不报错,必须重写其doGet()等方法。
(16)观察者模式,又称发布订阅模式
一个Subject角色,一个Observer角色,Subject持有Observer的对象(可能有多个),在某个方法被调用是调用Observer中的某个方法(称为通知,或者发布消息)
典型的例子 Spring中的Interceptor
(17)策略模式
原来是大量if-else,根据传入的int type来决定执行哪个分支。
策略模式改成定义一个接口,多个实现类作为枚举,然后设置一个根据int type来寻找对应实现类的方法。后续如果要增加一种策略就可以直接增加一个枚举类即可,不需要修改原来if-else所在方法的代码。
传入不同实现,实现不同行为,比如ThreadPoolExecutor中的拒绝策略
策略模可以用来改写大量嵌套的if else语句,直接传入实现类
表驱动编程 比如1-12月的天数可以放到一个map中,每次根据传入的月份获取相应的天数,这样就不用一堆if/else语句了
状态模式?
卫语句 让简单的逻辑先提前返回
联想我们现有的业务代码,里面有些很复杂的嵌套if-else,能用上述几种方式进行吗?
状态模式、策略模式、表驱动编程都不行,因为这种是针对if多个条件,但不嵌套的情况;如果是嵌套if-else,显然是行不通的
卫语句可以
不对,好像用表驱动+策略模式可以,每个清单报表都搞一个类,每个类的一些特殊操作定义在里面。这些特殊操作都有一些共同的回调时机,比如插入前,插入后,删除前,删除后等,然后再特定时机调用这些回调函数就行。获得每个清单报表的类,可以用表驱动方式,写在一个final修饰的map里面,比如清单id是0001,获取xxx清单对象,清单id是
0002,获取yyy清单对象
(18)状态模式
TCP通信中的状态机
包括图灵机也是一种状态机
典型的例子:
先不用设计模式:
比如一个Machine类,内部有一个属性int state,同时有一个操作方法operate1(){
if(state == 1){
sout(行为A)
state = 2
}else if(state == 2){
sout(行为B)
state=3
}else{
sout(行为C)
state=2
}
};operate2()、operate3()等等
如果像这样用大量if-else语句去判断就会很乱
怎么改进?
用状态模式
Machine持有一个状态类,这个状态类也实现operate1(),operate2(),operate3()……
然后这个状态类弄成一个枚举,比如NO_MONEY,HAS MONEY,SELL,SELLOUT等,然后Machie中operate1()中就是用state.operate1(this),operate2等同理
在具体的State枚举类中去实现对应状态下的行为,包括改变状态
(19)命令模式
一个Command接口,里面有execute和undo两个接口方法
LightOnCommand实现此接口,持有一个LightReceiver对象,
LightOnCommand的execute方法就是调用LigtReceiver的on方法
LightOnCommand的undo方法就是调用LightReceiver的off方法
还可以有LightOffCommand,跟LightOnCommand正好相反
注意LightReceiver并不实现任何接口,因为它的on和off方法是自己独有的,可能另一个receiver就不是这俩方法
再来个NoCommand类,里面什么也没做。主要是省略掉一些判空操作。
有一个RemoteController(遥控器对象),持有两个Command的数组(onCommands和offCommands)和一个undoCommand,模拟有两列按钮加一个撤销按钮
new RemoteController时初始化全部赋值NoCommand
然后有一个setCommand方法,指定数组编号,传入onCommand和offCommand设置
然后有一个pushOnButton(int no)按钮和一个pushOffButton(int no)按钮,调用则表示按下某个按键
pushOnButton方法:{onCommands[no].execute();undoCommand=onCommands[no]}
pushOffButton方法:{offCommands[no].execute();undoCommand=offCommands[no]}
另外还有一个单独的pushUndoButton方法,按下则撤销上一个操作
{
undoCommand.undo();
undoCommand = new NoCommand(); //只能撤销一步NoCommand可以考虑做成枚举 //如果做成一个栈结构,也可以实现多次的撤销
}
1、通过Command这个中间变量,实现了调用者和被调用者的松耦合。调用者不需要关心应该向谁发命令,只需要想需要实现什么就行。并且还能实现对操作的记录、撤销等
2、新的命令很容易加到系统中去,扩展性强
(20)迭代器模式
比如一个集合,里面有List还有数组,是多种方式组成的集合
提供一种遍历元素的统一接口,用一致的方法去遍历集合元素,而不需要这些集合的底层结构
典型的比如jdk就用迭代器模式实现了对ArrayList和LinkedList的统一遍历
//TODO 代码演示
//源码查看
(21)中介者模式
典型的例子注册中心
如果没有注册中心,每个微服务都要自己去找其他微服务,他们的ip还可能变化,非常难以维护
引入注册中心后,每个微服务都注册到注册中心,并且要找其他微服务时都到注册中心去查
从网状结构变成星状结构
缺点:中介者角色的跟其他角色可能耦合度较高,代码复杂,较难维护
(22)访问者模式
最复杂的设计模式,使用频率不高。但一旦你需要使用,那就真的需要了。
将数据结构和数据操作分离
数据对象结构比较稳定,但访问、操作方法经常变化
一些东西,很多人都可以访问 比如就是一个抽象的Character类
实现Charater的有Gallen,有Lax,有Yasoo,他们每个人都有一些属性,一种一些是都有的,比如血量;还有一些是跟别人不一定一样的,
比如盖伦没有蓝条,拉克丝有,而亚索的是护盾积累进度条
同时有很多的访问方法,比如访问是否有蓝,比如访问当前护盾值,每个英雄的访问都应该有自己的实现
比如有这样一个Visitor接口,里面有visit(Character character)方法
Class MagicVisitor implements Visitor{
@Override
public void visit(Gallen gallen){
//访问盖伦蓝量的方法
比如直接返回0
System.out.println(0);
}
@Override
public void visit(Lax lax){
//访问拉克丝蓝量的方法
System.out.println(lax.magic);
}
@Override
public void visit(Yasoo y){
//访问亚索蓝量的方法
System.out.println(y.whiteValue);
}
}
Class HpVisitor implements Visitor{
//跟MagicVisitor类似,也是每个英雄都有对应的重载方法
}
注意现在还不是访问者模式。现在怎么访问
public class Demo{
Character gallen = new Gallen();
MagicVisitor magicVistor = new MagicVisitor();
magicVistor.visit(gallen);
//其他英雄、其他类型的访问、类似
}
现在的问题是什么,那就是每种访问方式要分别调用visit方法
比如magicVistor.visit(gallen);upVisitor.visit(gallen);
如何实现统一的访问呢?
增加一个Acceptor接口,里面有void accept(Vistor visitor)方法
然后每个Character的实现类再额外实现Acceptor接口
里面的代码都一样
public void accept(Vistor visitor){
visitor.visit(this);
}
根据传过来的Visitor的不同、调用者(this)的不同,就能实现优雅的“来访者统一接待”了
(23)解释器模式
典型的场景是计算一个表达式,比如就是一个逆波兰表达式吧
有一个Interpretor接口,里面有一个Interpret()方法
然后定义AddInterpretor,MultipleInterpretor等
然后用一个ExpressionParser,结合里面的栈结构,算出最终的结果
这个解释器模式主要是用于一些文本的解析吧,有点涉及编译
或者自定义配置文件啥的也可以用这个模式来读
(24)责任链模式
定义Hanlder接口,里面有handle方法
定义多个类实现Handler接口,并且每个Handler对象都持有一个Handler对象作为下一个处理者
每个处理者判断请求自己是否需要处理、是否需要交给下一个节点处理
调用者可以通过setNextHandler等类似方法,用链式编程设置处理顺序
Servlet中的Filter和spring中的Interceptor都有点责任链的意思吧
然后Spring也可以通过@Order来灵活进行责任链中处理顺序的编排
(25)备忘录模式
一个普通对象,比如是
里面有属性int hp,int level,String name
里面再提供一个方法,叫createMemento,即创建一个备忘、或者也可以叫存档
pulic class GameRole{
private String name;
private int hp;
private int level;
//无参构造、全参构造
//getter & setter
public Memento creteMemento(){
return new Memento(this);
}
public restoreFromMemento(Memento memento){
this.name = memento.getName();
this.hp = memento.getHp();
this.level = memento.getLevel();
}
//toString
}
public class Memento{
private String name;
private int hp;
private int level;
public Memento(GameRole role){
this.name = role.getName();
this.hp = role.getHp();
this.level = role.getLevel();
}
//getter
}
public class MementoDemo{
public void main(String[] args){
GameRole role = new GameRole("zhangsan",10,20);
System.out.println(role);
Memento memento = role.createMemento();
role.setLevel(30);
System.out.println(role);//level变化了
role.restoreFromMemento(memento);
System.out.println(role);//level用备忘录恢复了
}
}
如果Memento中用栈结构来存GameRole的深拷贝对象,还能多次恢复之前的状态。
说白了这个就是把之前的历史状态存一份,需要时再恢复嘛
说备忘录模式允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态,那是站在使用者,即这里的MementoDemo来说的
使用者只需要调用 createMemento() 和 restoreFromMemento() 方法即可,至于具体怎么实现的拷贝和恢复,他不用关心
这里的问题就是,如果要保存历史的对象很大,而且用深拷贝的话可能会占用很大的空间
如有不同意见欢迎评论区讨论。里面的东西我都敲了代码,需要可以私我