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


    设计模式不应该停留于理论,跟具体业务结合,它才会变得更香~

    1.前言

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

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

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

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

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

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

    2.理解设计模式

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

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

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

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

    • 开闭原则(首要原则):要对扩展开放,对修改关闭
    • 单一职责原则:实现类要职责单一
    • 里氏代换原则:不要破坏继承体系
    • 依赖倒转原则:面向接口编程
    • 接口隔离原则:设计接口要精简单一
    • 合成/聚合复用原则:尽量先使用组合或者聚合来实现,其次才考虑使用继承关系来实现
    • 最少知识原则或者迪米特法则:降低耦合

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

    3.设计模式实战案例

    3.1工厂模式

    1)工厂模式介绍

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

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

     

    工厂模式的优点很明显:

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

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

    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();
        }
    }

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

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

     

    3.2 代理模式

    1) 模式介绍

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

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

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

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

     

    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) 模式介绍

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

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

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

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

     

    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就行了。

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

     

    4.总结

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

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

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

    其实很简单。

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

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

    看到这里了,原创不易,点个关注、点个赞吧,你最好看了~

    知识碎片重新梳理,构建Java知识图谱:https://github.com/saigu/JavaKnowledgeGraph(历史文章查阅非常方便)

    扫码关注我的公众号“阿丸笔记”,第一时间获取最新更新。同时可以免费获取海量Java技术栈电子书、各个大厂面试题。

    阿丸笔记

  • 相关阅读:
    其他
    聚类算法:ISODATA算法
    大神博客
    Fiddldr 教程之:HTTP协议详解(转)
    设计模式之装饰模式的复习
    NOIP 2011 聪明的质监员
    CSP-S2020/NOIP2020模板总结(Updating)
    CSP-S2020/NOIP2020复习指南
    洛谷 U137412 高斯的小宇宙
    NOIP2020模板测试题大全
  • 原文地址:https://www.cnblogs.com/awan-note/p/12555872.html
Copyright © 2020-2023  润新知