设计模式
阅读提示
设计模式题不要“背定义大全”,建议挑 3 个自己项目中用过的模式重点讲。
推荐优先:策略、责任链、代理,并准备一个落地案例。
设计模式(选几个自己熟悉的深入了解一下,是怎样实现的和在项目中是怎么使用的)
1、Java的动态代理如何实现?
参考回答:
Java中的动态代理是一种在运行时动态生成代理类及其对象的技术。它主要用于实现AOP(面向切面编程)的思想,允许你在不修改原始类代码的情况下,增加新的功能或行为。动态代理通常用于实现接口,通过接口定义业务方法,并在运行时动态为接口生成实现类。
Java中的动态代理指的是在运行时动态创建代理类和对象的机制,它允许开发者在运行时确定代理类的行为。实现动态代理主要有以下两种方式:
使用JDK提供的Proxy类和InvocationHandler接口: 通过实现InvocationHandler接口创建自己的调用处理器,然后使用Proxy类的静态方法newProxyInstance()创建代理对象。JDK代理是通过实现接口的方式实现的。
使用CGLIB库:CGLIB是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成,CGLIB通过继承方式实现代理。
2、静态代理和动态代理的区别?
参考回答:
- 静态代理是在编译期确定的,而动态代理是在运行期确定的。静态代理需要手动编写代理类的代码,可能会导致代码冗余和繁琐,特别是在代理的类方法较多或需要同时代理多个对象的情况下,这会增加不必要的复杂性。
- 动态代理是通过在运行时生成代理对象,避免了手动编写代理类的繁琐过程。这使得动态代理更加灵活,尤其适用于需要代理的类方法较多或动态代理多个对象的情况。动态代理的实现更加自动化,能够在运行期根据需要生成代理,减少了手动编写代理类的工作,提高了代码的可维护性和灵活性。
3、设计模式的基本原则?
参考回答:
面向对象的基本原则主要包括以下几个核心原则:
单一职责原则****(Single Responsibility Principle, SRP):一个类最好只做一件事
单一职责原则是指一个类应该只负责一个明确的职责或功能。它通过将不同的职责分离到不同的类中,提高了代码的可读性、可维护性和可扩展性,并降低了系统的耦合性。
开放封闭原则****(Open-Close Principle, OCP): 对扩展开放、对修改封闭
开放封闭原则是指软件实体应该对扩展开放,对修改封闭。它通过扩展现有的代码来实现新功能,而不是直接修改已有的代码。遵循开放封闭原则可以提高代码的可维护性和可扩展性,降低系统的耦合性。
里氏替换原则****(Liskov Substitution Principle, LSP):子类必须能够替换其基类
里氏替换原则是指任何基类的实例都可以在程序中被其子类的实例替换,而不会影响程序的正确性。遵循里氏替换原则可以提高代码的可靠性、可扩展性和可维护性。
依赖倒置原则****(Dependency Inversion Principle, DIP):程序要依赖于抽象接口,而不是具体的实现
依赖倒置原则是指高层模块不应该依赖于低层模块,它们应该通过抽象来互相依赖。遵循依赖倒置原则可以提高代码的灵活性、可扩展性、可维护性和可测试性。
接口隔离原则****(Interface Segregation Principle, ISP):将庞大的接口拆分成更小的、更具体的接口
接口隔离原则是指接口应该尽可能小,客户端不应该依赖它不需要的接口。遵循接口隔离原则可以实现高内聚、低耦合的代码设计,提高代码的可维护性、可扩展性和可测试性。
4、各种设计模式介绍(了解)
创建型模式(5种)
- 工厂方法模式(Factory Method):定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
- 抽象工厂模式(Abstract Factory):提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
- 单例模式(Singleton):确保一个类只有一个实例,并提供一个全局访问点。
- 建造者模式(Builder):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- 原型模式(Prototype):用原型实例指定创建对象的种类,并且通过复制这些原型来创建新的对象。
结构型模式(7种)
- 适配器模式(Adapter):将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而无法一起工作的类可以一起工作。
- 装饰器模式(Decorator):动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更为灵活。
- 代理模式(Proxy):为其他对象提供一个代理以控制对这个对象的访问。
- 外观模式(Facade):为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
- 桥接模式(Bridge):将抽象部分与它的实现部分分离,使它们可以独立地变化。
- 组合模式(Composite):将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。
- 享元模式(Flyweight):运用共享技术有效地支持大量细粒度的对象。
行为型模式(11种)
- 策略模式(Strategy):定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。策略模式使得算法可以独立于使用它的客户而变化。
- 模板方法模式(Template Method):定义一个操作中的算法的框架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
- 观察者模式(Observer):定义对象之间的一对多依赖关系,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
- 迭代器模式(Iterator):提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
- 责任链模式(Chain of Responsibility):使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
- 命令模式(Command):将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
- 状态模式(State):允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
- 备忘录模式(Memento):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
- 访问者模式(Visitor):表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
- 中介者模式(Mediator):用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
- 解释器模式(Interpreter):给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
5、责任链模式
责任链模式的设计目的是为了解耦请求发送者与多个接收者之间的关系。它通过将多个接收者组成一条链,并沿着这条链传递请求,直到有一个接收者处理请求,从而实现了灵活的请求处理机制。
在责任链模式中,处理请求的对象通常被称为处理器或链的节点。每个节点包含了处理请求的逻辑以及指向下一个节点的引用。当请求到达一个节点时,如果该节点无法处理请求,它会将请求转发给下一个节点,直到有一个节点处理请求或整个链都无法处理。
责任链模式在实际开发中有广泛的应用场景,例如:
过滤器链:在Web开发中,可以通过责任链模式实现过滤器链,例如Spring框架中的FilterChain。每个过滤器都有机会处理请求,直到最后一个过滤器完成处理。
日志记录器:日志系统中可以利用责任链模式将多个日志记录器组成一条链,以实现不同日志记录方式的灵活组合。
异常处理器:应用程序中可以使用责任链模式实现异常处理器的链式调用,以灵活地处理各种异常情况。
授权认证:系统中可以利用责任链模式实现授权认证的链式调用,以灵活地控制不同用户对系统的访问权限。
6、状态模式
状态模式允许对象在其内部状态发生变化时改变行为,使其看起来像是修改了其类结构。这通过将对象的行为封装在不同状态对象中实现,在运行时能够动态改变对象的状态,从而影响其行为。
实际应用中,状态模式有多种场景,如:
订单状态管理:订单状态包括未付款、已付款、已发货、已签收等。不同状态下,订单的行为也会有所不同。
游戏角色状态:游戏角色可能处于待机、行走、攻击、受伤等不同状态。不同状态下,角色表现出不同的行为。
音视频播放器:音视频播放器状态包括播放、暂停、停止、快进、快退等。不同状态下,播放器的行为也会有所差异。
在实际应用中,状态模式通常需要与其他设计模式结合使用,如工厂模式、单例模式、策略模式等,以实现更灵活和高效的代码设计。这种组合能够提供更好的扩展性和维护性,使系统更易于理解和修改。
7、策略模式
策略模式是一种行为设计模式,它允许在运行时根据不同情况选择不同算法的实现。该模式将算法及其相应的行为封装在独立的类中,使得它们可以相互替换而不影响客户端的使用。策略模式遵循开闭原则,即可以在不修改现有客户端代码的情况下,动态地添加、删除或替换算法。
相较于使用if-else语句,策略模式具有以下优势:
易于扩展:策略模式方便地支持增加、删除或更换算法,只需添加新的策略类而无需修改原有代码。
更好的可读性:将复杂的条件语句分散到不同的策略类中,使代码更清晰、易于理解和维护。
避免大量条件判断:在if-else语句中,条件判断可能会变得复杂难以维护,而策略模式可以将条件判断分散到各个策略类中,简化代码。
提高代码复用性:将常用算法封装在策略类中,可以被多个客户端共享使用,提高代码的复用性。
在实际应用中,策略模式通常与工厂模式、模板方法模式等结合使用,以实现更灵活、可维护的代码设计。
8、观察者模式
观察者模式是一种行为设计模式,它建立了对象之间的一对多依赖关系。当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新,从而实现了松耦合。被观察者对象无需知道观察者的具体实现细节,只需通知它们即可。
观察者模式在许多应用场景中都具有广泛应用,特别是在需要处理对象之间松耦合、实时通知和更新的情况下。以下是观察者模式常见的应用场景:
发布-订阅系统: 观察者模式是发布-订阅模式的核心。发布者发布消息或事件时,所有订阅者都会收到通知并执行相应操作。
事件处理机制: 用于处理事件驱动的编程,事件触发时通知事件处理程序(观察者)执行相应操作。
实时数据更新: 在需要实时更新数据的应用中,将数据源与数据消费者连接起来。数据源变化时,观察者可以获取最新数据并进行处理。
库和框架: 许多编程库和框架使用观察者模式来支持插件和扩展。开发人员可以编写自定义观察者以响应库或框架中的事件或回调。
消息队列系统: 用于消息队列系统,生产者将消息发送到队列,而消费者作为观察者订阅队列接收和处理消息。
股票市场监测: 股票市场应用程序使用观察者模式监测股票价格变化,并通知投资者。
游戏开发: 用于处理各种游戏事件,如玩家输入、碰撞检测、角色状态变化等。
网络通信: 在网络应用中,观察者模式可用于实现即时通信系统,实现用户之间的消息传递。
9、代理模式
代理模式是一种结构设计模式,通过创建代理对象来控制对其他对象的访问。代理对象充当原始对象的接口,客户端通过代理对象间接地访问原始对象,并有机会在访问过程中添加额外的逻辑或控制。
代理模式的主要目的是引入代理对象,为原始对象提供一层间接访问方式,以实现对原始对象的控制、保护或增强。常见的应用场景包括:
远程代理: 在分布式系统中,代理模式可用于代理远程对象,隐藏了远程对象的实际实现细节,使客户端可以像访问本地对象一样访问远程对象。例如,Dubbo框架使用了代理模式的概念。
动态代理: 允许在运行时动态创建代理对象,并将方法调用动态分派到不同的处理器。通过Java的反射机制实现,可用于实现通用的代理逻辑,避免为每个被代理的类单独创建代理。Spring的AOP就是一个使用动态代理的典型例子。
缓存代理: 用于缓存原始对象的结果,以避免重复计算或访问资源。代理对象首先查询缓存,如果缓存中不存在,则访问真实对象。这种模式在需要优化访问性能的场景中很常见。
日志代理: 用于记录统一的日志信息,通过创建代理对象,在代理中进行日志记录和管理。这在需要对系统进行日志记录时非常有用。
异常代理: 用于处理系统中的统一异常机制或ERROR_CODE机制。通过创建一个统一的代理,可以在代理对象中捕捉和转换异常,实现异常的集中处理。
代理模式在以上场景中发挥重要作用,通过适当的代理对象,能够实现对原始对象的控制和增强,同时保持客户端对对象的透明性。
10、模版方法
- 模板方法模式是一种行为设计模式,其主要目的在于提高代码的复用性。在许多情况下,代码中可能存在一些共享的核心逻辑和一些需要定制的部分。模板方法模式通过将这些共享的部分定义在父类中,而将具体的实现交给子类,实现了对算法结构的保持稳定,同时允许子类根据需求灵活扩展或重写父类的方法。
- 通常情况下,模板方法模式与策略模式一起使用,因为在使用策略模式时,我们将具体的策略实现放置在策略服务中,而一些通用的逻辑部分仍然存在。通过模板方法模式,可以实现这些通用逻辑的复用,使得代码更加清晰和可维护。这种组合使用的方式能够充分利用两种设计模式的优势,提高系统的灵活性和可扩展性。
