十一大行为模式(下)

本文深入探讨了四种行为设计模式:观察者模式、状态模式、备忘录模式和访问者模式。通过现实世界中的例子,如朋友圈动态、用户状态管理、SQL解析和企业宴请,详细解释了这些模式的实现和应用场景。观察者模式用于对象状态变化的通知,状态模式用于根据对象状态改变行为,备忘录模式用于保存和恢复对象状态,访问者模式则允许在不改变数据结构的前提下定义新的操作。这些模式在软件设计中提供了灵活性和扩展性。
摘要由CSDN通过智能技术生成

行为模式之七:观察者模式

定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
(来自《设计模式之禅》)

你要的故事

想来想去,就拿我们现在生活中最常体会到的事情来讲观察者模式--朋友圈。小明、小红、小东 3 人是好朋友,最近他们的父母都给安排了手机,刚用上手机那是相当的兴奋呀。他们立马从 QQ 转投到微信的怀抱,对微信的朋友圈玩的不亦乐乎,什么事情都往上面发。突然有一天,小明和小红因为一些小事争执闹别扭了,原因就是他们对一道数学题有不同的见解。就跟我们小时候和朋友玩得好好的,突然因为一点小事就闹翻了。小红比较孩子气,立马就屏蔽了小明的朋友圈,不想再看到有关小明相关的信息。故事就是这么一回事,关注点就在这朋友圈上。朋友圈就是运用观察者模式的一个很好的样例。为什么这么说?我们发朋友圈的时候,那些没有屏蔽我们朋友圈的好友,会收到信息推送。也就是没有屏蔽我们朋友圈的好友其实是订阅了我们朋友圈,好友相当于观察者,我们是被观察的对象。符合观察者模式这个关系。

我们通过代码来描述小明、小红、小东他们在朋友圈玩的场景。利用观察者模式,需要观察对象和被观察对象,所以我们先定义 2 个接口,分别是 Observable (可被观察接口) 和 Observer (观察者接口)。

实现 Observable 接口的对象说明是可被订阅观察的,所以它需要 addObserver() 新增订阅者方法和 removeObserver() 移除订阅者方法,另外还有一个是必须的,就是通知各个订阅者消息的方法 notifyObservers()。那 Observable 接口代码如下所示。

interface Observable {
    void addObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers(String message);
}

实现 Observer 接口的对象说明是可以去订阅观察的,也就是说可以接收被订阅的对象发出来的消息,那就需要一个接收消息的方法 update()。代码如下所示。

interface Observer {
    void update(String name, String message);
}

为了让大家不混淆,先把观察者和被观察者分离开,其实在这个例子中,观察者和被观察者是同一个对象 User 的。这里就分开,分成 User 和 Friend,后面会给出正确的代码,稍安勿躁哈。这里 User 作为被观察者,实现了 Observable 接口,而 Friend 作为观察者,实现了 Observer 接口。代码如下。

class User implements Observable {

    private List<Observer> friends;
    private String name;

    public User(String name) {
        this.name = name;
        this.friends = new LinkedList<>();
    }

    public void sendMessage(String message) {
        this.notifyObservers(message);
    }

    @Override
    public void addObserver(Observer observer) {
        this.friends.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        this.friends.remove(observer);
    }

    @Override
    public void notifyObservers(String message) {
        this.friends.forEach(friend -> {
            friend.update(this.name, message);
        });
    }
}

class Friend implements Observer {

    private String name;

    public Friend(String name) {
        this.name = name;
    }
    @Override
    public void update(String name, String message) {
        System.out.println("【" + this.name + "】看到【" + name + "】发的朋友圈:" + message);
    }
}

public class ObserverTest {

    public static void main(String[] args) {
        User xiaoMing = new User("小明");
        Friend xiaoHong = new Friend("小红");
        Friend xiaoDong = new Friend("小东");
        xiaoMing.addObserver(xiaoHong);
        xiaoMing.addObserver(xiaoDong);
        xiaoMing.sendMessage("今天真开心");
        // 小红和小明闹别扭了,小红取消订阅小明的朋友圈
        xiaoMing.removeObserver(xiaoHong);
        xiaoMing.sendMessage("希望明天也像今天一样开心");
    }

}

打印结果:
【小红】看到【小明】发的朋友圈:今天真开心
【小东】看到【小明】发的朋友圈:今天真开心
【小东】看到【小明】发的朋友圈:希望明天也像今天一样开心

看到代码执行结果,小红和小东都订阅了小明的朋友圈,小明发了朋友圈:今天真开心。他们俩都收到了,因为小红和小明闹别扭,小红取消订阅小明的朋友圈,所以小明后来发的朋友圈,小红没收到。

