常用设计模式总结

刚刚开始写博客总结,有不对的地方欢迎大家指正和讨论,废话不多说,看就完了。
多态:父类的引用指向子类的对象。

设计模式六大原则

  1. 单一职责 :降低类的复杂度,一个类只负责一项职责,降低类和类的耦合,提高可读性,增加可维护性和可拓展性,降低可变性的风险;
  2. 接口隔离原则 :类和类之间应该建立在最小接口的上,不要在一个接口里面放很多的方法,这样会显得这个类很臃肿不堪。接口应该尽量细化,一个接口对应一个功能模块,同时接口里面的方法应该尽可能的少,使接口更加轻便灵活。和单一职责的区别,单一职责主要体现在业务的实现和细节上,接口隔离主要体现在程序的构建上;
  3. 迪米特法则 : 降低系统的耦合度,一个对象的改变不会给太多其他对象带来影响;
  4. 依赖倒置原则 : 面向接口编程。高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象;
  5. 里氏替换原则 : 这里举个例子方便大家理解,如果我说我喜欢喝可乐,那么我可以再具体说我喜欢喝可口可乐,因为可口可乐是可乐的一种,如果我说我喜欢喝可口可乐,那么就不能说我喜欢喝所有可乐,因为还有百事可乐等等(这里不是引战,我都爱喝的)。说到程序上,就是运用继承的思想,任意使用父类的地方,我可以用子类去替换,不会产生任何错误。子类可以扩展父类的功能,但不改变父类原有的功能,即使后续增加了新的需求,直接增加子类,并且与原有子类互不冲突;
  6. 开闭原则 : 对扩展开放,对修改关闭;

总结:高内聚,低耦合。抽象构建框架、实现扩展细节。

常用设计模式

单例模式

概念:单例对象的类只能允许一个实例存在。

适⽤场景: 单例模式只允许创建⼀个对象,因此节省内存,加快对象访问速度,所以对象需要被公⽤的场合适合使⽤,如多个模块使⽤同⼀个数据源连接对象等等。如:

  1. 需要频繁实例化然后销毁的对象。

  2. 创建对象时耗时过多或者耗资源过多,但⼜经常⽤到的对象。

  3. 有状态的⼯具类对象(有状态对象就是指有实例变量的对象,可以保存数据,线程不安全;无状态对象就是指不能保存数据,是不变的,所以线程安全)。

  4. 频繁访问数据库或文件的对象。

常用三种实现方式

饿汉式

public class HungrySingleton {
    private static HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getHungrySigleton() {
        return hungrySingleton;
    }
}

优点:实现简单,类加载的时候完成实例化,线程安全。
缺点:没有达到lazy loading的效果,如果一直没有被调用,浪费内存。

懒汉式

public class LazySingleton {
    private static LazySingleton lazySingleton;

    private LazySingleton() {
    }

