校验业务与主逻辑解耦设计探讨与实践(观察者模式篇)

又好久没写博客了,偷懒成本太低了,想记录的东西蛮多但是太细碎就懒癌发作,Gson的博客也就这么吹了。其实这类技术基础的博客网上很多,自己记录还没有人家写的好,这种情况用收藏夹就能搞定了。我觉得我记录的不应该是网上存在的第10001个拷贝,应该是自己实践过思考过的,和实际应用更加贴近的东西。
这次写的这个主题是一个在现在工作中困扰了我很久的问题。我现在的项目是基于Java GUI的,系统逻辑的耦合度很高。
大家都有过这样的经验,比如说提交表单时,会在onCommit方法里面加上判断格式,空值等等各种验证内容,差一点的代码就是

onClick(){
    if(name == null){
        System.out.println("Please input your name");
        ///...
    }else if(password == null){
        ///...
    }//...以下省略1000个if else

稍微重构一下可以是这样:

onClick(){
    doValidate();
}
doValidate(){
    if(name == null){
        System.out.println("Please input your name");
        ///...
    }else if(password == null){
        ///...
    }//...以下省略1000个if else
}

但是这时候校验的逻辑其实还是在Register业务中,并没有达到解耦的目的。
这时候有同事就说了,Register的校验只有在这里才会用到啊,本身就是一体的,为什么要拆散呢。那如果有这样一个校验规则,有一批产品,产品包括型号,等级。。。等等信息,现在要把产品装箱操作,肯定要校验这些关键属性是否相同才能包装吧,这就是一个Compare的规则,这个规则在很多地方都要用到,比如系统的进出模块,要保证相同的产品才能同时进出,这时候校验规则就必须抽象出来,与上面的业务逻辑解耦。
而且即使没有上面的废话,设计模式第一原则就是单一职责,校验和注册本身是两件事情,当然应该给不同的对象来做了。
为了实现这一步的解耦,我参考了数据校验器架构模式组,实现了可组装校验器结构。
大致代码如下:

onClick(){
    List<Status> list = DefaultValidator.doValidate(User,Register.class);
    statusHandle(list);
}
statusHandle(List<Status> list){
    for(Status status : list){
        System.out.println(status.getMessage());
    }
}

这样之后干扰代码减少很多,可以专注在业务逻辑上,而且也方便将来的扩展。但是,但是,但是,我真是一个很懒的人,每到写这样的代码:if(a == null){}else{}的时候,总觉得又浪费时间又破坏代码美感和阅读的节奏。所以上面的方案还是不满意,还是没有实现理想中的解耦。因为几乎所有的Button.onClick(),不论什么业务,都会有对应的Validate,因此我觉得在这个场景中,DefaultValidator.doValidate(User,Register.class)应该是onClick()自动执行,不需要显示调用的,我大约是有强迫症的TAT。
对于这个终极目标,脑袋中首先跳出来的面向切面AOP的解决方案,奈何没研究过AOP,而且不能为了优化一个可运行的功能而拖延其他工作的进度,所以这个想法就被搁置了,直到一个契机,看到了RxJava,它的异步执行方案似乎可以解决现在系统中的速度瓶颈(可能是我理解错了?我正准备进行一轮突击学习),关键是看到了观察者模式(设计模式什么的光看书不去用,一个星期后就忘的屁都不剩了,所以我现在的都是要使用的时候再看具体的实现步骤),把所有的业务页面视为被观察者,校验逻辑放在观察者中,每当onClick()时,通知观察者,观察者执行校验,而且被观察者是把本身作为参数给到观察者,所以可以显示Dialog提示框。观察者模式的参考博客
利用观察者模式的实现如下:
被观察者

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Vector;

/**
 * 因为不能多继承,因此不能继承util包里的Observable类,所以把Observable的功能稍微简化一下直接写在基类中,这部分代码直接copy于Java源码,不知道会不会被Oracle告哦。
 */
public class MyObservableFrame extends JFrame {

    private boolean changed = false;
    private Vector<MyObserver> obs;
    protected Object obj;

    /**
     * Construct an Observable with zero Observers.
     */
    public MyObservableFrame(String title) {
        super(title);
        obs = new Vector<MyObserver>();
    }

    /**
     * 将一个观察者添加到观察者聚集上面
     */
    public synchronized void addObserver(MyObserver o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    public void notifyObservers() throws Exception {
        notifyObservers(null);
    }

    /**
     * 如果本对象有变化(那时hasChanged 方法会返回true)
     * 调用本方法通知所有登记的观察者,即调用它们的update()方法
     * 传入this和arg作为参数
     */
    public void notifyObservers(Object arg) throws Exception {

        MyObserver[] arrLocal;

        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray(new MyObserver[obs.size()]);
            clearChanged();
        }

        for (int i = arrLocal.length - 1; i >= 0; i--)
            arrLocal[i].update(this, arg);
    }

    /**
     * 将“已变化”设置为true
     */
    protected synchronized void setChanged() {
        changed = true;
    }

    /**
     * 将“已变化”重置为false
     */
    protected synchronized void clearChanged() {
        changed = false;
    }

    /**
     * 设置要校验的信息
     * @param obj
     */
    public void setData(Object obj) {
        this.obj = obj;
    }

    /**
     * 抽象的Listener,默认通知观察者,具体实现放在doAction抽象方法中
     * 如果校验不通过,通过异常阻止doAction
     */
    abstract class MyObservableListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            setChanged();
            try {
                notifyObservers(obj);
                doAction();
            } catch (Exception e1) {
                e1.printStackTrace();
                System.out.println("Validate error, cannot register");
            }
        }

        public abstract void doAction();
    }
}

观察者

public interface MyObserver {
    void update(MyObservableFrame o, Object arg) throws Exception;
}

测试类,把几个类写在一个文件里了

import javax.swing.*;

public class ObserverTest {

    public static void main(String[] args) {
        MyObservableFrame frame = new RegisterFrame();
//        frame.setData(new User("XiWenRen","12345678"));
        frame.addObserver(new ValidateObserver());
        frame.setVisible(true);
    }
}

class ValidateObserver implements MyObserver {

    @Override
    public void update(MyObservableFrame o, Object arg) throws Exception {
        if (arg == null) {
            throw new NullPointerException("User cannot be null");
        }
        System.out.println(arg);
        //如果这里传入一个User类,就可以进行数据验证了
    }
}

class RegisterFrame extends MyObservableFrame {

    JButton registerBtn;

    public RegisterFrame() {
        super("Register");
        this.setSize(200, 200);
        initComps();
    }

    private void initComps() {
        registerBtn = new JButton("Register");
        this.add(registerBtn);
        registerBtn.addActionListener(new MyObservableListener() {
            @Override
            public void doAction() {
                System.out.println("Register....");
            }
        });
    }

    @Override
    public void setData(Object obj) {
        this.obj = obj;
    }
}

class User{

    public User(String userName, String passWord){
        this.userName = userName;
        this.passWord = passWord;
    }
    public String userName;
    public String passWord;
}

运行测试代码,如果没有setData,运行结果如下
这里写图片描述
加入`frame.setData(new User(“XiWenRen”,”12345678”));后,运行结果如下:
这里写图片描述
基本达到了设计的要求,但是有个问题在我能力之外,没有办法在创建窗口的时候为观察者进行注册,这部分代码是在系统UI框架中的,没有源码的权限。
不过没关系,这篇博客本就是边学边写的,或者说写这篇文章的初衷就是让自己去学习AOP的,所以在实现观察者模式之前我就已经知道了这个方案不可行,下一步就是逼自己上梁山了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值