代码写的烂,经常被同事怼,教你一招!-程序员宅基地

技术标签: 面试  java  编程语言  数据库  设计模式  

面对复杂的业务场景,千变万化的客户需求,如何以一变应万变,以最小的开发成本快速落地实现,同时保证系统有着较低的复杂度,能够保证系统后续de持续迭代能力,让系统拥有较高的可扩展性。

这些是一个合格的架构师必须修炼的基础内功,但是如何修炼这门神功???

我将常用的软件设计模式,做了汇总,目录如下:

(考虑到内容篇幅较大,为了便于大家阅读,将软件设计模式系列(共23个)拆分成四篇文章,每篇文章讲解六个设计模式,采用不同的颜色区分,便于快速消化记忆)

本文是主要讲解桥接模式组合模式装饰模式门面模式代理模式责任链模式

1、桥接模式

自然界一般由实体和行为组成。当然为了提升系统的扩展性,它们两个又可以各自抽象,然后在抽象类中描述两者的依赖。

定义:

将抽象部分与它的实现部分分离,使它们都可以独立地变化。

什么场景使用桥接模式?

  • 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展。

  • 对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

核心思路:

  • 抽象实体:定义的一种抽象分类。比如:人

  • 具体实体:继承抽象实体的子类实体。比如:中国人、美国人、韩国人

  • 抽象行为:定义抽象实体中具备的多种行为。比如:学汉语、吃汉堡

  • 具体行为:实现抽象行为的具体算法。比如:中国人学汉语、美国人吃汉堡

代码示例:

/**
 * @author 微信公众号:微观技术
 * 抽象实体
 */
public abstract class AbstractEntity {
    protected AbstractBehavior abstractBehavior;

    public AbstractEntity(AbstractBehavior abstractBehavior) {
        this.abstractBehavior = abstractBehavior;
    }

    public abstract void out();

}

/**
 * 抽象行为
 */
public interface AbstractBehavior {

    public String action(String name);
}

/**
 * 关于食物的行为
 */
public class FoodBehavior implements AbstractBehavior {

    @Override
    public String action(String name) {
        if ("中国人".equals(name)) {
            return "吃 饺子";
        } else if ("美国人".equals(name)) {
            return "吃 汉堡";
        }
        return null;
    }
}

桥接模式是将抽象与抽象之间分离,具体实现类依赖于抽象。抽象的分离间接完成了具体类与具体类之间的解耦,它们之间使用抽象来进行组合或聚合,而不再靠多重继承来实现。本质是将一个对象的实体和行为分离,然后再基于这两个维度进行独立的演化。

适用场景:

  • 拆分复杂的类对象时。当一个类中包含大量对象和方法时,既不方便阅读,也不方便修改。

  • 希望从多个独立维度上扩展时。比如,系统功能性和非功能性角度,业务或技术角度等。

  • 运行时,组合不同的组件

2、组合模式

定义:

组合模式也称整体模式,把一组相似的对象当作一个单一的对象,然后将对象组合成树形结构以表示整个层次结构。

这里边有两个关键点:1、树形结构分层  2、业务统一化来简化操作

核心思路:

  • 抽象组件(AbstractNode):定义需要实现的统一操作。

  • 组合节点(CompositeNode):抽象组件的衍生子类,包含了若干孩子节点(其它组合节点或叶子节点)。

  • 叶子节点(LeafNode):抽象组件的子类,但它的下面没有子节点。

代码示例:

public abstract class AbstractNode {
    public abstract void add(AbstractNode abstractNode);
    public abstract void remove(AbstractNode abstractNode);
    public abstract void action();
}

public class CompositeNode extends AbstractNode {
    private Long nodeId;
    private List<AbstractNode> childNodes;  //存放子节点列表
    public CompositeNode(Long nodeId, List<AbstractNode> childNodes) {
        this.nodeId = nodeId;
        this.childNodes = childNodes;
    }
    @Override
    public void add(AbstractNode abstractNode) {
        childNodes.add(abstractNode);
    }
    @Override
    public void remove(AbstractNode abstractNode) {
        childNodes.remove(abstractNode);
    }
    @Override
    public void action() {
        for (AbstractNode childNode : childNodes) {
            childNode.action();
        }
    }
}

public class LeafNode extends AbstractNode {
    private Long nodeId;
    public LeafNode(Long nodeId) {
        this.nodeId = nodeId;
    }
    @Override
    public void add(AbstractNode abstractNode) {
        // 无子节点,无需处理
        return;
    }
    @Override
    public void remove(AbstractNode abstractNode) {
        // 无子节点,无需处理
        return;
    }
    @Override
    public void action() {
        System.out.println("叶子节点编号:" + nodeId);
    }
}

