23 种设计模式的详细学习参考: https://blog.youkuaiyun.com/A1342772/article/details/91349142
I: 23 种设计模式分为:(口诀是网上找的,想出来的真的是人才)
1)创建:5 工抽建单元 (工从抽着烟在改建单元门)即: 工厂,抽象工厂,建造者,单例模式,原型模式
2)结构:7 (桥代理组装适配器,享元再拿回云装饰外观)即:桥接,代理,组装,适配器,享元,装饰,外观
3)行为:11 (访问者提前写好了策略备忘录,到了仔细地观察模板的迭代状态,然后命令中介解释产权的责任链。)
即: 访问者,策略,备忘录,观察者,模板模式,迭代器,状态模式,命令模式, 中介模式,解释器,责任链模式。
II: spring中常用的委派模式不是23中经典设计模式中内容,我的理解是handlerMapping中,用uri作为key, 用handler作为值,一定程度上有点像C中的函数指针的方式,也有点类似策略模式,不过策略模式是几种完全可以相互替代的对象。
参考:https://blog.youkuaiyun.com/fu123123fu/article/details/80159551
III: 一句话总结设计模式:
工厂:
简单工厂:转入字符串,返回不同的对象。
工厂方法:一个工厂接口,有多个具体实现,从而生成不同的对象。
抽象工厂:
抽象工厂有多个实现,每个工厂里还生成不同的产品簇。
建造者:
一个聚合对象的创建,分成多个部分分别创建,这些子部件的创建都放到一个builder里,交给builder来按一定顺序来调用 。
单例模式:
一个类的构造函数私有的情况下,内部只保存一个实例,用一个方法来获取 。
原型模式:
由一个已经存在的对象来生成一个新对象,用到clone, 其实也可以用到serializable. readObject, writeObject来实现。
桥接:
比如手机中要调用软件,可以分别定义成单独的接口,手机的实现类中包含软件的实现实例,然后手机中调用软件的run, 其实是调用的软件的实现类中的run, 画出来就好像一座桥。所以叫桥接。
代理:
有静态代码,动态代理,后者又包括了JDK 和CGLIB, 分别要实现invokeHandler(), 和 inceptor()
代理主要是是封装原有的对象,对功能进行一些限制。动态代理则是向用户屏蔽一些细节操作,比如 RPC。
组装:
经典的例子树枝和叶子,都是同一接口component的实现,都有add, opt, getchild 方法。树枝中会维护叶子的list, 在opt 树枝时会getchild 得到所有的小树枝或者是叶子,如果是叶子那调用opt 又会递归地调用以下的child, 直到child为叶子 时,执行叶子的操作。 主要是整体和部分的关系要用到这个。
适配器:让两个不能在一块用的产品可以在一块用,经典设计是充电头把220V 转为5V。adaptor同时实现A,继承B。 然后在A中调用相关接口时,在里边适配后再调用B的方法,实现转换后得到期望的结果。
享元:经典设计,围棋中只有黑和白,只用2个对象的共享就可以完成事个游戏。
装饰:这个是包装一个对象,然后再增加一些功能。
外观 : 对象里有多个子对象,要一个动作要操作多个对象的多个方法,可以封装成一个方法提供给外部,也就是门面模式。
访问者:一个对象内,维护了很多Element, 并有一个accept(), 另外外部有一个vistor类,传给accept() 作为参数后,visitor可以访问所有的element.
策略: 比如同时有微信和支付宝2种策略,可以完全替代,使用时可以任意提供给调用者,然后调用pay()方法。
备忘录: 有memo 对象,有memokeeper 对象,维护所有的memo, 有调用者,负责调用 new memo() 并存到memoKeeper中。要用时再从memokeeper中取出来完成备忘录恢复 。
观察者: 聚集对象中维护所有的观察者,当有事发生成,遍历观察者list, 挨个调用观察者的update()
模板模式: 父类中定好一套流程,子类中可以自定义的实现对应的步骤。比如servLet中就开放了以一些接口让用户自己实现。
迭代器:聚集对象中要遍历多个子对象,那干脆再弄一个iterator() 器,把2个子对象的iterator包含进去,一次遍历完,用户不感知遍历了2次。
状态模式:要有context 对象和状态 对象,context 对象在生成时,里边要包含所有的状态对象,然后对种状态对象里要实现move, next() 下一状态。这样context只要调用move(), context中的状态 就会自动地切换。
命令模式:命令和接收都分开定义,比如打开命令和灯对象,命令里可以有turnon, turnoff方法,且包含一个灯对象,在命令调用方法时,实际调用的灯自己的On(), off()方法。
中介模式: 中介对象中维护一条同事的list, 同提供注册和relay()方法。 注册的同时,会把同事和中介关联起来,同时同事会加到list中。 同事对象中有send, receive方法,同事要send消息时,会告知介,我是谁,我要发给谁,我要发会什么。中介则从list中把这些人找出来,分别调用他们的receive()方法。
解释器:专门 用来解析语法的,现在很少用。
责任链模式: 经典例子,就是领导一级一级的审批过程,会有多个Handler, 级成一条链,用户并不知道最终会是谁来处理。每个handler 有一个next()方法指向下一个handler().
以下是这前的代码比较散,也没有写完。
一、工厂模式
1. 简单工厂模式
即通过固定的方法来获取想要的对象实例(参数不同,产品不同)。
public final static getInstance(string arg){
xxxx 逻辑
if(arg == xx){
return instance1;
}..
{
return instance N;
}
}
2.工厂方法模式
即父类只定义工厂方法接口,只有继承类实现了接口才能使用工厂方法,把get 实例的方法推迟到子类中去实现。
要多个不同的子类才能创建不同的工厂,得到不同的对象实例。
3.抽象工厂模式:(解决多个产品,每个产品又有不同级别的情况)
这个就有点复杂了。用代码来描述吧。
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。可以理解成是多个工厂方法的组合。
成员角色
抽象工厂(AbstractFactory):客户端直接引用,由未实现的工厂方法组成,子类必须实现其工厂方法创建产品家族。
具体工厂(ConcreteFactory):实现抽象工厂接口,负责实现工厂方法,一个具体工厂可以创建一组产品(所有产品,品牌不同)。
抽象产品(AbstractProduct):产品家族的父类,由此可以衍生很多子产品。
具体产品(Product):衍生自抽象产品,由工厂方法直接创建。
抽象工厂模式适合 生产不同的产品族, 但是有不同的品牌(也适用于不同的产品,下边有不同的子产品)
比如某一机构生产两种产品, 笔记和视频 两种产品。
笔记和视频都有各自的品牌: java, python.
那抽象产品就是INote, IVideo.
抽象工厂就是生成INote, IVideo;
子工厂1 负责生厂JAVA, 子工厂2负责生产PYTHON。(一个工厂生成一个品牌, 每个工厂都生成所有的产品)
// 抽象产品
public interface INote{
public string getname();
}
public interface IVdeo{
public string getname();
}
// 实际产品,分成python, java
public class NoteJava implements INote{
public string getname(){
return "nott java";
}
}
public class NotePython implements INote{
public string getname(){
return "nott python";
}
}
public class VideoJava implements INote{
public string getname(){
return "VideoJava";
}
}
public class VideoPython implements INote{
public string getname(){
return "VideoPython";
}
}
// 抽象工厂, 生产所有的产品
public abstract class ProductFactory{
public abstract INote createNote();
public abstract IVdeo createVideo();
}
// 实际工厂一,产品牌java( 子工厂按子产品分)
public class JavaFactory extends ProductFactory{
public INote createNote(){
return new NoteJava();
}
public INote createVideo(){
return new VideoJava();
}
}
// 实际工厂二,产品牌Python (产子产品python)
public class PythonFactory extends ProductFactory{
public INote createNote(){
return new NotePython();
}
public INote createVideo(){
return new VideoPython();
}
}
// 测试代码: 只有调用 不同的工厂就能得到不同的产品
public static void main(string[] args){
ProductFactory pf = new JavaFactory();
Inote nt = pf.createNote();
System.out.println(nt.getname()); // java note
ProductFactory pf2 = new PythonFactory();
Inote nt2 = pf.createNote();
System.out.println(nt2.getname()); // python note
}
3.2 抽象工厂的第二个实例
抽象工厂模式适合 生产不同的产品族, 但是有不同的品牌(也适用于不同的产品,下边有不同的子产品)
比如某一机构生产两种产品, 笔记和视频 两种产品。
笔记和视频都有各自的品牌: java, python.
那抽象产品就是INote, IVideo.
抽象工厂就是生成INote, IVideo;
子工厂1 负责生厂JAVA, 子工厂2负责生产PYTHON。(一个工厂生成一个品牌, 每个工厂都生成所有的产品)
// 抽象产品
public interface INote{
public string getname();
}
public interface IVdeo{
public string getname();
}
// 实际产品,分成python, java
public class NoteJava implements INote{
public string getname(){
return "nott java";
}
}
public class NotePython implements INote{
public string getname(){
return "nott python";
}
}
public class VideoJava implements INote{
public string getname(){
return "VideoJava";
}
}
public class VideoPython implements INote{
public string getname(){
return "VideoPython";
}
}
// 抽象工厂, 生产所有的产品
public abstract class ProductFactory{
public abstract INote createNote();
public abstract IVdeo createVideo();
}
// 实际工厂一,产品牌java( 子工厂按子产品分)
public class JavaFactory extends ProductFactory{
public INote createNote(){
return new NoteJava();
}
public INote createVideo(){
return new VideoJava();
}
}
// 实际工厂二,产品牌Python (产子产品python)
public class PythonFactory extends ProductFactory{
public INote createNote(){
return new NotePython();
}
public INote createVideo(){
return new VideoPython();
}
}
// 测试代码: 只有调用 不同的工厂就能得到不同的产品
public static void main(string[] args){
ProductFactory pf = new JavaFactory();
Inote nt = pf.createNote();
System.out.println(nt.getname()); // java note
ProductFactory pf2 = new PythonFactory();
Inote nt2 = pf.createNote();
System.out.println(nt2.getname()); // python note
}
二 、单例模式:
构造函数私有化。 private xx(){}
创建private static final instance = new xx();
创建public satic final getInsance(); 方法
1. 饿汉模式
无论怎么样都有instance 实例。
private static final instance = new xx(); 有点浪费空间
2.懒汉模式:
只有在调用getInstance时才生成对象
1).
private static final instance = null;
public satic final getInsance(){
if(null != instance) // mark1
{
instance = new xx();
}
return instance;
}
问题: 这样会有线程安全问题, 不同线程同时进入mark1时,会返回不同的instance.
2) 解决1)中的问题, 加在方法上加一个synchronized 字段
private static final instance = null;
public satic synchronized final getInsance(){
if(null != instance) // mark1
{
instance = new xx();
}
return instance;
}
问题: 这样效率会降低,因为方法是static的,相当于把class 整个锁住了。会大大降低效率。
3) 解决2)中的问题: 改成double check 锁。把synchronized 字段放到方法内。
private static final instance = null;
public satic final getInsance(){
if(null != instance) // mark1
{
(synchronized xx.class){ //mark2
if(null != instance) { //mark3
instance = new xx();
}
}
}
return instance;
mark1, mark2, mark3保证了一定只会生成一个instance.
问题: mark1, mark2, mark3 这里,JVM 会优化,指令进行重排,也有线程不安全的情况 。用volatile 关键字可以解决。
4) 静态内部类可以实现线程安全(而且延迟了对象实例化的时机),只有在加载内部类时才返回实例:
缺点:要用外部类来调用,创建内部类的实例,传不了参数。
public clase A{
private A(){}
private static class InnerClass{
private static A instance = new A();
}
public satic A getInsance(){
return InnerClass.instance;
}
}
main(){
A a = A.getInsance();
}
问题: 正常情况下不会生成多个实例,但是如果有人用反射访问私有的构造函数, 还是可以new 出新的instance.
解决: 在构造函数中判断instance 不是Null的话,抛异常,这样就用不了构造函数了。
问题: 将对象序列化后,然后写到文件再反序列化出来 ,这样也可以有多个实例。
解决:
因为反序列化都会调用 readObject(), 这个过程会先判断有没有readResolve() 函数,JDK 设计时考虑到了这个问题。
我们可以在 readResolve()中做特殊处理。
public void readResolve(){
return instance; // 这样实际不是用的文件中读出来的。
}
3. 注册模式
1) 枚举实现 <<effective java>> 中推荐的用法, JDK 的实现使得枚举不能被反射反序列化调用。
public Enum myEnumSingleton{
INSTANCE;
private Object obj;
public void getObj(){return obj;}
public void setObj(Object o){ this.obj = o;}
public static myEnumSingleton getInstance(){
return INSTANCE;
}
}
main(){
myEnumSingleton ins = myEnumSingleton.getInstance();
ins.setObject(new Object());
myEnumSingleton ins2 = myEnumSingleton.getInstance();
ins.setObject(new Object());
System.out.println(ins1.getObject() == ins2.getObject()); // 恒为true
}
2).容器式注册单例
单例其实就是要有一种结构来保证,得到的实例是唯一的,这里就是用Map来保存类的对象,用key 的唯一性来保证实例是唯一的。容器式单例 模式适用保存很多类的单例的情况。以下是容器中保存一种类的单例 的情况 :
public class ContainerSigleton{
private static Map<String, Object > ioc = new Hashmap<string, Object>();
private ContainerSigleton(){}
public static Object getBean(String className){
synchronized(ioc){ // 这里如果不加同步,会有线程安全问题。
if(!ioc.containkey(className)){
Object obj = null;
try{
obj = class.forName(className).newInstance();
}catch(Exeption ex)(
ex.printStackTrace();
)
return obj;
}else {
return io.get(className);
}
}
}
}
4.ThreadLocal 单例
这是一种伪线程安全模式,当多个线程同时生成单例时, 在同一个线程中不管怎样都生成的同一个单例, 但是在另一个线程里会生成另一个单例。所以只是在当前线程中保证了线程安全的。
应用场景: 多数据源动态切换时,会用到ThreadLocal, ORM 框架中有介绍。下边是threadLocal的生成代码:
public class ThreadLocalSingleton{
private ThreadLocalSingleton(){}
private static final ThreadLocal <ThreadLocalSingleton> threadLocalInstance = new ThreadLocal <ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue(){
return new ThreadLocalSingleton();
}
}
public static ThreadLocalSingleton getInstance(){
return threadLocalInstance.get();
}
}
三、原型模式
又叫Prototype 模式,spring中配置的时候就会有singleton模式和prototype模式。
Prototype模式生成实例的时候不用调用类的构造函数,而是直接从字节码中copy。
适用场景:当构造一个对象调用的构造函数很复杂,比如要调用很多次get, set 方法。 或者在循环中需要创建很多的实例时可以用Protoytpe来创建对象。
Prototype的实质就是实现类的Clone方法, 通常情况下默认的clone()方法是浅铐贝,所以我们实现了类的deepclone() 方法后,就可以直接调用deepclone()来完成实例的copy, 然后再对实例中的个别字段进行改造(比如日期)。
// 利用序列和反序列化, 生成一个新的实例
public Object deepclone(){
try{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ObjectInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Object obj = ois.readObject();
// 这里可以改实例的一些字段
...
return obj; // 这样生成的实例是深copy,字段是对象的引用时,会把对象也copy进去.
} catch(Exeption ex){
ex.printStackTrace();
}
}
四、代理模式
1.简单代理
其实就是在访问目标类的中间再创建一个类,第二个类对第一个类的功能进行封装,对外而言,只能访问第二个类,但是第二个类中只开放了第一个类的部分功能。
在功能扩展时,要修改两个类。
2.动态代理,分JDK, CGLIB
动态代码比静态代理要灵活很多,实现了动态代理的类在执行被代理类的方法时,会自动加上我们自定义的行为。而不用修改原始类。比如原始方法为 f(); 代理后变成 before(); f(); after(); 这样就调用 f(), 就会自动加上before(), after()的行为。下边分别对两种动态代理进行描述:
2.1) JDK动态代理:
被代理类要求: 必须实现了某个接口,而且动态代理只能代理接口中声明的方法。
应用:创建代理类,实现InvocationHandler 接口,实现invoke方法。
被代理类:
public Interface Person{
public ovid f();
}
public class Customer implements Person{
public ovid f(){
dosomeThing();
}
}
代理类:
public class MyProxyClass implements InvocationHandler{
private Object target;
public Object getProxyInstance(Object target){
this.target = target;
class<?> clazz = target.getClass();
return Proxy.newProxyInstance(class.getClassLoader(), clazz.getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args){
before();
method.invoke(this.target, args);
after();
}
public
}
测试代码:
MyProxyClass proxyObj = MyProxyClass.getProxyInstance(new Customer());
proxyObj .f();
底层原理:
JDK 底层的关键类是Proxy类,这个类在执行过程中动态的生成了一个$proxy0 的类。 这个类中把Person 接口中的方法(反射取得)重新实现 了一次,然后存成.java 文件。 然后动态的调用 javaCompiler build成class 文件,再用classLoader 重新加入到JVM中。利用反射获取到这个新类的构造函数,生成一个代理后的对象。 这样调用代理后的对象中的方法,就会调用我们在InvocationHandler 中实现 的invoke方法,就实现了执行我们增强的那些行为。比如打日志,打时间,清理工作等。
2.2) CGlib动态代理:
被代理类要求: 必须是可以被继承的类,动态代理可以代理目标类声明的方法。
应用:实现 MethodInterceptor 接口,实现intercept()方法
public class MyProxyClass implements InvocationHandler{
private Object target;
public Object getProxyInstance(Object target){
this.target = target;
class<?> clazz = target.getClass();
return Proxy.newProxyInstance(class.getClassLoader(), clazz.getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args){
before();
method.invoke(this.target, args);
after();
}
public
}
public class MyCglibProxyClass implements MethodInterceptor{
public Object getProxyInstance(class<?> clazz){
Enhancer enhancer = new Enhancer();
enhancer.setSuperClass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy){
before();
Object r = proxy.invokeSuper(obj, args);
after();
return r;
}
}
测试代码:
Custome cs =(Custome) new MyCglibProxyClass().getProxyInstance(Custome.clas);
cs.f();
底层实现:
执行代码时,动态生成了三个类: 代理类的fastClass, 被代理类的FastClass, 代理类
代理类继承了原始类,实现 了Factory. 获得了原始类中的所有方法,且都有MethodProxy 与之对应。
代理类和被代理类的FastClass 中的方法都有index 一一对应,代理类在执行方法时用index 直接从Fastclass中找到对应的方法。省去了JDK 实现 中的反射执行,所以效率比JDK 动态代理要快。 FastClass 之所以快, 是因为里边有汇编代码。
五、委派模式
六、策略模式
七、模板模式
八、适配器模式
九、装饰器模式
十、观察者模式
总结: