从设计模式的角度看Java程序优化

一、前言

  Java程序优化有很多种渠道,比如jvm优化、数据库优化等等,但都是亡羊补牢的措施,如果能在设计程序架构时利用设计模式就把程序的短板解决,就能使程序更加健壮切容易维护迭代

二、常用的设计模式

1、单例模式

  单例模式可以确保一个类只产生一个实例,对于系统中频繁使用的对象建议使用单例模式设计,可以节省创建对象耗费的时间以及减少GC清理的压力,代码如下

public class A
{
    // 实例化一个私有的静态对象
    private static A a = new A();
    // 构造方法私有,防止其他代码对其实例化
    private A()
    {
    }
    // 通过getInstance获取使用该对象
    public static A getInstance()
    {
        return a;
    }
}

因为对象实例是静态对象,所以JVM在加载这个类时就会把对象创建出来,这样无法做到延时加载(就是用到的时候再加载),所以还有一些写法会将对象设置为null,当有人调用getInstance方法时,先判断对象是否为空,为空再实例化对象,达到延时加载的目的,代码如下

public class A
{
    // 声明对象时赋值为null,加载类时,不会生成对象
    private static A a = null;
    private A()
    {
    }
    // 加入synchronized关键字,将这个办法锁起来,当多线程调用时竞争锁,达到线程安全
    public static synchronized A getInstance()
    {
        if (a == null)
        {
            a = new A();
        }
        return a;
    }
}

以上代码虽然解决了线程安全的问题,但是加入同步锁,会使代码效率降低为代价,所以还需要再改进一下,完美的做法如下

public class A
{
    private A()
    {
    }
    // 声明一个静态内部类
    private static class AHolder
    {
        private static A a = new A();
    }
    public static A getInstance()
    {
        // 直接返回内部类的对象
        return AHolder.a;
    }
}

以上代码声明了一个静态内部类,当调用A类时,内部类是不会被初始化的,当调用getInstance方法时,内部类才会被调用,内部类中的对象就会随之实例化,这样使用内部类巧妙的达成了延时加载的目的,同时也不用加锁,性能不会降低

2、代理模式

   使用代理模式可以使用一个代理类来间接调用其他类的功能,达到屏蔽主类的效果,在程序优化的角度上,他可以做到:当系统启动时,会做许多初始化的操作,导致系统启动时非常缓慢,可以使用代理类来处理初始化的操作,当系统启动时,加载代理类,而不用做真正的初始化操作,当这个组件真正被用到时,代理类才会真正去调用初始化的操作,这样达到了系统压力的分摊,而加快了系统的启动速度。当然代理类除了应对性能问题,还有很多其他的用途。代码如下

public interface IOperation
{
    // 业务逻辑接口
    void doIt();
}
public class TrueOperation implements IOperation
{
    @Override
    public void doIt()
    {
        // 真正的业务逻辑
        System.out.println("do it.");
    }
}
public class ProxyOperation implements IOperation
{
    // 聚合真正的实现类
    private TrueOperation trueOperation = null;

    @Override
    public void doIt()
    {
        if (trueOperation == null)
        {
            trueOperation = new TrueOperation();
        }
        // 代理执行真正的方法
        trueOperation.doIt();
    }
}

以上代码可以看出,其实代理类就是将真正的业务类聚合到自己,然后代执行,只有当业务方法真正被调用时,业务类才会被初始化,也是延时加载的思想,以上是静态代理模式的写法,存在的问题就是,当业务多了以后,同时还要维护代理类,这样就增加了开发维护的难度,所以进一步优化就是使用动态代理模式,如下代码使用JDK自带的动态代理模式

// 业务逻辑接口
public interface IOperation
{
    void operationA();
    void operationB();
}
// 业务逻辑的实现
public class OperationImpl implements IOperation
{
    @Override
    public void operationA()
    {
        System.out.println("do A...");
    }
    @Override
    public void operationB()
    {
        System.out.println("do B...");
    }
}
// 动态代理实现类
public class OperationHandler implements InvocationHandler
{
    // 需要代理的业务逻辑
    private OperationImpl operation = null;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        // 延时加载
        if (operation == null)
        {
            operation = new OperationImpl();
        }
        // 执行一些自定义的逻辑
        System.out.println("before work...");
        // 代理执行业务逻辑
        method.invoke(operation, args);
        // 执行一些自定义的逻辑
        System.out.println("after work...");
        return null;
    }
}
public class Main
{
    public static void main(String[] args)
    {
        // 生成代理对象
        IOperation operation = (IOperation)Proxy
                .newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{IOperation.class}, new OperationHandler());
        // 代理执行业务
        operation.operationA();
        operation.operationB();
    }
}

如果需要更好性能的动态代理模式,可以使用第三方库,比如CGLIB、Javassist等

3、享元模式

  当系统中存在多个相同的对象,可以优化让大家都是用同一个对象,工厂类中拿着一个对象实例,当需要使用到这个对象时,通过工厂类获取对象实例,不用每次使用都创建一遍对象,从而减少创建开销、GC压力和对象的内存占用。这听起来有点像单例模式和对象池的合体,这与对象池的区别就是:对象池中的每个对象都是等价的,而享元模式中的工厂的对象,则各有各自的用途,如下代码

