设计模式(基本原则)之适配器模式、委派模式、访问者模式、工厂模式、桥接模式(双维度扩展)、解释器模式

设计模式之适配器模式、委派模式、访问者模式、工厂模式、观察者-发布订阅模式

设计模式基本原则

一:单一职责原则

官方定义:应该有且仅有一个原因引起类的变更。
 单一职责适用于类、接口、方法,比如:比如一个方法尽量只做一件事情。
优点:提高可读性、可维护性。
总结:"职责"没有一个量化的标准、一个类到底要负责哪些职责?职责怎么去细化需要根据项目的实际去考虑,
比如项目的可变因素和不可变因素,项目的收益成本比。
    接口一定要做到单一职责、实现类建议多方面考虑,尽量做到只有一个原因引起的变化,过分细分类的职责也会增加系统的复杂性。

二:里氏替换原则

官方定义:所有使用基类的地方必须能透明的使用其子类对象
解释:只要父类能出现的地方子类就能出现,且替换子类也不会产生任何错误或异常,使用者可能根本就不需要知道使用的是父类还是子类,但是反过来就不行。
总结:采用里氏替换原则时,尽量避免子类的“个性“。里氏替换原则为继承定义了如下规范:
1.子类必须完全实现父类的方法。
2.子类可以有自己的个性
3.子类覆盖父类的方法时参数可以放大(不是overrivde而是overload)

三:依赖倒置原则

官方定义:高层模块不应该依赖低层模块,两者都应该依赖抽象。抽象不应该依赖细节。细节应该依赖抽象。
解释:在JAVA中,这个定义就是
	1.模块间的依赖通过抽象发生,具体实现类不直接发生依赖,依赖关系通过接口和抽象类产生。
	2.接口或实现类不依赖具体实现类。
	3.实现类依赖接口或抽象类。
	简而言之:面向接口编程。
总结:依赖倒置理论很好的解释了“面向接口编程”的思想,依赖倒置的三种写法:
	1.构造函数传递依赖对象
	2.Setter方法传递依赖对象
	3.接口声明依赖对象(通过方法参数传递)

四:接口隔离原则

官方定义:1.客户端不应该依赖他不需要的接口,2.类之间的依赖关系应该建立在最小的接口上。
解释:个人简单理解为:1.不需要的接口不要注入进来,2.接口细化,依赖的接口方法尽量少,不需要的方法也不要提供给我调用。
总结:接口隔离即是接口的定义,也是对类的定义,接口和类尽量使用原子类或原子接口来组装。接口设计的颗粒度越小,系统越灵活,
	但是灵活的同时也带来了结构的复杂化,接口的设计一定要注意一个"度",这个”度“没有一个固化或可测量的标准,可以参考
	如下原则:
	1.一个接口只服务于一个模块或一个业务逻辑;
	2.通过业务逻辑压缩接口中的public方法,尽量让接口”满身筋骨肉“,而不是”肥嘟嘟“的一大堆方法。
	3.已经被污染的接口,尽量去修改,如果修改风险比较大,可以采用适配器模式去转换。

五:迪米特法则

官方定义:迪米特法则又称最少知道原则。(Only talk to your immedate friends,只跟直接的朋友通信)。
解释:出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内的类不属于朋友类;
	如老师让体育委员清点女生的例子,老师跟女生就不是之直接朋友)
总结:迪米特法的核心观点是类之间的解耦则归纳为一下几点:
	1.只和朋友交流
	2.朋友之间也有距离(刺猬取暖)?
	3.自己的就是自己的;如果有一个方法放在哪个类感觉都可以就可以使用如下原则:放在本类既不增加类关系也不会产生负面影响。

六:开闭原则

官方定义:一个类或方法应该对扩展开发,对修改关闭。
解释:软件只要在生命周期内都会发送变化,在设计时尽量适应这些变化,以提高项目的稳定性和灵活性。
总结:开闭原则告诉我们尽量通过扩展实体(类)的行为,而不是通过修改已有的代码来完成。

设计模式分类:

适配器模式(Adapter Pattern)

定义

将一个类的接口变成客户端期望的另一种接口

使用场景

  1. 已经存在的类,他的方法和需求不匹配,
  2. 适配器模式不是软件设计阶段考虑的类,是由于随着软件的发展,不同产品,不同厂家功能类似、而接口不同的情况的解决方案。

总结:
适配器模式的局限性在于
1.原功能与期望的功能的输入参数和返回值要相同,即使不同也要有固定的关联关系
就像适配器经典例子中的电压一样,输入都是插头,输出都是电流。

代码实现

写法一:类适配器

类适配器采用继承的方式实现,暴漏了原方法给客户端,违法了最小职责原则,使用对象适配器能解决这一问题。

//原始类(已经存在的功能)
public class Origin {
    //已经存在的方法
    public int exitsMethod(){
        return 1;
    }
}

//目标类(开发的新功能)
public interface Target {
	// 开发的新方法
    public Boolean expectMethod(String args);
}

//测试
public class Test {
    public static void main(String[] args) {
        Adapter adapter = new Adapter();
        Boolean abc = adapter.expectMethod("abc");
        int i = adapter.exitsMethod(); //两个方法都暴漏给了客户端,不合适
    }
}

// 适配器
public class Adapter extends Origin implements Target {
    @Override
    public Boolean expectMethod(String args) {
        return exitsMethod() == 1;
    }
}

类图:
在这里插入图片描述

写法二:对象适配器

代码:

//原始类(已经存在的功能)
public class Origin {
    //已经存在的方法
    public int exitsMethod(){
        return 1;
    }
}

//目标类(开发的新功能)
public interface Target {

	// 开发的新方法
    public Boolean expectMethod(String args);
}

// 适配器
public class Adapter implements Target {
    private Origin origin;
    public Adapter(Origin origin) {
        this.origin = origin;
    }
    
    @Override
    public Boolean expectMethod(String args) {
        return origin.exitsMethod() == 1;
    }
}

//测试
public class Test {

    public static void main(String[] args) {
        Adapter adapter = new Adapter(new Origin());
        Boolean abc = adapter.expectMethod("abc");
    }
}

类图:
在这里插入图片描述

实际应用例子

需求:
现有登录方式:

  1. 用户名密码登录
    需要新增如下第三方登录功能
  2. 手机号验证码登录
  3. 微信登录
    需要实现不同的厂商的同一登录功能。

代码实现(版本一)

//============= 现有功能代码===start=====

//现有的用户名密码登录
public class PasswordLoginService {
    // 注册
    public Boolean register(String userId,String password){
        return true;
    }
    //用户密码登录
    public Boolean login(String userId,String password){
        return true;
    }
}

//============= 现有功能代码===end=====

//新增第三方登录接口
public interface ThirdPartyLogin {
    /** 手机号密码登录*/
    Boolean loginByPhone(String phone,String code);

    /**微信登录*/
    Boolean loginWechat(String openId);
}

//适配器
public class PasswordForThirdPartyLoginAdapter extends PasswordLoginService implements ThirdPartyLogin{

    @Override
    public Boolean loginByPhone(String phone, String code) {
        //缺少调用运营商校验验证码是否输入正确
        return this.loginForRegister(phone,null);
    }

    @Override
    public Boolean loginWechat(String openId) {
        //缺少调用微信平台,校验openId
        return this.loginForRegister(openId,null);
    }

    private Boolean loginForRegister(String userId,String password){
        if(password == null){
            password = "第三方登录,不需要密码";
        }
        if(super.register(userId,password)){
            super.login(userId,password);
        }
        return true;
    }
}

//测试 
public class Test {

    public static void main(String[] args) {
        PasswordForThirdPartyLoginAdapter adapter = new PasswordForThirdPartyLoginAdapter();
        adapter.login("用户命密码登录","密码");
        adapter.loginByPhone("13822992932","372652");
        adapter.loginWechat("wechatopenid");
    }
}

代码实现(版本二)

版本一有一些缺陷
适配器中缺少第三方的一些校验代码逻辑,实际开发中如果将对接第三方的代码写在这个适配器中将会变得十分臃肿。


// 原有的密码登录
public class PasswordLoginService {
    // 注册
    public Boolean register(String userId,String password){
        return true;
    }
    //用户密码登录
    public Boolean login(String userId,String password){
        return true;
    }
}

public interface ThirdPartyLogin {
    /** 手机号密码登录*/
    Boolean loginByPhone(String phone,String code);
    /**微信登录*/
    Boolean loginWechat(String openId);
}

//登录适配器接口,所有的第三方登录实现改接口
public interface LoginAdapter {
    Boolean support(LoginAdapter adapter);
    Boolean login(String userId,LoginAdapter adapter);
}

//适配器(仅转接,不承接业务)
public class PasswordForThirdPartyLoginAdapter extends PasswordLoginService implements ThirdPartyLogin {
    @Override
    public Boolean loginByPhone(String phone, String code) {
        return handleLogin(phone,new PhoneLoginAdapter());
    }

    @Override
    public Boolean loginWechat(String openId) {
        return handleLogin(openId,new WechatLoginAdapter());
    }

    private Boolean handleLogin(String userId,LoginAdapter loginAdapter){
        if(loginAdapter.support(loginAdapter)){
           return loginAdapter.login(userId,loginAdapter);
        }

        return false;
    }

}

// 抽象登录适配器,作用:抽取公共的注册登录方法
public abstract class AbstractLoginAdapter extends PasswordLoginService implements LoginAdapter {

    protected Boolean loginForRegister(String userId,String password){
        if(password == null){
            password = "第三方登录,不需要密码";
        }
        if(super.register(userId,password)){
            super.login(userId,password);
        }
        return true;
    }
}

//手机登录适配器
public class PhoneLoginAdapter extends AbstractLoginAdapter {
    @Override
    public Boolean support(LoginAdapter adapter) {
        return adapter instanceof PhoneLoginAdapter;
    }

    @Override
    public Boolean login(String userId, LoginAdapter adapter) {
        return super.loginForRegister(userId,null);
    }
}

//微信登录适配器
public class WechatLoginAdapter extends AbstractLoginAdapter {
    @Override
    public Boolean support(LoginAdapter adapter) {
        return adapter instanceof WechatLoginAdapter;
    }

    @Override
    public Boolean login(String userId, LoginAdapter adapter) {
        return super.loginForRegister(userId,null);
    }
}

//测试 
public class Test {

    public static void main(String[] args) {
        PasswordForThirdPartyLoginAdapter adapter = new PasswordForThirdPartyLoginAdapter();
        adapter.login("用户命密码登录","密码");
        adapter.loginByPhone("13822992932","372652");
        adapter.loginWechat("wechatopenid");
    }
}

委派模式(delegate)

定义:

委派模式不属于GOF23种设计模式,是一种行为模式,与门面模式类似,不同的是委派模式是运行中的行为处理逻辑。
委派模式更加注重委派的逻辑,而门面模式是一开始就定义好了的委托对象。

应用场景:

  1. 委派对象本身并不知道处理业务的逻辑,只是把请求交给其他程序来处理。
  2. 实现程序解耦

现实中的例子:

例如一个领导有很多下属,领导会跟进下属熟悉的领域进行安排工作,老板本身不参加具体事务。

优缺点总结:

委派模式能够将一个大型的任务拆分,更够通过管理子任务的执行情况解耦和提升效率。

与代理模式的区别

委派模式与代理模式也有很多相似之处,区别如下:

  1. 委派模式是行为模式,代理模式是结果模式
  2. 委派模式是任务派遣,注重结果;代理模式是代码增强,注重过程。

代码实现

代码

//员工接口
public interface Employee {
    void doSomething(String task);
}
public class EmployeeA implements Employee{
    @Override
    public void doSomething(String task) {
        System.out.println("我擅长写报告...");
    }
}

public class EmployeeB implements Employee{
    @Override
    public void doSomething(String task) {
        System.out.println("我擅长公关...");
    }
}

// 领导跟进员工的特征安排任务
public class Leader implements Employee {
    @Override
    public void doSomething(String task) {
        if("接待".equals(task)){
            new EmployeeB().doSomething(task);
        } else if("汇报".equals(task)){
            new EmployeeA().doSomething(task);
        }
    }
}

// 测试,老板发话给领导安排任务,领导找到员工
public class Boos {
    public void command(String task,Leader leader){
        leader.doSomething(task);
    }
    public static void main(String[] args) {
        new Boos().command("接待",new Leader());
    }
}

类图:
在这里插入图片描述

访问者模式(visitor)

定义

是一种将数据结构与数据操作分离的设计模式

生活中的例子

例如软件公司有若干码农,他们有一个共同的数据结构(姓名,KPI,bug数量,开发的功能,代码行数),对于不同的领导,如直属领导关注开发的功能,代码行数,开发部门经理关注程序员的bug数,公司CEO关注码农的KPI。

结合例子解释概念:

程序员的属性就是一个稳定的数据结构,公司的不同领导就是不同的访问者,他们关注的点(操作)各不相同。

适用场景

  1. 数据结构稳定、作用与数据结构的操作经常变化的操作
  2. 需要数据结构和数据操作分离的场景

代码实现

基础数据结构

#(一 员工抽象)
public abstract class Employee {
    //员工姓名
    private String name;
    //kpi分数
    private int kpi;
    public Employee(String name, int kpi) {
        this.name = name;
        this.kpi = kpi;
    }
    # 提供一个访问者进入的接口
    public abstract void accept(Visitor visitor);
}

#(二 程序员实现)
public class Coder extends Employee {
    public Coder(String name, int kpi) {
        super(name, kpi);
    }
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    /**
     * 产生的bug数
     */
    public Integer bugNumbers(){
        return new Random().nextInt(100);
    }
}
#(三 产品经理实现)
public class Manager extends Employee{

    public Manager(String name, int kpi) {
        super(name, kpi);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    /**
     * 经理考核上线的系统数量
     * @return
     */
    public int systemCount(){
        return new Random().nextInt(10);
    }
}

访问者

# 访问者接口
public interface Visitor {
     // 访问员工
    void visit(Coder employee);

     * 访问经理
     */
    void visit(Manager employee);
}

# 访问者实现一:CTO
public class CTOVisitor implements Visitor {

    @Override
    public void visit(Coder employee) {
        System.out.println("CTO看"+employee.getName()+"的bug数"+employee.bugNumbers());
    }

    @Override
    public void visit(Manager employee) {
        System.out.println("CTO看"+employee.getName()+"的系统上线数"+employee.systemCount());
    }
}

# 访问者实现二:CEO
public class CEOVisitor implements Visitor {

    @Override
    public void visit(Coder employee) {
        System.out.println("CEO看"+employee.getName()+"的KPI"+employee.getKpi());
    }

    @Override
    public void visit(Manager employee) {
        System.out.println("CEO看"+employee.getName()+"的KPI"+employee.getKpi());
    }
}

测试

public class Test {
    private static List<Employee> employeeList = new ArrayList();
    static {
        Coder coder = new Coder("张小开", 90);
        Manager manager = new Manager("李经理", 90);
        employeeList.add(coder);
        employeeList.add(manager);
    }
    public static void main(String[] args) {
        System.out.println("CTO===看员工");
        CTOVisitor ctoVisitor = new CTOVisitor();
        for (Employee employee : employeeList) {
            employee.accept(ctoVisitor);
        }

        System.out.println("CEO=====看员工");
        CEOVisitor ceoVisitor = new CEOVisitor();
        for (Employee employee : employeeList) {
            employee.accept(ceoVisitor);
        }
    }
}

优缺点总结

优点

  1. 解耦的数据结构和数据的相关操作
  2. 随时扩展访问者对数据结构进行不同的数据操作

缺点

  1. 如果数据结构不稳定,需要经常修改或删除则访问者需要做出对于的操作,违背开闭原则。
  2. 违背了依赖倒置原则,访问者依赖具体的对象,而没有依赖抽象。(如上述例子中访问者依赖了程序员、产品经理的具体实现,如果在增加一种员工类型则访问者接口又要增加一种访问类型,所有的访问者实现都需要调整)

总结:

访问者模式是一种比较复杂的设计模式,使用场景较少,数据行为类设计模式。

工厂模式(Factory Pattern)

简单工厂模式(Simple Factory)

定义:

是指由一个工厂根据不同的参数决定创建出那种产品。

使用场景:

  1. 需要创建的对象比较少
  2. 客户端只需要传入工厂类的参数,不需要关心创建对象的逻辑。

优缺点:

优点:只需要传入一个参数就可以获取想要的对象。
缺点:工厂类的职责过重,增加新的对象时需要修改创建工厂类的判断逻辑,违被了开闭原则

代码实现:

需求

根据不同的审批流程渠道获取申请人信息

实现代码:

public class CustomerSimpleFactory {

    public static CustomerInterface getInstance(String taskType){
        if("1".equals(taskType)){
            return new MainCustomerService();
        }else if("2".equals(taskType)){
            return new EleCustomerService();
        }else if("3".equals(taskType)){
            return new PreCustomerService();
        }else if("4".equals(taskType)){
            return new QrCustomerService();
        }
        return null;
    }

    public static CustomerInterface getInstance2(Class<? extends CustomerInterface> clazz){
        try {
            return clazz.newInstance();
        } catch (Exception e) {
        }
        
        return null;
    }
}

工厂方法模式(Factory Method pattern)

定义

定义一个工厂的接口,由子类实现工厂接口来决定创建的对象,工厂方法模式让类的实例推迟到子类工厂中

适用场景

1.创建对象需要由大量的逻辑,(实例化目标类时逻辑比较复杂)
2.预计有较多的新实例需要加入

优缺点

优点:

1.客户不需要知道类具体的创建细节 ,只需要关系返回的结果(与简单工厂一样有这个优点,似乎所有的工厂都是这个优点)
2.方便扩展新产品,符合开闭原则

缺点:

1.类的代码太多,增加了类的结构复杂度,2.增加了系统的抽象复杂度。

代码示例

/**
 * 工厂方法模式
 */
public interface CustomerFactory {

    CustomerInterface getInstance();
}
/** 实现(一) 主流程 */
class MainCustomerFactory implements CustomerFactory{
    @Override
    public CustomerInterface getInstance() {
        return null;
    }
}
/** 实现(二) 预决策 */
class PreCustomerFactory implements CustomerFactory{
    @Override
    public CustomerInterface getInstance() {
        return null;
    }
}

class Test{
    public static void main(String[] args) {
        CustomerFactory factory = new MainCustomerFactory();
        CustomerInterface instance = factory.getInstance();
        
    }
}

抽象工厂(Abstract Factory)

定义

指通过工厂创建一个具有产品,而同一个产品内有多个相互依赖或相关的接口。

比较绕口,举一个实际例子:总共有4个流程,其中每个流程都有获取申请人和担保人的方法。

适用场景

