面试官问,你在开发中有用过什么设计模式吗?我懵了

简介: 面试官问,你在开发中有用过什么设计模式吗?我懵了

1.前言


设计模式我们多少都有些了解,但是往往也只是知道是什么。


在真实的业务场景中,你有用过什么设计模式来编写更优雅的代码吗?


我们更多的是每天从产品经理那里接受到新需求后,就开始MVC一把梭,面向sql编程了。


我们习惯采用MVC架构,实时上是非常容易创建很多贫血对象模型,然后写出过程式代码。我们使用的对象,往往只是数据的载体,没有任何逻辑行为。我们的设计过程,也是从ER图开始,以数据为中心进行驱动设计。一个需求一个接口,从controller到service到dao,这样日复一日的CRUD。


什么设计模式?根本不存在的!


今天,我们尝试从常用设计模式(工厂模式、代理模式、模版模式)在CRUD中的可落地场景,希望能给大家带来一些启发。


2.理解设计模式


设计模式(Design pattern),不是前人凭空想象的,而是在长期的软件设计实践过程中,经过总结得到的。


使用设计模式是为了让代码具有可扩展性,实现高聚合、低耦合的特性。


世上本来没有设计模式,写代码的人多了,便有了设计模式。


面向对象的设计模式有七大基本原则:


  • 开闭原则(Open Closed Principle,OCP)
  • 单一职责原则(Single Responsibility Principle, SRP)
  • 里氏代换原则(Liskov Substitution Principle,LSP)
  • 依赖倒转原则(Dependency Inversion Principle,DIP)
  • 接口隔离原则(Interface Segregation Principle,ISP)
  • 合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)
  • 最少知识原则(Least Knowledge Principle,LKP)或者迪米特法则(Law of Demeter,LOD)


简单理解就是:开闭原则是总纲,它指导我们要对扩展开放,对修改关闭;单一职责原则指导我们实现类要职责单一;里氏替换原则指导我们不要破坏继承体系;依赖倒置原则指导我们要面向接口编程;接口隔离原则指导我们在设计接口的时候要精简单一;迪米特法则指导我们要降低耦合。


过去,我们会去学习设计模式的理论,今天,我们尝试从常用设计模式(工厂模式、代理模式、模版模式)在CRUD中的可落地场景,希望能给大家带来一些实战启发。


3.设计模式实战案例


3.1工厂模式


1)工厂模式介绍


工厂模式应该是我们最熟悉的设计模式了,很多框架都会有经典的xxxxFactory,然后通过xxxFactory.create来获取对象。这里不详细展开介绍,给出一个大家耳熟能详的工厂模式类图应该就能回忆起来了。


50.jpg


工厂模式的优点很明显:


  • 一个调用者想创建一个对象,只要知道其名称就可以了。
  • 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
  • 屏蔽产品的具体实现,调用者只关心产品的接口。


那么,实际业务开发该怎么落地使用呢?


2)需求举例


我们需要做一个HBase的管理系统,类似于MySQL的navicat或者workbench。

那么有一个很重要的模块,就是实现HBase的增删改查。

而开源的HBase-client已经提供了标准的增删改查的api,我们如何集成到系统中呢?


3)简单代码


@RestController("/hbase/execute")
public class DemoController {
    private HBaseExecuteService hbaseExecuteService;
    public DemoController(ExecuteService executeService) {
        this.hbaseExecuteService = executeService;
    }
    @PostMapping("/insert")
    public void insertDate(InsertCondition insertCondition) {
        hbaseExecuteService.insertDate(insertCondition);
    }
    @PostMapping("/update")
    public void updateDate(UpdateCondition updateCondition) {
        hbaseExecuteService.updateDate(updateCondition;
    }
    @PostMapping("/delete")
    public void deleteDate(DeleteCondition deleteCondition) {
        hbaseExecuteService.deleteData(deleteCondition);
    }
    @GetMapping("/select")
    public Object selectDate(SelectCondition selectCondition) {
        return hbaseExecuteService.seletData(selectCondition);
    }
}


每次增加一个功能,都需要从controller到service写一遍类似的操作。


还需要构建很多相关dto进行数据传递,里面会带着很多重复的变量,比如表名、列名等查询条件。


4)模式应用


抽象接口


public interface HBaseCommand {
    /**
     * 执行命令
     */
    ExecResult execute();
}


抽象类实现公共配置


public class AbstractHBaseCommand implements HBaseCommand {
    Configuration configuration;
    AbstractHBaseCommand(ExecuteCondition executeCondition) {
        this.configuration = getConfiguration(executeCondition.getResourceId());
    }
    private Configuration getConfiguration(String resourceId) {
        Configuration conf = HBaseConfiguration.create();
        //做一些配置相关事情
        //。。。。
        return conf;
    }
    @Override
    public ExecResult execute() {
       return null;
    }
}


工厂类生产具体的命令


public class CommandFactory {
    private ExecuteCondition executeCondition;
    public CommandFactory(ExecuteCondition executeCondition) {
        this.executeCondition = executeCondition;
    }
    public HBaseCommand create() {
        HBaseCommand hBaseCommand;
        switch (ExecuteTypeEnum.getTypeForName(executeCondition.getQueryType())) {
            case Get:
                return new GetCommand(executeCondition);
            case Put:
                return new PutCommand(executeCondition);
            case Delete:
                return new DeleteCommand(executeCondition);
        }
        return null;
    }
}


一个执行接口,执行增删改查多个命令


public class ExecuteController {
    private ExecuteService executeService;
    public ExecuteController(ExecuteService executeService) {
        this.executeService = executeService;
    }
    @GetMapping
    public Object exec(ExecuteCondition executeCondition) {
        ExecResult execResult = executeService.execute(executeCondition);
        return transform(execResult);
    }
}


service调用工厂来创建具体的命令进行执行


@Service
public class ExecuteService {
    public ExecResult execute(ExecuteCondition executeCondition) {
        CommandFactory factory = new CommandFactory(executeCondition);
        HBaseCommand command = factory.create();
        return command.execute();
    }
}


每次添加一个新的命令,只需要实现一个新的命令相关内容的类即可

51.jpg


3.2 代理模式


1) 模式介绍


代理模式也是大家非常熟悉的一种模式。


它给一个对象提供一个代理,并由代理对象控制对原对象的引用。它使得用户不能直接与真正的目标对象通信。


代理对象类似于客户端和目标对象之间的中介,能发挥比较多作用,比如扩展原对象的能力、做一些切面工作(打日志)、限制原对象的能力,同时也在一定程度上面减少了系统的耦合度。


52.jpg


2)需求举例


现在已经有一个client对象,实现了若干方法。


现在,有两个需求:


  • 希望这个对象的方法A不再被支持。
  • 希望对这个client的所有方法的执行时间进行记录。


3)简单代码


对原本的类进行修改


  • 删除方法A(不兼容改动,如果别人有引用,可能会编译报错)
  • 对每个方法的前后埋点计算时间(业务入侵太大,代码严重冗余)


4)模式应用


对于方法A的不再支持,其实有挺多办法的,继承或者静态代理都可以。


静态代理代码:


public class ConnectionProxy implements Connection {
    private Connection connection;
    public ConnectionProxy(Connection connection) {
        this.connection = connection;
    }
    @Override
    public Admin getAdmin() throws IOException {
        //抛出一个异常
        throw new UnsupportedOperationException();
    }
    @Override
    public boolean isClosed() {
        return connection.isClosed();
    }
    @Override
    public void abort(String why, Throwable e) {
        connection.abort(why, e);
    }
}


对于每个方法的前后计算埋点,可以使用动态代理进行实现。