叶子节点不能新增、删除子节点,所以对应的方法为空。

组合模式本质上封装了复杂结构的内在变化,让使用者通过一个统一的整体来使用对象之间的结构。数据结构方面支持树形结构环形结构网状结构。如我们常见的 深度优先搜索广度优先搜索都是采用这种模式。

适用场景:

  • 一组对象按照某种层级结构进行管理。如:管理文件夹和文件,管理订单下的商品。

  • 需要按照统一的行为来处理复杂结构中的对象

  • 快速扩展对象组合。

手机开始是按品牌来归属分类,现在业务增加价格维度分类,我们只需要引入新的分支节点,按新的维度构建组合关系。

3、装饰模式

定义:

动态地向一个现有对象添加新的职责和行为,同时又不改变其结构,相当于对现有的对象进行包装。

核心思路:

  • 抽象组件(Component):装饰器基类,定义组件的基本功能

  • 具体组件(ConcreteComponent):抽象组件的具体实现

  • 抽象装饰器(Decorator):包含抽象组件的引用

  • 具体装饰器(ConcreteDecorator):抽象装饰器的子类,并重写组件接口方法,同时可以添加附加功能。

代码示例:

public abstract class Component {
    public abstract void execute();
}

public class ConcreteComponent extends Component {
    @Override
    public void execute() {
        System.out.println("具体子类 ConcreteComponent invoke !");
    }
}

public class Decorator extends Component {
    protected Component component;
    public Decorator(Component component) {
        this.component = component;
    }
    @Override
    public void execute() {
        component.execute();
    }
}

public class ConcreteDecorator extends Decorator {
    public ConcreteDecorator(Component component) {
        super(component);
    }
    @Override
    public void execute() {
        System.out.println("装饰器子类 ConcreteDecorator invoke !");
        super.execute();
    }
}

装饰模式本质上就是给已有不可修改的类附加新的功能,同时还能很方便地撤销。

适用场景:

  • 无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的功能

  • 将业务逻辑组织为层次结构,可以为各层创建一个装饰,在运行时将各种不同逻辑组合成对象。由于这些对象都遵循通用接口,客户端代码能以相同的方式使用这些对象。

  • 不支持继承扩展类的场景。如:final 关键字限制了某个类的进一步扩展,可以通过装饰器对其进行封装,从而具备扩展能力。

4、门面模式

定义:

门面模式提供一个高层次的接口,要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行,使得子系统更易于使用。

门面模式要求我们使用统一的标准与系统交互,比如:我们打印日志基本会选择slf4j框架,其内部统一了log4jlog4j2CommonLog等日志框架,简化了我们的开发成本。

核心思路:

  • 门面系统。接收外部请求,并将请求转发给适当的子系统进行处理

  • 子系统。表示某个领域内的功能实现、或者具体子接口实现,比如,订单、支付等,专门处理由门面系统指派的任务。

简单来讲,引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口。

可能很多人有疑问,这个不就是代理模式吗?

门面模式可能代理的是多个接口,而代理模式通常只是代理一个接口。

业务场景:

移动互联网,我们都习惯了在线支付,相信很多人在付款时都听过这么一句话,”微信支付还是支付宝“,商户根据用户反馈再针对性选择收款渠道。

是不是很繁琐,为了解决这个问题,市面就有了聚合支付(该领域做非常棒的是收钱吧),整个业务模式就是这节要讲的门面模式,不管你用什么软件支付,只要打开付款二维码即可,收钱吧底层识别解析二维码,并根据扫描结果自动适配对应的收款渠道,完成用户的扣款动作,确实带来不错的用户体验。

优点:

  • 简化复杂系统,提供统一接口规范。比如:JPA提供了统一Java持久层API,底层适配多样化的存储系统。

  • 复杂的业务逻辑由内部子系统消化,只要对外接口规范不变,外部调用方不需要频繁修改

  • 扩展性较好,类似于SPI架构一样,支持水平扩展。

  • 较高的平滑过渡性。比如:我们要对老的系统架构升级,开发一系列新接口来替换原来的老接口,过渡期需要新老灰度测试、流量切换、平滑升级,可以采用该模式。门面模式在兼容多套系统、系统重构方面是把利器。

5、代理模式

定义:

为其他对象提供一种代理以控制对这个对象的访问

现实场景:

  • 房产中介

  • 包工头

核心思路:

  • 抽象主题类(AbstractSubject):定义接口方法,供客户端使用

  • 主题实现类(RealSubject):实现了抽象主题类的接口方法

  • 代理类(Proxy):实现了抽象主题类的接口方法,内部包含主题实现类的逻辑, 同时还包含一些自身的扩展操作。