  1. 客户端需要依赖产品的创建细节
  2. 强调一系列相关的产品对象需要一起被创建

优缺点

优点:
1.具体产品在应用层代码隔离,无需关心具体创建细节。
2.将一系列的产品组放在一起创建。(如:主流程申请人和主流程担保人在一起创建)
缺点:
1.产品中扩展新产品困难,需要修改抽象类,如新增一个联系人则需要修改抽象工厂方法,所有的子类都需要实现。
2.类结构复杂,增加了系统的抽象性、复杂度。

代码实现

我们代码实现定义中举的例子:
总共有4个流程,其中每个流程都有获取申请人和担保人的方法。
分析:
1.工厂目标:客户端需要根据自己的需要,获取4个不同的流程产品对象。
2. 每一个产品对象都分别由获取申请人信息和担保人信息的方法(申请人和担保人相关的接口)

代码实现

//客户接口,以及不同流程的实现类
public interface Customer {
}
class MainCustomer implements Customer{
}
class PreCustomer implements Customer{
}
//担保人接口,以及不同流程的实现类
public interface Guarantor {
}

class MainGuarantor implements Guarantor{
}

class PreGuarantor implements Guarantor{
}
// 抽象工厂
public abstract class TaskFactory {
    
    public void init(){
        //一些初始化操作
    }

    public abstract Customer getCustomer();

    public abstract Guarantor getGuarantor();
}

//主流程抽象工厂实现
class MainTaskFactory extends TaskFactory{

    @Override
    public Customer getCustomer() {
        return new MainCustomer();
    }

    @Override
    public Guarantor getGuarantor() {
        return new MainGuarantor();
    }
}

//预批流程工厂实现
class PreTaskFactory extends TaskFactory{
    @Override
    public Customer getCustomer() {
        return new PreCustomer();
    }

    @Override
    public Guarantor getGuarantor() {
        return new PreGuarantor();
    }
}

class Test {
    public static void main(String[] args) {
        TaskFactory factory = new MainTaskFactory();
        Customer customer = factory.getCustomer();
        Guarantor guarantor = factory.getGuarantor();
    }
}

桥接模式(Bridge Pattern)

定义

桥接模式又称双维度扩展模式,实际应用场景不太多。是将抽象部分与具体实现分离,使他们都可以独立变化。通过组合的方式将他们建立联系,而不是通过继承。

适用场景

一个类存在两个或多个独立变化的维度,且这两个维度都需要进行独立扩展。
举例:发送通知的方式有邮件、短信两种方式,而通过发送通知的方式又有加急、普通。

优缺点

优点:

1.分离抽象与具体
2.提高了系统的扩展性
3.复合开闭原则

缺点:

1.增加了系统的复杂度和设计难度
2.需要准确的识别系统中两个独立变化的维度(实际项目中难度较大)

代码实现

上面的例子【发送通知的方式有邮件、短信两种方式,而通过发送通知的方式又有加急、普通。】实现代码

public interface Message {
    void send(String message);
}

class EmailMessage implements Message{
    @Override
    public void send(String message) {
        System.out.println("发送邮件信息:"+message);
    }
}

class PhoneMessage implements Message{
    @Override
    public void send(String message) {
        System.out.println("发送手机短信信息:"+message);
    }
}

//抽象加急、普通短信桥接
public abstract class AbstractMessageBridge  {
    private Message message;
    public AbstractMessageBridge(Message message){
        this.message = message;
    }

    public void send(String message) {
        this.message.send(message);
    }
}
//普通短信
class NormalMessage extends AbstractMessageBridge{
    public NormalMessage(Message message) {
        super(message);
    }

    @Override
    public void send(String message) {
        super.send("[普通]"+message);
    }
}
//加急短信
class UrgentMessage extends AbstractMessageBridge{
    public UrgentMessage(Message message) {
        super(message);
    }

    @Override
    public void send(String message) {
        super.send("[加急]"+message);
    }
}
// 测试
class Test {
    public static void main(String[] args) {
        AbstractMessageBridge message = new UrgentMessage(new EmailMessage());
        message.send("紧急休假");

        AbstractMessageBridge message2 = new NormalMessage(new EmailMessage());
        message2.send("休假");

        AbstractMessageBridge message3 = new NormalMessage(new PhoneMessage());
        message3.send("休假");
    }
}

实际应用

数据库驱动与操作数据的API
在这里插入图片描述

观察者模式(Observer Pattern)

定义

观察者模式又称发布订阅模式(Publish/Subscribe),定义一种一对多的依赖关系,一个主题对象可以被多个观察者同时监听,每当主题对象状态变化时,多依赖的对象都会被通知到。

适用场景

  • 当一个或多个对象的变化依赖另一个对象的变化
  • 实现类似的广播机制,发布者无需知道具体的收听者,感兴趣的对象会自动接受该广播

传统实现

发布主题以及实现

public interface Subject<E> {

    void addObserver(Observer<E> observer);
    /**
     * 通知对象
     * @param event
     */
    void notify(E event);
}

public class NodeCodeSubject implements Subject<String> {
    private List<Observer<String>> observers = new ArrayList<>();
    
    == 通知所有观察者
    @Override
    public void notify(String event) {
        for (Observer<String> observer : observers) {
            observer.update(event);
        }
    }

	== 增加观察者的方法
    @Override
    public void addObserver(Observer<String> observer) {
        observers.add(observer);

    }
}

观察者实现

public interface Observer<E> {
    /**
     * 观察者对象变化
     * @param event
     */
    void update(E event);
}

public class NodeCodeObserver implements Observer<String>{

    @Override
    public void update(String event) {
        System.out.println("监听到节点变化了nodeCode:"+event);
    }
}


测试

public class Test {

    public static void main(String[] args) {
        NodeCodeObserver nodeCodeObserver = new NodeCodeObserver();
        NodeCodeObserver nodeCodeObserver2 = new NodeCodeObserver();

        Subject subject = new NodeCodeSubject();
        subject.addObserver(nodeCodeObserver);
        subject.addObserver(nodeCodeObserver2);

        subject.notify("end");
    }
}

Guava 实现

public class NodeCodeEvent {

    @Subscribe
    public void assignee(String nodeCode){
        System.out.println("监听到nodeCode改变:"+nodeCode);
    }

}

# 测试
public class Test {

    public static void main(String[] args) {

        EventBus eventBus = new EventBus();

        eventBus.register(new NodeCodeEvent());

        eventBus.post("evl");
        eventBus.post("visit");
    }
}

实际应用

监听节点的变化来分别处理不同的逻辑同时满足一个观察者可以监听多个节点的功能,分别实现如:刷新接口,人工派件等功能。

1.定义一个节点信息,用于存储基本信息以及后续扩展

public class NodeInfo {

    private Long caseId;
    private String nodeCode;
 // ignore getting/setting
}

2.定义观察者接口

继承 InitializingBean 为了结合Spring使用,提醒子类主动注册观察者

public interface NodeObserver extends InitializingBean {
    /**
     * 需要监听的节点
     * @return
     */
    List<String> registerCode();

    /**
     * 节点变化
     * @param nodeInfo
     */
    void nodeChange(NodeInfo nodeInfo);

}

3.定义一个节点管理器,用于实现注册和发布方法

public class NodeCodeManager {

	//注册的程序通过Key,Map存储到这里
    private static Map<String,List<NodeObserver>> map = new HashMap<>();

    public static void register(NodeObserver observer){
        for (String nodeCode : observer.registerCode()) {
            List<NodeObserver> list = null;
            if((list = map.get(nodeCode)) == null ){
                list = new ArrayList<>();
            }
            list.add(observer);
            map.put(nodeCode,list);
        }
    }

    /**
     * 节点变化,通知对应的节点
     * @param nodeInfo
     */
    public static void  notifyAll(NodeInfo nodeInfo){
        List<NodeObserver> list = map.get(nodeInfo.getNodeCode());
        if(list != null){
            for (NodeObserver nodeObserver : list) {
                nodeObserver.nodeChange(nodeInfo);
            }
        }
    }
}

4.应用一:实现指定的节点刷新接口

public class RefreshCoreService implements NodeObserver {

    @Override
    public List<String> registerCode() {
    	// 注册这两个节点作为这个类的观察节点,实现同时观察指定的多个接口
        return Arrays.asList("eval","vrf2");
    }

    @Override
    public void nodeChange(NodeInfo nodeInfo) {

        System.out.println("现在的节点是:"+nodeInfo.getNodeCode());
        System.out.println("开始刷新接口.....");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
    	//主动注册本实现作为观察者
        NodeCodeManager.register(this);
    }
}

5. 应用二、派件给对应的人(通过抽象类注册)

public abstract class AbstractNodeCodeObserver implements NodeObserver {

	//这个抽象类的作用是帮助子类注册作为观察者,子类无需在重复定义 
    @Override
    public void afterPropertiesSet() throws Exception {
        NodeCodeManager.register(this);
    }
}

@Service
public class AssigneeNodeObserver extends AbstractNodeCodeObserver {