上面代码其实是不对的,不应该用 User 和 Friend 2 个类来定义。如果小明订阅小红和小东的朋友圈呢?这样实现比较麻烦,主要是为了分清 观察者 和 被观察者 这 2 个概念,通过上面的例子应该分清楚了 2 个概念了,那就可以来看正确的代码,小明、小红、小东他们其实都是观察者和被观察者,所以我们用 User2 来定义他们就可以,User2 实现了 Observable 和 Observer 接口。代码如下。

class User2 implements Observable, Observer {

    private List<Observer> friends;
    private String name;

    public User2(String name) {
        this.name = name;
        this.friends = new LinkedList<>();
    }

    @Override
    public void addObserver(Observer observer) {
        this.friends.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        this.friends.remove(observer);
    }

    @Override
    public void notifyObservers(String message) {
        this.friends.forEach(friend -> {
            friend.update(this.name, message);
        });
    }

    @Override
    public void update(String name, String message) {
        System.out.println("【" + this.name + "】看到【" + name + "】发的朋友圈:" + message);
    }

    public void sendMessage(String message) {
        this.notifyObservers(message);
    }
}

public class ObserverTest {

    public static void main(String[] args) {
        User2 xiaoMing2 = new User2("小明");
        User2 xiaoHong2 = new User2("小红");
        User2 xiaoDong2 = new User2("小东");
        xiaoMing2.addObserver(xiaoHong2);
        xiaoMing2.addObserver(xiaoDong2);
        xiaoMing2.sendMessage("今天真开心");
        xiaoMing2.removeObserver(xiaoHong);
        xiaoMing2.sendMessage("希望明天也像今天一样开心");

        xiaoHong2.addObserver(xiaoMing2);
        xiaoHong2.addObserver(xiaoDong2);
        xiaoHong2.sendMessage("今天和小明吵架了,屏蔽他的朋友圈");

        xiaoDong2.addObserver(xiaoMing2);
        xiaoDong2.addObserver(xiaoHong2);
        xiaoDong2.sendMessage("小明和小红吵架了,夹在中间好尴尬");
    }

}

打印结果:
【小红】看到【小明】发的朋友圈:今天真开心
【小东】看到【小明】发的朋友圈:今天真开心
【小红】看到【小明】发的朋友圈:希望明天也像今天一样开心
【小东】看到【小明】发的朋友圈:希望明天也像今天一样开心
【小明】看到【小红】发的朋友圈:今天和小明吵架了,屏蔽他的朋友圈
【小东】看到【小红】发的朋友圈:今天和小明吵架了,屏蔽他的朋友圈
【小明】看到【小东】发的朋友圈:小明和小红吵架了,夹在中间好尴尬
【小红】看到【小东】发的朋友圈:小明和小红吵架了,夹在中间好尴尬

从代码中,我们看到小明、小红、小东 3 个人互相订阅朋友圈,当然中途小红屏蔽了小明的朋友圈。这就是 观察者 和 被观察者 刚好是同一个对象的实现。

总结

观察者模式 是一个比较特殊的设计模式,它定义了触发机制,观察者只要订阅了被观察者,就可以第一时间得到被观察者传递的信息。在工作中,使用观察者模式的场景也比较多,比如消息队列消费,Android 开发中的事件触发机制等等。好,观察者模式就到这。

行为模式之八:状态模式

当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。
(来自《设计模式之禅》)

你要的故事

现在有好多个人贷款软件,比如:支付宝、360借条(打广告。。。)等等。贷款会有一个用户状态流程,游客->注册用户->授信用户->借款用户(这里简化了状态,只用 4 个)。每个状态拥有的权限不一样,如下图所示。

图片

状态

从上图可以看到,一个用户有 3 种行为,分别是注册、授信、借款。当注册成功后,用户的状态就从『游客』改变为『注册用户』;当授信成功后,用户的状态就从『注册用户』改变为『授信用户』;当借款成功后,用户的状态就从『授信用户』改变为『借款用户』。现在我们就来实现用户注册、授信、借款的过程,因为每个状态的权限不一样,所以这里需要根据用户的状态来限制用户行为。

很快,我们就完成下面的代码。

class User {
    private String state;

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public void register() {
        if ("none".equals(state)) {
            System.out.println("游客。注册中。。。");
        }else if ("register".equals(state)) {
            System.out.println("注册用户。不需要再注册。");
        } else if ("apply".equals(state)) {
            System.out.println("授信用户。不需要再注册。");
        } else if ("draw".equals(state)) {
            System.out.println("借款用户。不需要再注册。");
        }
    }

    public void apply() {
        if ("none".equals(state)) {
            System.out.println("游客。不能申请授信。");
        }else if ("register".equals(state)) {
            System.out.println("注册用户。授信申请中。。。");
        } else if ("apply".equals(state)) {
            System.out.println("授信用户。不需要再授信。");
        } else if ("draw".equals(state)) {
            System.out.println("借款用户。不需要再授信。");
        }
    }

    public void draw(double money) {
        if ("none".equals(state)) {
            System.out.println("游客。申请借款【" + money + "】元。不能申请借款。");
        } else if ("register".equals(state)) {
            System.out.println("注册用户。申请借款【" + money + "】元。还没授信,不能借款。");
        } else if ("apply".equals(state)) {
            System.out.println("授信用户。申请借款【" + money + "】元。申请借款中。。。");
        } else if ("draw".equals(state)) {
            System.out.println("授信用户。申请借款【" + money + "】元。申请借款中。。。");
        }
    }
}

public class NoStateTest {

    public static void main(String[] args) {
        User user = new User();
        user.setState("register");
        user.draw(1000);
    }

}

打印结果:
注册用户。申请借款【1000.0】元。还没授信,不能借款。

上面代码实现了用户 register (注册),apply (授信),draw (借款) 这 3 种行为,每个行为都会根据状态 state 来做权限控制。看起来有点繁琐,扩展性不高,假设新增了一个状态,那么注册、授信、借款这 3 种行为的代码都要修改。下面通过状态模式来解决这个问题。

我们把状态给抽出来,作为一个接口,因为在每种状态中都可能有注册、授信、借款行为,所以把这 3 个行为作为状态接口的方法,让每个状态子类都实现相应的行为控制。如下代码所示。

interface State {

    void register();

    void apply();

    void draw(double money);
}

/**
 * 游客
 */
class NoneState implements State {

    @Override
    public void register() {
        System.out.println("游客。注册中。。。");
    }

    @Override
    public void apply() {
        System.out.println("游客。不能申请授信。");
    }

    @Override
    public void draw(double money) {
        System.out.println("游客。申请借款【" + money + "】元。不能申请借款。");
    }
}

/**
 * 注册状态
 */
class RegisterState implements State {

    @Override
    public void register() {
        System.out.println("注册用户。不需要再注册。");
    }

    @Override
    public void apply() {
        System.out.println("注册用户。授信申请中。。。");
    }

    @Override
    public void draw(double money) {
        System.out.println("注册用户。申请借款【" + money + "】元。还没授信,不能借款。");
    }
}

/**
 * 授信状态
 */
class ApplyState implements State {

    @Override
    public void register() {
        System.out.println("授信用户。不需要再注册。");
    }

    @Override
    public void apply() {
        System.out.println("授信用户。不需要再授信。");
    }

    @Override
    public void draw(double money) {
        System.out.println("授信用户。申请借款【" + money + "】元。申请借款中。。。");
    }
}

/**
 * 借款状态
 */
class DrawState implements State {

    @Override
    public void register() {
        System.out.println("借款用户。不需要再注册。");
    }

    @Override
    public void apply() {
        System.out.println("借款用户。不需要再授信。");
    }

    @Override
    public void draw(double money) {
        System.out.println("申请借款【" + money + "】元。申请借款中。。。");
    }
}

class User1 {
    private State state;

    public State getState() {
        return state;
    }

    public void setState(State state) {
        this.state = state;
    }

    public void register() {
        this.state.register();
    }

    public void apply() {
        this.state.apply();
    }

    public void draw(double money) {
        this.state.draw(money);
    }
}

public class StateTest {
    public static void main(String[] args) {
        User1 user1 = new User1();
        user1.setState(new RegisterState());
        user1.apply();
        user1.draw(1000);
        user1.setState(new ApplyState());
        user1.draw(2000);
    }

}
打印结果:
注册用户。授信申请中。。。
注册用户。申请借款【1000.0】元。还没授信,不能借款。
授信用户。申请借款【2000.0】元。申请借款中。。。

看上面代码,我们抽象了 State 接口,4 种状态分别用 NoneState (游客)、RegisterState (注册)、ApplyState (授信)、DrawState (借款) 表示。而每个状态都有 3 种行为,它们各自对这些行为进行权限控制。这样子实现可以让权限逻辑分离开,分散到每个状态里面去,如果以后要业务扩展,要新增状态,那就很方便了,只需要再实现一个状态类就可以,不会影响到其他代码。这也是为什么《阿里巴巴 Java 开发手册》里面讲的,当超过 3 层的 if-else 的逻辑判断代码,推荐用状态模式来重构代码。

总结

状态模式 很好的减低了代码的复杂性,从而提高了系统的可维护性。在业务开发中可以尝试使用,比如在迭代开发中,业务逻辑越来越复杂,从而不得不使用很多 if-else 语句来实现时,就可以考虑一下是不是可以用 状态模式 来重构,特别是一些有状态流程转换方面的业务。看到这篇文章,想想工作中是不是有些复杂的代码可以重构,赶紧行动起来。

 

行为模式之九:备忘录模式

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
(来自《设计模式之禅》)