代理模式与适配器模式相似。但适配器模式是转换为新的接口,而代理模式不会改变原有接口。

代码示例:

/**
 * @author 微信公众号:微观技术
 */
public interface AbstractSubject {
    void execute();
}

public class RealSubject implements AbstractSubject {
    @Override
    public void execute() {
        System.out.println("我是Tom哥,我要努力工作!");
    }
}

public class Proxy implements AbstractSubject {

    private AbstractSubject abstractSubject;

    public Proxy(AbstractSubject abstractSubject) {
        this.abstractSubject = abstractSubject;
    }

    @Override
    public void execute() {
        System.out.println("老板给Tom哥分配工作了。。。");
        abstractSubject.execute();
    }
}

按使用职责分为静态代理和动态代理。

  • 静态代理,代理类需要自己编写代码完成。

  • 动态代理,代理类通过 Proxy#newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法生成。

  • JDK实现的代理中不管是静态代理还是动态代理,都是面向接口编程。CGLib可以不限制一定是接口。

优点:

  • 职责清晰

  • 高扩展,只要实现了接口,都可以用代理

  • 智能化,动态代理

  • 降低了对象的直接耦合

适用场景:

  • 远程代理。无法直接操作远程对象。比如:Dubbo、gRPC,提供远程服务,客户端调用时需要走参数组装、序列化、网络传输等操作,这些通用逻辑都可以封装到代理中,客户端调用代理对象访问远程服务,就像调用本地对象一样方便。

  • 保护代理。当客户端通过代理对象访问原始对象时,代理对象会根据规则判断客户端是否有权限访问。比如:防火墙

  • 日志代理。比如:日志监控,正常业务访问时,调用代理,增加一些额外的日志记录功能。

  • 虚拟代理,适用于延迟初始化,用小对象表示大对象的场景,减少资源损耗,提升运行速度。

  • 不希望改变原对象,但需要增加类似于权限控制、日志、流控等附加功能时,可以使用代理模式。

6、责任链模式

定义:

责任链模式是一种行为设计模式,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链。收到请求后,每个处理者均可对请求进行处理,或将其传递给链中的下个处理者。

责任链模式是对数据结构中的链表结构的具体应用。

核心思路:

  • 抽象处理者(Handler):定义一个接口,内部包含处理方法和下一个节点的引用对象

  • 具体处理者(ConcreteHandler):抽象处理者的实现子类,判断本次请求是否处理,如果需要则处理,否则跳过,然后将请求转发给下一个节点。

优点:

  • 降低了对象之间的耦合度。链上各个节点各司其职,通过上下文传递数据,避免直接依赖。

  • 增强系统的可扩展性。如果有新的业务需求,只需要在合适的位置增加一个链节点即可,满足开闭原则。

  • 灵活性强。如果业务有变化,需要对工作流程做调整,只需要动态调整链上节点的次序即可。甚至为了满足多元化业务的多样化需求,我们可以为不同的业务类型定义自己的专属执行顺序。

  • 简化了对象之间的连接。每个对象只需保存下一个节点的引用,而不需保持所有节点。

  • 责任明确。每个节点只需处理自己的工作,如果不处理则传递给下一个对象。明确各类的责任范围,符合类的单一职责原则。

像我们常见的网关架构推荐使用该模式,通过服务编排,可以自由地在任意位置添加或移除节点,满足一系列个性化功能。

写在最后


设计模式很多人都学习过,但项目实战时总是晕晕乎乎,原因在于没有了解其核心是什么,底层逻辑是什么,《设计模式:可复用面向对象的基础》有讲过,

在设计中思考什么应该变化,并封装会发生变化的概念。

软件架构的精髓:找到变化,封装变化。

业务千变万化,没有固定的编码答案,千万不要硬套设计模式。无论选择哪一种设计模式,尽量要能满足SOLID原则,自我review是否满足业务的持续扩展性。有句话说的好,“不论白猫黑猫,能抓老鼠就是好猫。”

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/g6U8W7p06dCO99fQ3/article/details/118532937

智能推荐

oracle partition的方法-程序员宅基地

文章浏览阅读606次。本文章的目的 了解Oracle Partition的使用和技巧本文對象 具有Oracle的基礎知識,具有一定的Oracle管理知識的DBA管理人員系統環境說明操作系統 , Win2..._oracle insert into partition

SpringBoot事务-REQUIRES_NEW 事务属性陷阱-程序员宅基地

文章浏览阅读9.1k次,点赞3次,收藏6次。REQUIRES_NEW 事务属性陷阱不管是使用 Spring Framework,还是使用 EJB,使用 REQUIRES_NEW 事务属性都会得到不好的结果并导致数据损坏和不一致。REQUIRES_NEW 事务属性总是会在启动方法时启动一个新的事务。许多开发人员都错误地使用 REQUIRES_NEW 属性,认为它是确保事务启动的正确方法。考虑清单 11 中的两个方法:清单 11. 使用..._requires_new