    @Override
    public List<String> registerCode() {
        return Arrays.asList("vrf","evl","visit");
    }

    @Override
    public void nodeChange(NodeInfo nodeInfo) {
        System.out.println("案件:caseId:"+nodeInfo.getCaseId()+"开始派件:"+nodeInfo.getNodeCode());
    }

}


#测试,使用spring环境

@SpringBootTest
class SpbootApplicationTests {

	@Resource
	private AssigneeNodeObserver assigneeNodeObserver;

	@Test
	public void notifyAllTest() {

		NodeInfo nodeInfo = new NodeInfo();
		nodeInfo.setCaseId(1L);
		nodeInfo.setNodeCode("vrf");
		NodeCodeManager.notifyAll(nodeInfo);

	}

}

解释器模式(interpreter pattern)

定义

是一种按照规定语法进行解析的方案,如给定一个字符串表达式“a+b-c+…”,这种两个或以上的数字进行运算,如何优雅的实现呢?这就是解释器模式要做的事情。
目前在项目中能遇到的场景比较少,如Spring的EL表达式就实现了很多的表达式,包括运算。

例子1(标准版)

实现上述例子代码较多,见源码:https://chenglj.coding.net/public/spboot/spboot/git/files/master/src/main/java/org/chenglj/spboot/design/interpreter

例子2(简化版本)

上面的例子结构清晰,实际业务算法比较复杂的话优先使用该例子,但似乎有点类爆炸,在调用方式完全不变的情况下可以简化成2个类,具体见代码。

采用的是函数式接口的方式,用新增方法替换新增类。

1.新增一个函数式接口,和例子1是一样的,只不过不去实现该接口而已

@FunctionalInterface
public interface ExpressionFunction {
    int interpreter(Map<String,Integer> context);
}

2.实现方法

public class Calculator2 {

    private ExpressionFunction expressionFunction;

    public int interpreter(String key,Map<String,Integer> context) {
        return context.get(key);
    }

    public int add(ExpressionFunction left, ExpressionFunction right, Map<String,Integer> context) {
        return left.interpreter(context) + right.interpreter(context);
    }

    public int sub(ExpressionFunction left, ExpressionFunction right, Map<String,Integer> context) {
        return left.interpreter(context) - right.interpreter(context);
    }

    //后续加减乘除等运算只需要添加方法即可,不需要添加类

    public Calculator2(String expression) {
        char[] exprArray = expression.toCharArray();
        Stack<ExpressionFunction> stack = new Stack<>();
        for (int i = 0; i < exprArray.length; i++) {
            char expr = exprArray[i];

            if('+'== expr) {
                int z = ++i;
                ExpressionFunction function = (x) -> add(stack.pop(),(c) -> interpreter(String.valueOf(exprArray[z]),c),x);
                stack.add(function);
            } else if('-' == expr) {
                int z = ++i;
                ExpressionFunction function = (x) -> sub(stack.pop(),(c) -> interpreter(String.valueOf(exprArray[z]),c),x);
                stack.add(function);
            } else {
                int j = i;
                stack.add((x) -> interpreter(String.valueOf(exprArray[j]),x));
            }

        }

        this.expressionFunction = stack.pop();

    }

    public int getValue(Map<String,Integer> context) {
        return expressionFunction.interpreter(context);
    }

}

3.测试

public class Client {

    public static void main(String[] args) {
        String expression = "a+b-c-d";
        Map<String,Integer> context = new HashMap<>();

        context.put("a",11);
        context.put("b",6);
        context.put("c",3);
        context.put("d",7);

        System.out.println(new Calculator2(expression).getValue(context));

    }
}

源码分享:例子2源码

例子3(没必要版)

在例子2的基础还还可以简化一个类,使用JDK提供原生的Function<T,R>替换ExpressionFunction,不过这种方案不推荐。

public class Calculator3 {

    private Function<Map<String,Integer>,Integer> function;

    public int interpreter(String key,Map<String,Integer> context) {
        return context.get(key);
    }

    public int add(Function<Map<String,Integer>,Integer> left
            ,Function<Map<String,Integer>,Integer> right,Map<String,Integer> context) {
        return left.apply(context) + right.apply(context);
    }

    public int sub(Function<Map<String,Integer>,Integer> left,
                   Function<Map<String,Integer>,Integer> right,
                   Map<String,Integer> context) {
        return left.apply(context) - right.apply(context);
    }

    //后续加减乘除等运算只需要添加方法即可,不需要添加类