你要的故事

点开看这篇文章的各位,都是。。。程序界的大佬。作为程序猿,免不了『上线』这件小事。每逢上线必祭天。。。上线这件事我们很多人都操作过,每家公司有不同的上线流程以及上线的技术能力。按照发布的平台的完善程度大概分为 3 种。

  1. 发布平台牛逼的公司:只需要按下『一键部署』按钮,就搞定上线,按下『回滚』按钮,就搞定回滚上一个版本。

  2. 发布平台稍差点的公司:可能就得多个步骤操作了,上线:备份并关闭应用、部署并启动新应用;回滚:关闭新应用、恢复旧应用并启动。

  3. 没有发布平台的公司:那就全程手工操作,上线:关闭旧应用、复制旧应用到备份空间、复制新应用到部署环境、启动新应用;回滚:关闭新应用、删除新应用、从备份空间复制旧应用到部署环境、启动旧应用。

其实通过发布平台完善程度可以侧面反映企业的技术成熟程度。怎么说呢?发布系统的操作难易程度在我们作为程序猿心中,都有一个可接受的范围。假设刚开始是单体应用,一个 tomcat 和一个 war 就搞定,你需要发布平台么?并不需要,咱 ctrl+Cctrl+Vshutdownstartup 就行,还弄什么发布平台。当系统有多套服务时,每次上线都需要部署 10 个机器的应用,这时你能忍么?要是还是手工操作,那发布一次系统得花费好长时间,要是再搞不好,回滚一次,一晚上都没了;这时就会逼迫开发出一个简易的发布平台,把对多台机器的操作步骤放到发布平台上。当系统是以微服务的架构发展时,每个服务都有上百个实例,那这时就不能简单的把操作步骤搬到发布平台了,还得简化步骤,最终变成上面说的 一键部署 和一键回滚

上面的 3 种我都亲身经历过。。。在刚出来实习时候,就经历了第 3 种情况,因为是单体应用,一个应用搞定所有东西,手动部署已满足要求。到了银行工作,接触到了云平台,那时就只需要一个按钮就唰唰唰的部署了,也是上面说的第 1 种。而现在,正在经历第 2 种发布平台,只是简单的把操作步骤搬到了系统上,目前的情况是机器越来越多,操作步骤没删减的话,每次发布会花费很多时间,这也会去促进开发出更方便使用的发布平台。

回到今天的主题,今天讲的是备忘录模式,从字面上理解,就是讲备份东西,有了备份就可以恢复。上面讲了一大堆发布的东西,也是咱们工作中接触蛮多的事情,发布的最核心就是要支持部署新应用以及回滚老应用回滚特别重要,它能够保证在新应用出现异常的情况下,马上恢复到旧应用可用的状态,减少异常的影响面。发布这东东也很符合备忘录模式,下面通过模拟发布步骤代码来讲备忘录模式。这里讲的不是上面说的第 1 种,这里围绕着第 3 种发布步骤写,不管哪种发布平台,它们的底层都是一样的。

先定义应用实例这个类,应用一般会有应用名、版本号信息。

/**
 * 应用实例
 */
class App {
    private String content;
    private String version;

    public App(String content, String version) {
        this.content = content;
        this.version = version;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    @Override
    public String toString() {
        return "App{" +
                "content='" + content + '\'' +
                ", version='" + version + '\'' +
                '}';
    }
}

定义 AppBackup 来充当备忘录角色,它有一个属性就是 App,也就是备份的应用。

/**
 * 应用备份(充当备忘录角色)
 */
class AppBackup {

    private App app;

    public AppBackup(App app) {
        this.app = app;
    }

    public App getApp() {
        return app;
    }

    public void setApp(App app) {
        this.app = app;
    }
}

有了备忘录,也需要一个空间来存放备忘录,并对外提供备忘录。

/**
 * 备份空间
 */
class Space {
    private AppBackup appBackup;

    public AppBackup getAppBackup() {
        return appBackup;
    }

    public void setAppBackup(AppBackup appBackup) {
        this.appBackup = appBackup;
    }
}

有了这些备份机制,还需要有一个程序猿来部署,这位同学需要掌握发布步骤的所有过程,部署新应用以及回滚旧应用。

/**
 * 部署应用的同学
 */
class Deployer {

    // 要部署的应用
    private App app;

    public App getApp() {
        return app;
    }

    // 设置部署应用
    public void setApp(App app) {
        this.app = app;
    }

    // 创建应用的备份
    public AppBackup createAppBackup() {
        return new AppBackup(app);
    }

    // 从备忘录恢复应用
    public void setAppBackup(AppBackup appBackup) {
        this.app = appBackup.getApp();
    }

    // 显示应用的信息
    public void showApp() {
        System.out.println(this.app.toString());
    }

    // 暂停应用
    public void stopApp() {
        System.out.println("暂停应用:" + this.app.toString());
    }

    // 启动应用
    public void startApp() {
        System.out.println("启动应用:" + this.app.toString());
    }
}

再献上测试代码。

public class MementoTest {

    public static void main(String[] args) {
        Deployer deployer = new Deployer();
        deployer.setApp(new App("apply-system", "1.0.0"));

        System.out.println("1. 暂停旧应用");
        deployer.stopApp();

        System.out.println("2. 备份旧应用");
        Space space = new Space();
        space.setAppBackup(deployer.createAppBackup());

        System.out.println("3. 拷贝新应用到服务器");
        deployer.setApp(new App("apply-system", "2.0.0"));
        deployer.showApp();

        System.out.println("4. 启动新应用");
        deployer.startApp();

        System.out.println("5. 有异常,暂停新应用");
        deployer.stopApp();

        System.out.println("6. 回滚旧应用,拷贝备份的旧应用到服务器");
        deployer.setAppBackup(space.getAppBackup());
        deployer.showApp();

        System.out.println("7. 启动备份的旧应用");
        deployer.startApp();
    }

}

打印结果:
1. 暂停旧应用
暂停应用:App{content='apply-system', version='1.0.0'}
2. 备份旧应用
3. 拷贝新应用到服务器
App{content='apply-system', version='2.0.0'}
4. 启动新应用
启动应用:App{content='apply-system', version='2.0.0'}
5. 有异常,暂停新应用
暂停应用:App{content='apply-system', version='2.0.0'}
6. 回滚旧应用,拷贝备份的旧应用到服务器
App{content='apply-system', version='1.0.0'}
7. 启动备份的旧应用
启动应用:App{content='apply-system', version='1.0.0'}

备忘录模式代码实现搞定。有同学会不会觉得挺麻烦的,为什么要有AppBackup?我们看看个人介绍,在对象之外保存状态,AppBackup 就是对象之外的对象,用来保存旧应用。

总结

备忘录模式定义了一个备份机制。在很多场景都有类似备忘录模式的实现,比如数据库的事务的回滚机制。在平常业务开发中并没有经常使用这个设计模式,但是我们有使用它的思想,比如我们用数据库或者其他中间件做备份数据,其中备份思想是一致的。

行为模式之十:解释器模式

给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
(来自《设计模式之禅》)

你要的故事

解释器顾名思义就是对 2 个不同的表达方式进行转换,让本来不懂的内容解释成看得懂的。比如翻译官就是解释器,把英文翻译成中文,让我们明白外国人说什么。咱们工作中也有很多类似的场景,开发系统避免不了使用数据库,数据库有特定的语法,我们称为 SQL (Structured Query Language),而我们系统开发语言和 SQL 的语法不一样,这中间就需要做一层转换,像把 Java 语言中的 userDao.save(user) 变成 insert into user (name,age) values ('小明', 18),这一层转换也可以称为解释器。很多框架实现了这个功能,比如 Hibernate,我们称这些框架为 ORM

今天,我们就来简单的实现 SQL 拼接解释器,通过参数组装成我们要的 SQL 语句。好多开发同学都吐槽工作天天在 CRUD,也就是只干增删改查的活,对于 SQL 我们经常用的也就是这 4 种语法:insert 语句、delete 语句、update 语句、select 语句。这 4 种语法各有不同,也即需要不同的解释器去解析。利用今天要讲的解释器模式,我们来实现一番。

解释器模式中,会有一个上下文类,这个类用于给解释器传递参数。这里我们 SQL 解释器需要的参数分别是

  1. tableName :数据库名

  2. params :修改时更新后的数据

  3. wheres :where 语句后的条件

class Context {
    private String tableName;
    private Map<String, Object> params = new HashMap<>();
    private Map<String, Object> wheres = new HashMap<>();

    public String getTableName() {
        return tableName;
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    public Map<String, Object> getParams() {
        return params;
    }

    public void setParams(Map<String, Object> params) {
        this.params = params;
    }

    public Map<String, Object> getWheres() {
        return wheres;
    }

    public void setWheres(Map<String, Object> wheres) {
        this.wheres = wheres;
    }
}

解释器主角来了,定义 SQL 解释器抽象类,它有一个抽象方法 interpret,通过这个方法来把 context 中的参数解释成对应的 SQL 语句。

/**
 * SQL 解释器
 */
abstract class SQLExpression {

    public abstract String interpret(Context context);

}

我们上面说了 SQL 语句用的比较多的就是 4 种,每一种其实就是一个解释器,因为语法不一样,解释的逻辑也就不一样,我们就利用 SQLExpression 解释器抽象类,来实现 4 个具体的 SQL 解释器,分别如下:

Insert SQL 解释器代码实现:

/**
 * Insert SQL 解释器
 */
class InsertSQLExpression extends SQLExpression {

    @Override
    public String interpret(Context context) {
        StringBuilder insert = new StringBuilder();
        insert.append("insert into ")
                .append(context.getTableName());

        // 解析 key value
        StringBuilder keys = new StringBuilder();
        StringBuilder values = new StringBuilder();
        keys.append("(");
        values.append("(");
        for (String key : context.getParams().keySet()) {
            keys.append(key).append(",");
            values.append("'").append(context.getParams().get(key)).append("',");
        }
        keys = keys.replace(keys.length() - 1, keys.length(), ")");
        values = values.replace(values.length() - 1, values.length(), ")");

        // 拼接 keys values
        insert.append(keys)
                .append(" values ")
                .append(values);

        System.out.println("Insert SQL : " + insert.toString());
        return insert.toString();
    }
}

Update SQL 解释器代码实现:

/**
 * Update SQL 解释器
 */
class UpdateSQLExpression extends SQLExpression {

    @Override
    public String interpret(Context context) {
        StringBuilder update = new StringBuilder();
        update.append("update ")
                .append(context.getTableName())
                .append(" set ");

        StringBuilder values = new StringBuilder();
        for (String key : context.getParams().keySet()) {
            values.append(key)
                    .append(" = '")
                    .append(context.getParams().get(key))
                    .append("',");
        }

        StringBuilder wheres = new StringBuilder();
        wheres.append(" 1 = 1 ");
        for (String key : context.getWheres().keySet()) {
            wheres.append(" and ")
                    .append(key)
                    .append(" = '")
                    .append(context.getWheres().get(key))
                    .append("'");
        }

        update.append(values.substring(0, values.length() - 1))
                .append(" where ")
                .append(wheres);

        System.out.println("Update SQL : " + update.toString());
        return update.toString();
    }
}

Select SQL 解释器代码实现:

/**
 * Select SQL 解释器
 */
class SelectSQLExpression extends SQLExpression {

    @Override
    public String interpret(Context context) {
        StringBuilder select = new StringBuilder();
        select.append("select * from ")
                .append(context.getTableName())
                .append(" where ")
                .append(" 1 = 1 ");
        for (String key : context.getWheres().keySet()) {
            select.append(" and ")
                    .append(key)
                    .append(" = '")
                    .append(context.getWheres().get(key))
                    .append("'");
        }
        System.out.println("Select SQL : " + select.toString());
        return select.toString();
    }
}

Delete SQL 解释器代码实现

/**
 * Delete SQL 解释器
 */
class DeleteSQLExpression extends SQLExpression {

    @Override
    public String interpret(Context context) {
        StringBuilder delete = new StringBuilder();
        delete.append("delete from ")
                .append(context.getTableName())
                .append(" where ")
                .append(" 1 = 1");
        for (String key : context.getWheres().keySet()) {
            delete.append(" and ")
                    .append(key)
                    .append(" = '")
                    .append(context.getWheres().get(key))
                    .append("'");
        }
        System.out.println("Delete SQL : " + delete.toString());

        return delete.toString();
    }
}

测试代码

public class InterpreterTest {
    public static void main(String[] args) {
        Context context = new Context();
        context.setTableName("user");

        // Insert SQL
        Map<String, Object> params = new HashMap<>();
        params.put("name", "小明");
        params.put("job", "Java 工程师");
        context.setParams(params);
        SQLExpression sqlExpression = new InsertSQLExpression();
        String sql = sqlExpression.interpret(context);

        // Delete SQL
        Map<String, Object> wheres = new HashMap<>();
        wheres.put("name", "小明");
        context.setParams(null);
        context.setWheres(wheres);
        sqlExpression = new DeleteSQLExpression();
        sql = sqlExpression.interpret(context);

        // Update SQL
        params = new HashMap<>();
        params.put("job", "Java 高级工程师");
        wheres = new HashMap<>();
        wheres.put("name", "小明");
        context.setParams(params);
        context.setWheres(wheres);
        sqlExpression = new UpdateSQLExpression();
        sql = sqlExpression.interpret(context);

        // Select SQL
        wheres = new HashMap<>();
        wheres.put("name", "小明");
        context.setParams(null);
        context.setWheres(wheres);
        sqlExpression = new SelectSQLExpression();
        sql = sqlExpression.interpret(context);
    }

}

打印结果:

Insert SQL : insert into user(name,job) values ('小明','Java 工程师')
Delete SQL : delete from user where  1 = 1 and name = '小明'
Update SQL : update user set job = 'Java 高级工程师' where  1 = 1  and name = '小明'
Select SQL : select * from user where  1 = 1  and name = '小明'

上面实现了整个解释器模式的代码,其实咱们在开发中,SQL 解析没有这么去实现,更多是用一个工具类把上面的各个 SQL 解释器的逻辑代码分别实现在不同方法中,如下代码所示。因为咱们可以预见的就这 4 种语法类型,基本上不用什么扩展,用一个工具类就足够了。

class SQLUtil {