public class TableProxy implements InvocationHandler {
    private Object target;
    public TableProxy(Object target) {
        this.target = target;
    }
    /**
     * 获取被代理接口实例对象
     *
     * @param <T>
     * @return
     */
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long current = System.currentTimeMillis();
        Object invoke;
        try {
            invoke = method.invoke(target, args);
        } catch (Throwable throwable) {
            throw throwable;
        }
        long cost = System.currentTimeMillis() - current;
        System.out.println("cost time: " + cost);
        return invoke;
    }
}


3.3 模板方法


1) 模式介绍


定义一个操作中的流程框架,而将一些流程中的具体步骤延迟到子类中实现。


模板方法使得子类可以不改变一个流程框架下,通过重定义该算法的某些特定步骤实现自定义的行为。


当然,最便利之处在于,我们可以保证一套完善的流程,使得不同子类明确知道自己需要实现哪些方法来完成这套流程。


53.jpg


2)需求举例


其实模板方法是最容易理解的,也非常高效。


我们最常用模版方法的一类需求就是工单审批流。


具体来说,假如我们现在需要定义一套流程来实现一个工单审批,包含工单创建、审批操作、事件执行、消息通知等流程(实际上流程可能会更加复杂)。


而工单的对象非常多,可以是一个服务的申请、一个数据库的变更申请、或者是一个权限申请。


3)简单代码


每个工单流程写一套代码。


  • 重复工作多;
  • 流程某些关键环节可能会缺失,比如事件执行以后忘记通知了。


4)模式应用


定义一个接口,里面包括了若干方法


public interface ChangeFlow {
    void createOrder();
    boolean approval();
    boolean execute();
    void notice();
}


在一个流程模版中,拼接各个方法,实现完整工作流


public class MainFlow {
    public void mainFlow(ChangeFlow flow) {
        flow.createOrder();
        if (!flow.approval()){
            System.out.println("抱歉,审批没有通过");
        }
        if (!flow.execute()) {
            System.out.println("抱歉,执行失败");
        }
        flow.notice();
    }
}


然后,可以在AbstractChangeFlow里面实现通用的方法,比如approval、notice,大家都是一样的逻辑。


public class AbstractChangeFlow implements ChangeFlow {
    @Override
    public void createOrder() {
        System.out.println("创建订单");
    }
    @Override
    public boolean approval() {
        if (xxx) {
            System.out.println("审批通过");
            return true;
        }
        return false;
    }
    @Override
    public boolean execute() {
        //交给其他子类自己复写
        return true;
    }
    @Override
    public void notice() {
        System.out.println("notice");
    }
}


最后,就根据具体的工单来实现自定义的excute()方法就行了,实现DbFlow、MainFlow就行了。

54.jpg


4.总结


学习设计模式最重要的就是要在业务开发过程中保持思考,在某一个特定的业务场景中,结合对业务场景的理解和领域模型的建立,才能体会到设计模式思想的精髓。


如果脱离具体的业务场景去学习或者谈论设计模式,那是没有意义的。


有人说,怎么区分设计模式和过度设计呢?


其实很简单。


1)业务设计初期。如果非常熟悉业务特性,理解业务迭代方向,那么就可以做一些简单的设计了。


2)业务迭代过程中。当你的代码随着业务的调整需要不断动刀改动,破坏了设计模式的七大原则,尤其是开闭原则,那么,你就该去考虑考虑使用设计模式了。


