Java 设计模式总结

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 之所以快, 是因为里边有汇编代码。 

 

五、委派模式

六、策略模式

七、模板模式

八、适配器模式

九、装饰器模式

十、观察者模式

总结:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值