// 业务逻辑接口
public interface IOperation
{
    void doIt();
}
// 业务逻辑的实现
public class OperationImplA implements IOperation
{
    private String id = null;
    public OperationImplA(String id)
    {
        this.id = id;
    }

    @Override
    public void doIt()
    {
        System.out.println("do it A...");
    }
}
// 业务逻辑的实现
public class OperationImplB implements IOperation
{
    private String id = null;
    public OperationImplB(String id)
    {
        this.id = id;
    }

    @Override
    public void doIt()
    {
        System.out.println("do it B...");
    }
}
// 享元工厂
public class OperationFactory
{
    // 相同ID公用同一个对象
    private Map<String, IOperation> operationMapA = new HashMap<>();
    private Map<String, IOperation> operationMapB = new HashMap<>();

    public IOperation getOperationA(String id)
    {
        IOperation operation = operationMapA.get(id);
        // 延时加载
        if (operation == null)
        {
            operation = new OperationImplA(id);
            operationMapA.put(id, operation);
        }
        return operation;
    }

    public IOperation getOperationB(String id)
    {
        IOperation operation = operationMapB.get(id);
        // 延时加载
        if (operation == null)
        {
            operation = new OperationImplB(id);
            operationMapB.put(id, operation);
        }
        return operation;
    }
}
public class Main
{
    public static void main(String[] args)
    {
        OperationFactory operationFactory = new OperationFactory();
        IOperation operationA = operationFactory.getOperationA("A");
        IOperation operationB = operationFactory.getOperationB("B");
        operationA.doIt();
        operationB.doIt();
    }
}

如上代码看出,每个id会共享一个对象,多次使用时可以复用对象

4、装饰者模式

   装饰者模式也是比较常见的模式,他可以将系统中的功能组件进行包装,提升功能增加的能力,对于一些功能组件,我们可以用装饰者模式为其提高性能能力,具有低耦合的特性,最典型的例子就是JDK中的文件操作,平常经常使用FileInputStream来读取文件内容,FileInputStream是基于字节流的输入,不具备缓冲区的功能,JDK就使用装饰者模式将FileInputStream包装增加了缓冲区的性能优化,即BufferedInputStream,这个例子就是增加了功能组件的性能,还有例子比如Collections.synchronizedMap(),这个对HashMap进行了包装,增加了线程安全的特性。下面看一个简易实现代码

// 业务逻辑接口
public interface IOperation
{
    void doIt();
}
// 业务逻辑的实现
public class OperationImpl implements IOperation
{
    @Override
    public void doIt()
    {
        System.out.println("do it...");
    }
}
public abstract class AbsOperation implements IOperation
{
    IOperation operation = null;
    public AbsOperation(IOperation operation)
    {
        this.operation = operation;
    }
}
public class OperationPlusA extends AbsOperation
{
    public OperationPlusA(IOperation operation)
    {
        super(operation);
    }

    @Override
    public void doIt()
    {
        System.out.println("增强功能A。。。");
        operation.doIt();
    }
}
public class OperationPlusB extends AbsOperation
{
    public OperationPlusB(IOperation operation)
    {
        super(operation);
    }

    @Override
    public void doIt()
    {
        System.out.println("增强功能B。。。");
        operation.doIt();
    }
}
public class Main
{
    public static void main(String[] args)
    {
        IOperation operation = new OperationPlusA(new OperationPlusB(new OperationImpl()));
        operation.doIt();
    }
}

以上代码可以看出,我们对功能组件进行了两次增强,每次增强都是互相独立的,不会互相影响

5、观察者模式

  观察者模式对于性能优化主要在“通知”,当程序A依赖于程序B的执行,如果程序A开线程去轮询程序B来监视程序B的状态,那么会增加系统的负担,所以使用观察者模式来优化达成“通知”这个目的,当程序B执行完毕,就会回调程序A的方法,来告诉程序A自己已经执行好了,免去了开线程去监视的负担,JDK已经帮我们提供了观察者模式的接口,下面使用简单的代码去尝试一下观察者模式

// 观察者
public class ObserverA implements Observer
{
    @Override
    public void update(Observable o, Object arg)
    {
        // 观察者的通知回调
        System.out.println("收到。。。");
    }
}
// 被观察者
public class ObservableA extends Observable
{
    @Override
    protected synchronized void setChanged()
    {
        // 改变状态方法
        super.setChanged();
    }
}
public class Main
{
    public static void main(String[] args)
    {
        // 实例化观察者和被观察者
        ObserverA observerA = new ObserverA();
        ObservableA observableA = new ObservableA();

        // 将观察者加入到被观察者的观察列表中
        observableA.addObserver(observerA);

        // 改变状态,然后通知观察者自己改变了
        observableA.setChanged();
        observableA.notifyObservers();
        observableA.setChanged();
        observableA.notifyObservers();
    }
}

观察者需要重写update()方法来定义被通知时要做的事,而被观察者需要重写setChanged方法来改变自己的状态,使用时,先将观察者注册到被观察者中,需要通知时,要调用setChanged()方法改变状态,再调用notifyObservers()方法通知观察者,如果不改变状态是无法发出通知的

posted @ 2019-02-27 18:26  未分配微服务  阅读(634)  评论(0编辑  收藏  举报