目录
相关文章
|
3月前
|
设计模式 算法 搜索推荐
后端开发中的设计模式应用与实践
在软件开发的广袤天地中,后端技术如同构筑高楼大厦的钢筋水泥,支撑起整个应用程序的骨架。本文旨在通过深入浅出的方式,探讨后端开发领域内不可或缺的设计模式,这些模式犹如精雕细琢的工具箱,能够助力开发者打造出既健壮又灵活的系统架构。从单例模式到工厂模式,从观察者模式到策略模式,每一种设计模式都蕴含着深刻的哲理与实践价值,它们不仅仅是代码的组织方式,更是解决复杂问题的智慧结晶。
|
3月前
|
存储 安全 Java
每日大厂面试题大汇总 —— 今日的是“美团-后端开发-一面”
文章汇总了美团后端开发一面的面试题目,内容涉及哈希表、HashMap、二叉树遍历、数据库索引、死锁、事务隔离级别、Java对象相等性、多态、线程池拒绝策略、CAS、设计模式、Spring事务传播机制及RPC序列化工具等。
72 0
|
4月前
|
设计模式 算法 搜索推荐
后端开发中的设计模式应用
在软件开发的浩瀚海洋中,设计模式犹如一座座灯塔,为后端开发者指引方向。本文将深入探讨后端开发中常见的设计模式,并通过实例展示如何在实际项目中巧妙应用这些模式,以提升代码的可维护性、扩展性和复用性。通过阅读本文,您将能够更加自信地应对复杂后端系统的设计与实现挑战。
80 3
|
4月前
|
设计模式 安全 数据库连接
后端开发中的设计模式应用
在软件开发的浩瀚海洋中,设计模式如同灯塔,为后端开发者指引方向。它们不仅仅是代码的模板,更是解决复杂问题的智慧结晶。本文将深入探讨几种常见的设计模式,包括单例模式、工厂模式和观察者模式,并揭示它们在实际应用中如何提升代码的可维护性、扩展性和重用性。通过实例分析,我们将一窥这些模式如何在后端开发中大放异彩,助力构建高效、灵活的软件系统。
|
3月前
|
存储 消息中间件 NoSQL
每日大厂面试题大汇总 —— 今日的是“京东-后端开发-一面”
文章汇总了京东后端开发一面的面试题目,包括ArrayList与LinkedList的区别、HashMap的数据结构和操作、线程安全问题、线程池参数、MySQL存储引擎、Redis性能和线程模型、分布式锁处理、HTTP与HTTPS、Kafka等方面的问题。
163 0
|
3月前
|
设计模式 缓存 Java
面试题:谈谈Spring用到了哪些设计模式?
面试题:谈谈Spring用到了哪些设计模式?
|
3月前
|
SQL 安全 关系型数据库
第三次面试总结 - 吉云集团 - 全栈开发
本文是作者对吉云集团全栈开发岗位的第三次面试总结,面试结果非常好,内容全面覆盖了Java基础、MySQL和项目经验,作者认为自己的MySQL基础知识稍弱,需要加强。
44 0
第三次面试总结 - 吉云集团 - 全栈开发
|
4月前
|
设计模式 算法 搜索推荐
后端开发中的设计模式应用
在软件开发的浩瀚海洋中,设计模式犹如灯塔一般指引着方向。它们不是一成不变的规则,而是前人智慧的结晶。本文将深入探讨几种在后端开发中常用的设计模式,如单例、工厂、观察者和策略模式,并阐述如何在实际项目中灵活运用这些模式来提升代码质量、可维护性和扩展性。通过对比传统开发方式与应用设计模式后的差异,我们将揭示设计模式在解决复杂问题和优化系统架构中的独特价值。
|
4月前
|
设计模式 JavaScript Java
后端开发中的设计模式应用
本文将深入探讨后端开发中常见的设计模式,包括单例模式、工厂模式和观察者模式。每种模式不仅会介绍其定义和结构,还会结合实际案例展示如何在后端开发中应用这些模式来优化代码的可维护性与扩展性。通过对比传统方法与设计模式的应用,读者可以更清晰地理解设计模式的优势,并学会在项目中灵活运用这些模式解决实际问题。
|
3月前
|
消息中间件 存储 前端开发
资深Android开发的5个经典面试题
本文首发于公众号“AntDream”,欢迎关注。文章详细解答了五个常见的Android面试题,涵盖内存泄漏与溢出、Binder机制、MVC/MVP/MVVM架构、Handler机制及Context对象等内容,帮助读者深入了解Android开发的核心概念。
57 0