各模组相关interface_ar模块interface-程序员宅基地

文章浏览阅读507次。AP接口表:AP_INVOICES_INTERFACEAP_INVOICE_LINES_INTERFACE涉及的请求:应付款管理系统开放接口导入涉及案例: 运费导AP、费用导APPO接口表:申请:PO_REQUISITIONS_INTERFACE_ALL涉及请求:导入申请采购:po_headers_interfacepo_lines_interf_ar模块interface

深入 理解Statement 和 PreparedStatement_statement、preparedstatement-程序员宅基地

文章浏览阅读3.7k次。一、使用Statement而不是PreparedStatement对象JDBC驱动的最佳化是基于使用的是什么功能. 选择PreparedStatement还是Statement取决于你要怎么使用它们. 对于只执行一次的SQL语句选择Statement是最好的. 相反, 如果SQL语句被多次执行选用PreparedStatement是最好的.PreparedStatement的第一次执行消耗是很..._statement、preparedstatement

字典如何遍历_字典怎么遍历-程序员宅基地

文章浏览阅读1w次,点赞8次,收藏23次。a={'a':'1','b':'2','c':'3'}1. 遍历key值for key in a: pritn(key+':'+a[key]) for key in a.keys(): print(key+':'+a[key])2.遍历value值for value in a.values(): print(value)3.遍历字典项..._字典怎么遍历

python标准库模块_gsignal.c:2641: instance 'x55854ba4cfoo' has no ha-程序员宅基地

文章浏览阅读2k次。06/07 20:10:08 编译0.1. 关于本书 0.2. 代码约定 0.3. 关于例子 0.4. 如何联系我们 核心模块1.1. 介绍 1.2. _ _builtin_ _ 模块 1.3. exceptions 模块 1.4. os 模块 1.5. os.path 模块 1.6. stat 模块 1.7. st..._gsignal.c:2641: instance 'x55854ba4cfoo' has no handler with id

随便推点

Html5把页面分成左右两,把页面分成左右两个,如何实现在左侧点击,链接一个页面到右边的页面...-程序员宅基地

文章浏览阅读3.5k次。问:请问jsp页面用框架网页把页面分成左右两个,怎么实现在左侧点击,链接一个页面到右边的页面jsp页面用框架网页()把页面分成左右两个页面,怎么实现在左侧点击,链接一个页面到右边的页面?另外,为什么我在jsp页面里面设置onmouseover事件,不管用呢?--------------------解决方案--------------------起个名字 target = "name"linkfra..._html怎么分成左右两块

VS2015社区版、企业版、专业版下载官网地址_vs2015官网-程序员宅基地

文章浏览阅读5.9k次,点赞20次,收藏20次。VS2015 专业版下载链接http://download.microsoft.com/download/B/8/9/B898E46E-CBAE-4045-A8E2-2D33DD36F3C4/vs2015.pro_chs.isoVS2015 企业版下载链http://download.microsoft.com/download/B/8/F/B8F1470D-2396-4E7A-83F5-AC09154EB925/vs2015.ent_chs.isoVS2015 社区版下载链接http://d_vs2015官网

SQL查询语句关键字的执行顺序_sql中查询关键字的顺序-程序员宅基地

文章浏览阅读214次。SQL查询语句关键字的执行顺序1)Mysql语法顺序:select ->from ->join ->on ->where ->group by ->having ->union ->order by ->imit2)MySQL语句执行顺序from ->on ->join ->where ->group by ->having ->select ->distinct ->_sql中查询关键字的顺序

小白学习python-安装-程序员宅基地

文章浏览阅读145次。工作需要,学习了python!绝对好使 转载:https://blog.csdn.net/nmjuzi/article/details/79075736

VRChat火了,但VR社交还没迎来最好的时代-程序员宅基地

文章浏览阅读516次。谁也没想到,VR社交是通过视频博主在中国火起来的。九月份微博上一位二次元领域博主发布了一段视频,内容是在一款名为《VRChat》的游戏中,两名外国人莫名其妙地用中文交流起..._vrchat进去之后又要我重新进入

hdoj 蟠桃记_hdoj蟠桃-程序员宅基地

文章浏览阅读423次。蟠桃记 Time Limit : 2000/1000ms (Java/Other) Memory Limit : 65536/32768K (Java/Other)Total Submission(s) : 58 Accepted Submission(s) : 39Font: Times New Roman | Verdana | Georgia Font Size: ←_hdoj蟠桃

推荐文章

热门文章

相关标签