常用的几种设计模式
在平时的工作中多多少少都会用到点设计模式。很多书籍中都阐述了设计模式中的几大原则:开闭、李氏代换、依赖、接口隔离、复用、迪米特等,很多网上博客中都列举了23中设计模式,设计模式同时分为几大类:创建型、结构型和行为型,但本文不一一列举,本篇只介绍平时我用到比较多的几种模式,以提高系统性能为目的,如单例、装饰、代理、享元等模式。
单例
单例平时应该用的比较多,一般确保系统中该类只产生一个实例,带来的好处也是很明显的。
- 频繁创建的对象,尤其是重量级对象,节省创建对象所花费的时间
- 另外一点就是减少gc,因为创建的对象少了,gc当然会相应的减少
在网上单例有很多种实现方式,一般就是饿汉、懒汉、内部类(推荐最优雅)以及双重检查(不推荐,而且网上有的demo都有存在错误的写法)。
public class Singleton{
private Singleton(){//确保不会在系统中其它地方被实例化
System.out.println("Create a Singleton");
}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
上面这种饿汉式的创建单例实现简单,有不好的地方,没有多instance做延迟加载,如果这个类初始化比较慢,那么在jvm在加载该类时,单例就会被创建,因为它是static。但是有时候我们可能不会用到该单例,但是它还是被初始化了,所以希望在我们调用的时候才去初始化它,延迟加载懒汉。
public class SingletonLazy{
private SingletonLazy(){//确保不会在系统中其它地方被实例化
System.out.println("Create a Singleton");
}
private static SingletonLazy instance = null;
public static synchronized SingletonLazy getInstance(){
if(instance == null){
instance = new SingletonLazy();
}
return instance;
}
}
相比饥饿的单例,在调用获取单例的时候加了同步,因为在多线程的情况下,如果不加同步,会导致单例被初始化多个或者单例对象初始化不完全。懒加载再多线程的情况下,获取单例比饥饿下获取单例所消耗的时间更长,大家可以简单测试下,主要是因为同步造成的。 相比前两种,饥饿模式不能延迟加载,懒汉模式引入了同步,有没有一种折中的方式,内部类就可以满足这种。(文字巴拉一大堆,还是看code吧)
public class SingletonInner{
private SingletonInner(){//确保不会在系统中其它地方被实例化
System.out.println("Create a SingletonInner");
}
private static class SingletonHolder{
private static SingletonInner instance = new SingletonInner();
}
public static SingletonInner getInstance(){
return SingletonHolder.instance;
}
}
内部类的的实现最优雅,首先该类在jvm加载的时候不会被实例化,只有在调用的时候,jvm才会去加载该内部类SingletonHolder进而再去初始化实例instance,实例的创建发生在类加载时完成。推荐这种方式实现。
注意:极端情况,如果通过反射机制,强行调用私用构造函数,会产生多个实例。另外序列化和反序列化也会破坏单例。如果存在这些情况,那得注意。
代理
代理字面意思就是代替的意思,比如我们生活中的律师,他代理当事人授权处理问题,但是核心问题还得请示当事人。 代理我们一般分为静态代理和动态代理,代理的角色一般有:接口,接口实现类或者叫真实主题,代理类。 静态代理缺点很明显,如果需要代理的对象特别多,那代码也要写一大堆,而且如果接口变化了,需要被代理的类和代理类都需要一一修改,实在是噩梦。 动态代理却不一一样,动态代理是在运行时(字节码动态加载技术),代理类在运行时生成且加载类。与静态代理相比可以有效的减少代码行数,提升系统灵活性质。
- 静态代理 先来个简单的例子,我们日常生活中吃饭的方式,比如用手或者筷子
public interface Eat{
void tool();
}
真实的类
public class EatWithHands implements Eat{
@override
public void tool(){
System.out.println(" eat food with hands..");
}
}
代理实现类
public class EatProxy implements Eat{
private Eat eat;//代理对象
public EatProxy (){
this.eat = new EatWithHands();
}
@override
public void tool(){
start();
eat.tool();
end();
}
private void startEat(){
System.out.println("cook ..");
}
private void endQuery(){
System.out.println("sweep ..");
}
}
测试
//test
public static void main(String [] args){
Eat eat = new EatProxy();
eat.tool();
}
现在如果我想吃饭的时候使用筷子,但我又想复用吃饭前烹饪食物和收拾逻辑不变,那我该如何处理,那我只能在建一个使用筷子的类,然后再写个使用筷子代理类来实现该逻辑。是的,静态代理这么弄,如果我再想加个用西式的刀叉来吃饭,那还得跟使用筷子的代码类似,代码重复读较高。
- jdk动态代理
public class EatJdkProxy<T> implements InvocationHandler{
private T target;//被代理的对象
EatJdkProxy(T t){
this.target = t;
}
@override
public object invoke(Object proxy, Method method, Object[] args) throws Throwable{
start();
method.invoke(target,args);
end();
}
public <T> T getProxy(){
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
private void start(){
System.out.println("cook ..");
}
private void end(){
System.out.println("sweep ..");
}
}
测试
public static void main(String [] args){
//用手抓
Eat e = new EatJdkProxy<Eat>(new EatWithHands()).getProxy();
e.tool();
//用筷子
Eat e = new EatJdkProxy<Eat>(new EatWithChops()).getProxy();
e.tool();
}
- cglib代理 jdk自带的生成代理类固然好,但是缺点也明显,只能针对接口生成代理类,如果EatWithHands没有实现Eat接口那么不能生成EatWithHands的代理类。
public class EatCglibProxy<T> implements MethodInterceptor{
private T target;//被代理的对象
EatCglibProxy(T t){
this.target = t;
}
@override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable{
start();
method.invoke(target,args);
end();
}
public <T> T getProxy(){
Enhancer enhancer = new Enhancer();
enhancer.setCallback(this);
enhancer.setInterfaces(t.getClass().getInterfaces());
return (T) enhancer.create();
}
private void start(){
System.out.println("cook ..");
}
private void end(){
System.out.println("sweep ..");
}
}
测试
public static void main(String [] args){
Eat e = new EatCglibProxy<Eat>(new EatWithChops()).getProxy();
e.tool();
}