    public Calculator3(String expression) {
        char[] exprArray = expression.toCharArray();
        Stack<Function<Map<String,Integer>,Integer>> stack = new Stack<>();
        for (int i = 0; i < exprArray.length; i++) {
            char expr = exprArray[i];

            if('+'== expr) {
                int z = ++i;
                Function<Map<String,Integer>,Integer> function =
                        (x) -> add(stack.pop(),(c) -> interpreter(String.valueOf(exprArray[z]),c),x);
                stack.add(function);
            } else if('-' == expr) {
                int z = ++i;
                Function<Map<String,Integer>,Integer> function =
                        (x) -> sub(stack.pop(),(c) -> interpreter(String.valueOf(exprArray[z]),c),x);
                stack.add(function);
            } else {
                int j = i;
                stack.add((x) -> interpreter(String.valueOf(exprArray[j]),x));
            }

        }

        this.function = stack.pop();
    }

    public int getValue(Map<String,Integer> context) {
        return function.apply(context);
    }

}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 桥接模式适配器模式都是结构型设计模式,主要用于处理不同类之间的接口问题,但它们的适用场景和解决问题的方式略有不同。 相同点: 1. 都是为了解决两个不兼容的接口之间的问题; 2. 都使用组合关系将两个不同的类连接起来; 3. 都可以提高代码的可扩展性和复用性。 不同点: 1. 定义不同:桥接模式是为了让接口和实现部分分离,以便它们可以独立地变化。适配器模式是为了将一个类的接口转换成客户端所期望的另一个接口。 2. 用途不同:桥接模式通常用于抽象类和具体类之间的接口,而适配器模式通常用于将现有类的接口转换为其他客户端所期望的接口。 3. 实现方式不同:桥接模式使用组合关系将两个不同的类连接起来,并且这两个类可以独立地扩展适配器模式使用继承和组合的方式,将一个类的接口转换成另一个接口。 总的来说,桥接模式主要是为了解决抽象类和具体类之间的接口问题,而适配器模式主要是为了解决现有类与客户端所期望的接口不一致的问题。 ### 回答2: 桥接模式适配器模式作为常见的设计模式,都用于解决不同类之间的接口不兼容的问题,但是它们在解决问题的方式和应用场景上有一些不同。 首先,桥接模式的主要目标是将抽象与实现分离,使它们能够独立地变化。在桥接模式中,抽象和实现是通过一个桥接接口连接起来的。这种模式可以在运行时动态地选择具体的实现,而不会对客户端代码造成任何影响。桥接模式常用于系统中存在多个独立变化的维度时,可以将这些维度分离开来,从而提供更好的灵活性和可扩展性。 而适配器模式的主要目标是将一个类的接口转换成客户端所期望的另一个接口。适配器模式主要通过创建一个适配器类,将客户端的调用转换为被适配类的相应调用来实现。适配器模式常用于集成已有的组件或类库时,可以通过适配器来适配不同的接口,无需修改原有的代码。 两种模式的相同点是都能够解决接口不兼容的问题,都是通过创建一个中间层来实现接口转换。而不同点在于桥接模式更重视抽象和实现的分离,可以将不同维度的变化独立起来,提供更好的扩展性;适配器模式则更重视在已有代码的基础上进行接口转换,将已有代码和新接口进行适配,保证代码的兼容性。 综上所述,桥接模式适配器模式在解决接口不兼容问题时具有一定的相似性,但在具体的应用场景和解决问题的方式上有所不同。 ### 回答3: 桥接模式适配器模式是两种常见的设计模式,它们都属于结构型模式,用于处理不同接口之间的交互。下面我来介绍一下这两种模式的异同。 异同之处: 1. 定义:桥接模式(Bridge Pattern)是一种将抽象和实现解耦的设计模式,使得它们可以独立地变化。适配器模式(Adapter Pattern)将一个类的接口转换成客户希望的另一个接口。 2. 目的:桥接模式的目的是将抽象部分与实现部分分离,使它们可以独立地变化。适配器模式的目的是让两个不兼容的接口可以一起工作。 3. 用途:桥接模式通常用于抽象接口和实现之间存在多对多关系的情况,适配器模式通常用于将一个类的接口转换成另一个类的接口。 4. 实现方式:桥接模式使用组合关系将抽象部分和实现部分分离,通过桥接接口将它们连接起来。适配器模式通过继承或者对象组合的方式来适配不同的接口。 5. 关注点:桥接模式侧重于对抽象和实现进行解耦,注重抽象部分的稳定性和可扩展性。适配器模式侧重于将一个接口转换成另一个接口,注重两个接口的兼容性和一致性。 6. 灵活性:桥接模式可以在运行时动态切换抽象和实现的具体实现,使系统更加灵活。适配器模式在编译时或者运行时进行适配,不具备动态切换的能力。 总结起来,桥接模式适配器模式都是用于处理不同接口之间的交互,但是目的和实现方式略有不同。桥接模式关注于抽象接口和实现的解耦,适配器模式关注于接口之间的适配转换。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值