    private static LazySingleton getLazySingleton() {
        if (null == lazySingleton) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

优点:实现延迟加载,调用的时候才实例化。
缺点:多线程情况下,可能产生多个实例,线程不安全。

双重检锁机制

public class Singleton {
    private volatile static Singleton singleton;

    private Singleton() {
    }

    private static Singleton getSingleton() {
        if (null == singleton) {
            synchronized (Singleton.class) {
                if (null == singleton) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

优点:实现延迟加载,双重判定不但保证了单例还提高了程序的运行效率。

这里再简单介绍一下为什么用到了volatile。
volatile的两个作用,一个是可见性,另外一个就是防止指令重排序。在双重检锁机制中主要是防止指令重排序。
这就不得不先说在new一个对象时的过程:
1.为对象分配内存空间;
2.将这个对象初始化;
3.将初始化的对象指向分配好的内存。

而指令重排序就是指处理器和编译器为了优化程序性能会将2和3顺序颠倒。这样就会导致在代码中第二次判断是否为null的时候,并不是null,但是访问到的会是一个还未完成初始化的实例。这个时候使用volatile关键字,禁止了指令重排序,所有的写操作都会排在读操作之前。

指令重排序的原则:
1.在单线程环境下不能改变程序运行的结果
2.存在数据依赖关系的不允许重排序

那么单例模式可以被破坏吗?

可以。
1.通过反射直接调用私有构造器;
2.设置accessible为true,跳过权限检查。

避免这种情况只要在代码中进行判断即可。

//防止反射获取多个对象的漏洞 
private Singleton() {  
 if (null != SingletonClassInstance.instance){
  throw new RuntimeException(); 
 }
}

工厂模式

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

简单工厂模式

将业务逻辑放在工厂类中路由,在具体类中实现。这样做可以使代码比不使用工厂模式时可读性更好一点,但是不好的地方在于如果产品变多,那么在工厂类中会有很多switch case或者if else。这样就引申出工厂方法模式。

工厂方法模式

工厂方法模式就是在简单工厂模式的基础上,将工厂也抽象成一个接口,将生成逻辑放在不同的工厂实现类中,更好的体现了单一原则。但是如果公司业务发展的越来越多,就会有很多工厂实现类,所以我们在这个时候我们可以再进一步使用抽象工厂模式。

抽象工厂模式

抽象工厂模式就是在工厂方法模式的基础上,再抽象一层,以便区分不同的业务方向。

总结来说,工厂模式就是根据具体的业务体量去提炼代码。增强代码的可读性和可维护性。

策略模式

定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换,
优点:算法可以根据需要自由切换,避免了使用多重算法语句(if…else)
缺点:如果如果备选的策略很多,那么策略类数量会增多,并且只适⽤于客户端知道算法或⾏为的情况

策略模式和工厂模式的区别就是,工场模式属于创建模式,策略模式属于行为模式。工厂模式和策略模式可以结合使用。

命令模式

将一个请求封装成一个对象,从而可以使我们可用不同的请求对客户参数化;对请求排队或者记录请求日志,以及可撤销的操作。数据库事务操作的底层实现就用到了命令模式。

模式结构
Client(客户端)
Invoker(调用者/请求者)请求的发送者,它通过命令对象来执行请求
Command(抽象命令类):抽象出命令对象,可以根据不同的命令类型。写出不同的实现类
ConcreteCommand(具体命令类):实现了抽象命令对象的具体实现
Receiver(接收者):真正执行命令的对象

优点
1.降低耦合度;
2.新的命令可以很容易的加入到系统中;
3.可以比较容易的设计一个命令队列和宏命令(组合命令);
4.可以方便的实现队请求的undo和redo;

缺点
使⽤命令模式可能会导致某些系统有过多的具体命令类。因为针对每㇐个命令都需要设计㇐个具体命令类,因此某些系统可能需要⼤量具体命令类,这将影响命令模式的使⽤。

代理模式

静态代理

分为抽象角色、真实角色、代理角色(引用真实角色,在调用真实角色前后增强代码)。

优点:可以做到在不修改目标对象功能的基础上,对目标对象进行扩展。

缺点
每一个代理类都要实现委托类(即真事角色)的接口,如果委托类增加了一个接口,那么代理类也必须跟着修改。其次,每个代理类接口对象都对应一个委托对象,如果委托对象非常多,那么静态代理类也会变得臃肿不堪。

jdk动态代理

jdk动态代理解决了静态代理代码臃肿的问题,基于反射,借助Java自带的java.lang.reflect.Proxy,通过固定的规则实现。
核心就是创建⼀个动态代理类,实现InvocationHandler接⼝,并重写该invoke⽅法。
关键是Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)⽅法,该⽅法会根据指定的参数动态创建代理对象。三个参数的意义如下:
1.loader,指定代理对象的类加载器;
2. interfaces,代理对象需要实现的接⼝,可以同时指定多个接⼝;
3. handler,⽅法调⽤的实际处理者,代理对象的⽅法调⽤都会转发到这⾥。

cglib动态代理

CGLIB:CGLIB是⼀个强⼤的⾼性能的代码⽣成包,底层是通过使⽤⼀个⼩⽽快的字节码处理框架ASM,它可以在运⾏期扩展Java类与实现Java接⼝。

原理:通过CGLIB的Enhancer(CGLIB的字节码增强器)来指定要代理的⽬标对象、实际处理代理逻辑的对象,最终通过调⽤create()⽅法 得到代理对象,对这个对象所有⾮final⽅法的调用都会转发给MethodInterceptor.intercept()方法。通过调用MethodProxy.invokeSuper()方法,我们将调用转发给原始对象。
Object中的getClass()、wait()等方法,CGLIB无法代理,因为这些是final方法。

创建代理对象的⼏个步骤:
1.⽣成代理类的⼆进制字节码⽂件;
2.加载⼆进制字节码,⽣成Class对象( 例如使⽤Class.forName()⽅法 ) ;
3.通过反射机制获得实例构造,并创建代理类对象。

总结
1.jdk动态代理:利⽤拦截器(拦截器必须实现InvocationHanlder)加上反射机制⽣成⼀个实现代理接⼝的 匿名类,在调⽤具体⽅法前调⽤InvokeHandler来处理。只能对实现了接⼝的类⽣成代理。
2. cglib:利⽤ASM开源包,对代理对象类的class⽂件加载进来,通过修改其字节码⽣成⼦类来处理。主 要是对指定的类⽣成⼀个⼦类,覆盖其中的⽅法,并覆盖其中⽅法实现增强,但是因为采⽤的是继承, 对于final类或⽅法,是⽆法继承的。
3. 选择:
a. 如果⽬标对象实现了接⼝,默认情况下会采⽤JDK的动态代理实现AOP。
b. 如果⽬标对象实现了接⼝,可以强制使⽤CGLIB实现AOP。
c. 如果⽬标对象没有实现了接⼝,必须采⽤CGLIB库,Spring会⾃动在JDK动态代理和CGLIB之间转 换。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值