    public static String insert(String tableName, Map<String, Object> params) {
        StringBuilder insert = new StringBuilder();
        insert.append("insert into ")
                .append(tableName);

        // 解析 key value
        StringBuilder keys = new StringBuilder();
        StringBuilder values = new StringBuilder();
        keys.append("(");
        values.append("(");
        for (String key : params.keySet()) {
            keys.append(key).append(",");
            values.append("'").append(params.get(key)).append("',");
        }
        keys = keys.replace(keys.length() - 1, keys.length(), ")");
        values = values.replace(values.length() - 1, values.length(), ")");

        // 拼接 keys values
        insert.append(keys)
                .append(" values ")
                .append(values);

        System.out.println("Insert SQL : " + insert.toString());
        return insert.toString();
    }

    public static String update(String tableName, Map<String, Object> params, Map<String, Object> wheres) {
        StringBuilder update = new StringBuilder();
        update.append("update ")
                .append(tableName)
                .append(" set ");

        StringBuilder values = new StringBuilder();
        for (String key : params.keySet()) {
            values.append(key)
                    .append(" = '")
                    .append(params.get(key))
                    .append("',");
        }

        StringBuilder wheresStr = new StringBuilder();
        wheresStr.append(" 1 = 1 ");
        for (String key : wheres.keySet()) {
            wheresStr.append(" and ")
                    .append(key)
                    .append(" = '")
                    .append(wheres.get(key))
                    .append("'");
        }

        update.append(values.substring(0, values.length() - 1))
                .append(" where ")
                .append(wheresStr);

        System.out.println("Update SQL : " + update.toString());
        return update.toString();
    }

    public static String select(String tableName, Map<String, Object> wheres) {
        StringBuilder select = new StringBuilder();
        select.append("select * from ")
                .append(tableName)
                .append(" where ")
                .append(" 1 = 1 ");
        for (String key : wheres.keySet()) {
            select.append(" and ")
                    .append(key)
                    .append(" = '")
                    .append(wheres.get(key))
                    .append("'");
        }
        System.out.println("Select SQL : " + select.toString());
        return select.toString();
    }

    public static String delete(String tableName, Map<String, Object> wheres) {
        StringBuilder delete = new StringBuilder();
        delete.append("delete from ")
                .append(tableName)
                .append(" where ")
                .append(" 1 = 1");
        for (String key : wheres.keySet()) {
            delete.append(" and ")
                    .append(key)
                    .append(" = '")
                    .append(wheres.get(key))
                    .append("'");
        }
        System.out.println("Delete SQL : " + delete.toString());

        return delete.toString();
    }
}

总结

上面用解释器模式实现了 SQL 解释器,然后又指明了实际上咱们开发中大多数是直接一个 SQLUtil 工具类就搞定,并不是说解释器模式没用,想表达的观点是:解释器在工作中很少使用,工作中我们一般遵循的是能用就好策略,满足当前需求,加上一些易扩展性就足够了。解释器模式有比较大的扩展性,就如上面,再加上个建表语句 create table 只需要加一个 CreateTableSQLExpression 就可以轻松实现,不用去改动其他解释器代码。

行为模式之十一:访问者模式

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
(来自《设计模式之禅》)

你要的故事

先声明一下,下面故事全瞎编的。。。

我们是否还记得 N 年前反腐开始的时候,有一段时间提倡官员宴请吃饭只能几菜几汤,不能超出。我记得那会刚读大一,军事理论的老师说到这个问题,也发表了他的一些想法,他觉得这么做比较刻板。今天的故事就和宴请有关。现在中国企业发展越来越大,在社会中担任的责任也越来越大,政府也越来越重视企业,官员去参观企业是常有的事,而企业宴请官员也变得格外的常见。

故事的背景就是企业宴请各级官员。不同级别的官员宴请的菜式就不一样,每家企业的菜式丰富程度也不一样。我们这里的访问对象就用 Alibaba 和 Tencent 这 2 家公司,而访问者就用郭嘉领导人和省领导人做举例。这 2 家公司都跟喜来登酒店合作,Alibaba 合作方案是:宴请省级领导人及以下官员则十菜一汤,宴请郭嘉领导人及以上官员则十四菜两汤;Tencent 合作方案是:宴请省领导人及以下官员则八菜一汤,宴请郭嘉领导人及以上官员则十六菜两汤。

下面看看如何用访问者模式来实现上面的故事。

首先定义一个抽象类:企业。企业有一个共有的特性就是接受上级领导的访问。

/**
 * 企业
 */
abstract class Company {

    public abstract void accept(Vistor vistor);

}

上面故事我们举例了 2 家企业,分别是 Alibaba 和 Tencent,这里实现这 2 家公司的宴请方案,并实现接待访问者方法。

Alibaba 宴请郭嘉领导人及以上官员是十四菜两汤,宴请省领导及以下是十菜一汤。

/**
 * Alibaba 企业
 */
class AlibabaCompany extends Company {

    @Override
    public void accept(Vistor vistor) {
        vistor.visit(this);
    }

    public String entertainBelowProvincialLeader(String leader) {
        return "Alibaba 接待" + leader + ":十菜一汤";
    }

    public String entertainAboveNationalLeader(String leader) {
        return "Alibaba 接待" + leader + ":十四菜两汤";
    }

}

Tencent 宴请郭嘉领导人及以上是十六菜两汤,宴请省领导及以下是八菜一汤。

/**
 * Tencent 企业
 */
class TencentCompany extends Company {

    @Override
    public void accept(Vistor vistor) {
        vistor.visit(this);
    }

    public String entertainBelowProvincialLeader(String leader) {
        return "Tencent 接待" + leader + ":八菜一汤";
    }

    public String entertainAboveNationalLeader(String leader) {
        return "Tencent 接待" + leader + ":十六菜两汤";
    }
}

这里定义访问者接口,访问者接口有 2 个方法,分别是访问 Alibaba 企业和访问 Tencent 企业。

/**
 * 访问者接口
 */
interface Vistor {

    void visit(AlibabaCompany alibabaCompany);

    void visit(TencentCompany tencentCompany);

}

上面故事中有 2 个访问者,一个是郭嘉领导人,另一个是省领导人,因为不同企业对应不同访问者有不同的宴请方案,所以这里访问企业是需要调用对应企业的宴请方式。

省领导人访问企业时,需要调用企业对省领导及以下官员的宴请方案,为entertainBelowProvincialLeader()

/**
 * 省领导访问
 */
class ProvincialLeaderVistor implements Vistor {

    @Override
    public void visit(AlibabaCompany alibabaCompany) {
        System.out.println(alibabaCompany.entertainBelowProvincialLeader("省领导"));
    }

    @Override
    public void visit(TencentCompany tencentCompany) {
        System.out.println(tencentCompany.entertainBelowProvincialLeader("省领导"));
    }
}

郭嘉领导人访问企业时,需要调用企业对郭嘉领导人的宴请方案,为entertainAboveNationalLeader()

/**
 * 郭嘉领导访问
 */
class NationalLeaderVistor implements Vistor {

    @Override
    public void visit(AlibabaCompany alibabaCompany) {
        System.out.println(alibabaCompany.entertainAboveNationalLeader("省领导"));
    }

    @Override
    public void visit(TencentCompany tencentCompany) {
        System.out.println(tencentCompany.entertainAboveNationalLeader("郭嘉领导"));
    }
}

上面是访问者和被访问者的代码,因为企业是在喜来登酒店宴请领导人,所以这里还需要一个酒店,酒店里面有企业合作的名单,以及负责宴请各路领导的方法提供。

/**
 * 酒店
 */
class Hotel {
    private List<Company> companies = new ArrayList<>();

    public void entertain(Vistor vistor) {
        for (Company company : companies) {
            company.accept(vistor);
        }
    }

    public void add(Company company) {
        companies.add(company);
    }
}

下面提供测试代码,看看运行的结果怎样。

public class VisitorTest {

    public static void main(String[] args) {
        AlibabaCompany alibabaCompany = new AlibabaCompany();
        TencentCompany tencentCompany = new TencentCompany();
        ProvincialLeaderVistor provincialLeaderVistor = new ProvincialLeaderVistor();
        NationalLeaderVistor nationalLeaderVistor = new NationalLeaderVistor();

        Hotel xilaideng = new Hotel();
        xilaideng.add(alibabaCompany);
        xilaideng.add(tencentCompany);

        xilaideng.entertain(provincialLeaderVistor);
        xilaideng.entertain(nationalLeaderVistor);
    }

}

打印结果:
Alibaba 接待省领导:十菜一汤
Tencent 接待省领导:八菜一汤
Alibaba 接待郭嘉领导:十四菜两汤
Tencent 接待郭嘉领导:十六菜两汤

完整的访问者模式代码已经呈现,花 1 分钟思考一番,理解整个代码后我们来看看下面的总结。

总结

访问者模式有比较好的扩展性,看看访问者代码,我们如果要新增一个访问者:市领导人,只需新增市领导人类,便可实现。当然也有它不好的地方,就是把被访问者暴露给访问者,使得访问者可以直接了解被访问者的所有东西。明白了优缺点,才能更好的在实际中运用,一般访问者模式运用于要求遍历多个不同的对象的场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值