• 设计模式题目


    策略模式

    为什么要使用设计模式

    1. 重用设计,重用设计比重用代码更有意义,它会自动带来代码的重用
    2. 为设计提供共同的词汇,每个模式名就是一个设计词汇,其概念使的程序员间的交流更加方便。
    3. 在开发文档中采用模式词汇可以让其他人更容易理解你的想法和做法,编写开发文档也更方便。
    4. 应用设计模式可以让重构系统变得容易,可以确保开发正确的代码,并降低在设计或实现中出现的错误的可能。
    5. 支持变化,可以为重写其他应用程序提供很好的系统架构。
    6. 正确设计模式,可以节省大量时间。

    什么是策略模式?策略模式应用场景?

    其实策略模式就是对算法的包装,是把算法的责任和算法本身分割开来,委派给不同的对象管理,最终实现解决多重if判断问题。如果在一个系统中有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为(算法),也就是上面所说解决了多重if判断问题。
    使用场景:

    1. 相信很多开发者或多或少都接触过erp系统开发,在进行流程审核时,可以选择不同的类型来达到最终的审核结果。比如有工作汇报、KPI/OKR、微创新等审核流程。
    2. 说说我们平时的聚合支付吧,有支付宝支付、小米支付、京东支付和微信支付等等,大大小小的平台,这时作为开发者的我们要对接那么多的平台,要写那么多的if else if ,想想就难受,还是使用策略模式来解决该问题吧。

    请使用画图工具,画一个策略模式流程图

    在这里插入图片描述

    策略模式实现方式有哪些?

    第一种:通过接口来实现,不同的类通过实现这个接口实现不同的功能,最后存到map中(手动注册),在使用的时候按需要来获取不同的接口实现类对象来执行方法。
    第二种:实现InitializingBean接口(自动注册),策略类实现InitializingBean接口,并自动注册。该实现类必须要有@Component、@Service注解,否则afterPropertiesSet方法将不起作用! 因为只有bean被初始化时,才会执行afterPropertiesSet方法!!!
    第三种:基于SpringBoot实现策略模式

    策略Context上下文与策略的关系

    上下文这个类,你可以把公用的代码写在该类中。上层的调用需要与接口之间有一定的交互。交互的可能是一些属性,或是一些方法。这样的交互往往会让接口变的难以调用;于是上下文的引入就是势在必行。将相关的属性或一些公共的方法封装到上下文中,让上下文去和接口进行复杂的交互。而上层的调用只需要跟上下文打交道就可以。

    策略模式的优缺点有哪些?

    优点:

    1. 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法和行为,也可以灵活的增加 新的算法和行为。
    2. 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码转移到父类里面,从而避免重复的代码;
    3. 策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能;
    4. 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。

    缺点:

    1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。

    模版方法

    什么是模版方法设计模式?模版方法设计应用场景有哪些?

    定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的。重复代码全部在父类里面,不同业务的,使用抽象方法,抽取给子类进行实现。抽取过程—抽象方法。就是将一些相同操作的代码,封装成一个算法的骨架。核心的部分留在子类中操作,在父类中只把那些骨架做好。

    应用场景:数据库访问的封装、Junit单元测试、servlet中关于doGet()/doPost()方法的调用

    模版方法与策略模式的区别?

    策略模式核心在使用者的策略。 如果按照自己的策略去替换。模板方法模式核心在子类的怎么实现。

    模版方案设计模式优缺点有哪些?

    优点:

    1. 模板方法模式是通过把不变的行为挪到一个统一的父类,从而达到去除子类中重复代码的目的、
    2. 子类实现模板父类的某些细节,有助于模板父类的扩展
    3. 通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”

    缺点:
    按照设计习惯,抽象类负责声明最抽象、最一般的事物属性和方法,实现类负责完成具体的事务属性和方法,但是模板方式正好相反,子类执行的结果影响了父类的结果,会增加代码阅读的难度

    责任链模式

    什么是责任链模式,画出责任链流程图

    当一个请求可能需要被多个对象处理时,我们可以将这些处理对象链成一条链,并在这条链上传递该请求,直到该请求被处理完毕,这种设计模式就叫做责任链设计模式。
    在这里插入图片描述

    责任链模式应用场景

    多个对象可以处理同一请求,但具体由哪个对象处理则在运行时动态决定;
    在不明确指定接收者的情况下,向多个对象中的一个提交一个请求;
    可动态指定一组对象处理请求;

    责任链的底层实现原理

    责任链设计模式将每个处理请求的对象都当成链上的一个节点,这些对象(节点)中都有一个处理请求的方法,每个节点又可以设置它的下一节点,从而形成一条处理链,在本节点处理请求之后可以选择是否将请求继续传递给下一个节点进行处理。

    责任链模式的优缺点?

    优点:
    降低耦合度。它将请求的发送者和接收者解耦
    简化了对象,使得对象不需要知道链的结构
    增强给对象指派职责的灵活性,允许动态地新增或者删除责任链
    增加新的请求处理类方便

    缺点:
    不能保证请求一定被接收;
    系统性能将受到一定影响,调试时不方便,可能会造成循环调用

    装饰模式

    什么是装饰模式?装饰模式应用场景

    装饰模式,是面向对象编程领域中,一种动态地往一个类中添加新的行为的设计模式。就功能而言,装饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能。

    应用场景:

    1. 需要扩展一个类的功能。
    2. 动态的为一个对象增加功能,而且还能动态撤销。(继承不能做到这一点,继承的功能是静态的,不能动态增删。)

    请画出装饰模式的流程图

    答:

    装饰模式底层实现原理

    就是你拿一个类A来封装另外一个类B,但没有继承关系,
    这两个类可实现相通的接口,在A中的每个方法调用B的对应方法来实现逻辑,
    并在调用前后加入自己的处理来改变方法的行为。

    装饰模式的优缺点?

    优点:

    1. 装饰器是继承的有力补充,比继承灵活,不改变原有对象的情况下动态对对象扩展功能,即插即用。
    2. 通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。
    3. 装饰器完全遂守开闭原则。

    缺点:

    1. 动态装饰时,多层装饰时会更复杂。
    2. 会出现更多的代码,更多的类,増加程序复杂性。

    代理模式

    为什么需要使用代理(Proxy)模式,应用场景

    除了当前类能够提供的功能外,我们还需要补充一些其他功能。最容易想到的情况就是权限过滤,我有一个类做某项业务,但是由于安全原因只有某些用户才可以调用这个类,此时我们就可以做一个该类的代理类,要求所有请求必须通过该代理类,由该代理类做权限判断,如果安全则调用实际类的业务开始处理。可能有人说为什么我要多加个代理类?我只需要在原来类的方法里面加上权限过滤不就完了吗?在程序设计中有一个类的单一性原则问题,这个原则很简单,就是每个类的功能尽可能单一。为什么要单一,因为只有功能单一这个类被改动的可能性才会最小,就拿刚才的例子来说,如果你将权限判断放在当前类里面,当前这个类就既要负责自己本身业务逻辑、又要负责权限判断,那么就有两个导致该类变化的原因,现在如果权限规则一旦变化,这个类就必需得改,显然这不是一个好的设计。

    应用场景:

    1. 日志的采集
    2. 权限控制
    3. 实现aop
    4. Mybatis mapper
    5. Spring的事务
    6. 全局捕获异常
    7. Rpc远程调用接口
    8. 分布式事务原理代理数据源

    代理模式实现的方式有哪些?

    静态代理:实例和代理类都实现了同一个接口,不管传递什么实例进代理类中都能调用方法,不需要在每个新生成的实例中重复写某一个方法。

    jdk动态代理:Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:
    (1) Interface InvocationHandler:该接口中仅定义了一个方法
    public objectinvoke(Object obj, Methond method, Object[] args)在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,args为该方法的参数数组。
    (2) Proxy:该类即为动态代理类
    static ObjectnewProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类在接口中声明过的方法)

    CGLIB动态代理:cglib也是动态代理,不同于proxy,它不需要基于接口进行代理,利用asm字节码技术,生成子类实现对目标方法实现增强

    Jdk与Cglib动态代理的区别?

    1. Jdk动态代理利用反射技术生成匿名的代理类走InvokeHandler回调方法实现增强,同时也是一种基于接口的方式实现代理。
    2. Cglib动态代理利用asm字节码技术生成一个子类覆盖其中的方法实现增强
    3. 在Spring中如果需要被代理的对象如果实现了接口采用Jdk动态代理,没有实现接口则使用Cglib动态代理。

    静态代理与动态代理区别?

    静态代理:由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

    动态代理:动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

    区别:动态代理不需要写代理类对象,通过程序自动生成,而静态代理需要我们自己写代理类对象。

    动态代理实现方式有哪些?

    第一种:JDK动态代理
    实现步骤:
    1.创建被代理的接口和类;
    2.实现InvocationHandler接口,对目标接口中声明的所有方法进行统一处理;
    3.调用Proxy的静态方法,创建代理类并生成相应的代理对象;

    第二种:CGLIB动态代理
    利用asm字节码技术,生成子类实现对目标方法实现增强

    纯手写JDK动态代理的思路

    1. 创建代理类$Proxy0源代码文件实现被代理的接口。

    2. 使用JavaCompiler技术编译该 Proxy 0 文 件 获 取 到 Proxy0文件获取到 Proxy0文件获取到Proxy0.class

    3. 使用ClassLoader将该$Proxy0.class加入到当前JVM内存中。
      ClassLoader 顾名思义就是类加载器。ClassLoader 作用:负责将 Class 加载到 JVM 中,审查每个类由谁加载(父优先的等级加载机制),将 Class 字节码重新解析成 JVM 统一要求的对象格式。

    代理模式的优缺点

    优点:
    代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度;
    代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了保护目标对象的作用;

    缺点:
    由于在客户端和真实对象之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢;
    实现代理模式需要额外的工作,有些代理模式的实现非常复杂;
    静态代理在委托类变多的情况时会显的非常臃肿,不方便阅读与使用;

    观察者模式

    什么是观察者模式?

    观察者模式(Observer),又叫发布-订阅模式(Publish/Subscribe),定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。

    观察者模式应用场景有哪些?

    关联行为场景、事件多级触发场景、跨系统的消息变换场景,如消息队列的处理机制。

    业务使用场景:
    手机丢了,委托别人给其他人发消息通知
    通知老师/老板来了
    拍卖,拍卖师观察最高标价,然后通知给其它竞价者竞价
    在一个目录下建立一个文件,会同时通知目录管理器增加目录,并通知磁盘减少空间,文件是被观察者,目录管理器和磁盘管理器是观察者
    猫叫了一声,吓着了老鼠,也惊到了主人,猫是被观察者,老鼠和人是观察者

    Jdk中实现观察者模式 Observable 与ObServer区别?

    Observer接口
    Observer为java.util包下的一个接口,源码如下:

    public interface Observer {
        void update(Observable o, Object arg);
    }
    
    • 1
    • 2
    • 3

    该接口约定了观察者的行为。所有观察者需要在被观察对象发生变化时做出相应的反应,所做的具体反应就是实现Observer接口的update方法,实现update方法时你可以用到两个参数,一个参数的类型是Observable,另一个参数的类型是Object。当然如果完全由自己去实现一个观察者模式的方案,自己去设计Observer接口时,可能不会设计这两个参数。那为什么jdk设计该接口时规定接口中有这两个参数呢?那就是通用性。想想整个观察者模式有哪些类之间需要交互?使用该模式时牵扯三个类,一个是观察者,一个是被观察对象,一个是调用者(调用者可以是被观察对象本身调用,更多情况是一个具体的业务类),当前接口代表观察者,要与被观察对象交互,因此update方法需要持有被观察对象(Observable)的引用,第一参数产生了;如何与调用者通信,则是添加了类型为Object的参数(该参数是调用者调用Observable实例的notifyObservers(Object obj)方法时传入的,当然也可以不传);第一个参数可以说是为观察者提供了一种拉取数据的方式,update中的业务可以根据所需去拉去自己想要的被观察对象的信息(一般被观察对象中提供getter),第二个参数则是由调用者调用notifyObservers(Object obj)将一些信息推过来。通过这两个参数,观察者,被观察对象,调用者(调用通知刷新方法的可能是被观察对象本身,此时只存在观察者与被观察者两者)三者就联系起来了。

    Observable类
    Observable同样是java.util包下的接口(理所当然的事),该类的成员变量和方法如下:

    public class Observable {
        private boolean changed = false;
        private Vector<Observer> obs;
        public Observable(){};
        protected synchronized void setChanged(){};
        protected synchronized void clearChanged(){};
        public synchronized void addObserver(Observer o){};
        public synchronized void deleteObserver(Observer o) {};
        public synchronized void deleteObservers(){};
        public synchronized boolean hasChanged(){};
        public synchronized int countObservers(){};
        public void notifyObservers(){};
        public void notifyObservers(Object arg){};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    说说成员变量:
    1)该类中含有一个boolean型的变量changed,代表是否发生改变了,Observable类只提供这个boolean值来表明是否发生变化,而不定义什么叫变化,因为每个业务中对变化的具体定义不一样,因此子类自己来判断是否变化;该变量既提供了一种抽象(变与不变),同时提供了一种观察者更新状态的可延迟加载,通过后面的notifyObservers方法分析可知观察者是否会调用update方法,依赖于changed变量,因此即使被观察者在逻辑上发生改变了,只要不调用setChanged,update是不会被调用的。如果我们在某些业务场景不需要频繁触发update,则可以适时调用setChanged方法来延迟刷新。

    2)该类含有一个集合类Vector,该类泛型为Observer,主要用来存放所有观察自己的对象的引用,以便在更新时可以挨个遍历集合中的观察者,逐个调用update方法
    说明:
    1.8的jdk源码为Vector,有版本的源码是ArrayList的集合实现;
    Vector这个类和ArrayList的继承体系是一致,主要有两点不同,一是Vector是线程安全的,ArrayList不是线程安全的,Vector的操作依靠在方法上加了同步关键字来保证线程安全,与此同时ArrayList的性能是要好于Vector的;二是Vector和ArrayList扩容阀值不太一样,ArrayList较Vector更节省空间;

    说说方法:
    1)操作changed变量的方法为setChanged(),clearChanged(),hasChanged();见名知意,第一个设置变化状态,第二清除变化状态,这两个的访问权限都是protected,表明这两个方法由子类去调用,由子类来告诉什么时候被观察者发生变化了,什么时候变化消失,而hasChanged()方法的访问权限是公有的,调用者可以使用该方法。三个方法都有同步关键字保证变量的读写操作线程安全。

    2)操作Vector类型变量obs的方法为addObserver(Observer o), deleteObserver(Observer o), deleteObservers(),countObservers(),这四个方法分别实现了动态添加观察者,删除观察者,删除所有观察者,获取观察者数量。四个方法的访问权限都是公有的,这是提供给调用者的方法,让调用者来实时动态的控制哪些观察者来观察该被观察对象。

    3)操作Vector型变量obs的四个方法都加有同步关键字,但是我们刚才分析成员属性Vector obs这个变量时,说Vector类型为线程安全的,而上述四个方法为什么还要加同步关键字呢,这是怎么回事?据我推测应该是程序员重构遗留问题吧,因为前面我说道,有历史版本的源码是使用的ArrayList来持有Observer的引用,而ArrayList不是线程安全的,所以上述四个操作结合的方法需要加上同步关键字来保证线程安全,而后来换成线程安全的Vector了,但这四个操作集合的方法依旧保留了同步关键字。

    4)两个对外的方法notifyObservers(),notifyObservers(Object arg),该方法由调用者来操作,用来通知所有的观察者需要做更新操作了。

    观察者模式底层的实现原理

    在这里插入图片描述

    四个角色

    • 抽象主题(Subject):提供接口,可以增加和剔除观察者对象。一般用抽象类或者接口实现。
    • 抽象观察者(Observer):提供接口,在得到主题的通知时更新自己。一般用抽象类或者接口实现。
    • 具体主题(ConcreteSubject):将有关状态存入具体观察者,在具体主题的内部状态发生变化时,给所有注册过的观察者发出通知。一般是具体子类实现。
    • 具体观察者(ConcreteObserver):存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态 像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用

    在上述类图中,ConcreteSubject中有一个存储Observer的列表,这意味着ConcreteSubject并不需要知道引用了哪些ConcreteObserver,只要实现(继承)了Observer的对象都可以存到该列表中。在需要的时候调用Observer的update方法。

    Subject:
    在这里插入图片描述

    Observer:
    在这里插入图片描述

    ConcreteSubject:

    /**
     * 具体主题
     */
    public class ConcreteSubject implements Subject {
     
        private List<Observer> observerList = new ArrayList<>();
        private String state;
     
        @Override
        public void attach(Observer observer) {
            this.observerList.add(observer);
            System.out.println("向ConcreteSubject注册了一个观察者");
        }
     
        @Override
        public void detach(Observer observer) {
            this.observerList.remove(observer);
            System.out.println("从ConcreteSubject移除了一个观察者");
        }
     
     		//遍历观察者发出通知
        @Override
        public void notifyObservers() {
            this.observerList.forEach(observer -> observer.update(this.state));
        }
     
     		//发通知给观察者
        public void changeState(String state) {
            this.state = state;
            this.notifyObservers();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    ConcreteObserver:
    在这里插入图片描述

    测试类:MyObserverTest:
    在这里插入图片描述

    门面与状态模式

    什么是门面模式?门面模式的应用场景?

    是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

    2、使用门面模式重构支付回调业务逻辑代码(代码任务)
    答:
    3、什么是状态模式,状态模式与策略模式的区别
    答:
    适配器模式
    1、什么是适配器模式?适配器模式原理
    答:
    2、适配器的应用场景
    答:
    3、请使用适配器编写案例一个参数支持多种类型(代码实战)
    答:
    4、myabtis适配器源码分析
    答:
    单例模式
    深入理解单例
    1、什么是单例模式?
    答:
    2、单例模式有哪些创建方式?
    答:
    3、饿汉式与懒汉式区别?
    答:
    4、双重检验锁两个if判断作用
    答:
    5、静态内部类方式与双重检验锁的区别?
    答:
    6、如何破坏一个单例?
    答:
    7、java序列的作用有哪些?
    答:
    8、请完成七种单例模式代码编写
    答:

    深入理解枚举单例底层实现原理
    1、枚举单例底层是如何实现的?(源码解析流程图)
    答:
    2、为什么反射无法初始化枚举对象?
    答:

  • 相关阅读:
    [项目管理-10]:软硬件项目管理 - 项目质量管理(质量)
    jQuery中的prop方法(全选按钮)
    python定时器
    BFD双向转发检测协议理论讲解
    SpringBoot项目集成Dubbo
    电容笔品牌哪个牌子好?电容笔质量最好的品牌推荐
    分库分表利器:Sharding-JDBC、TDDL、Mycat选择与应用
    uni-app 系统状态栏高度CSS变量--status-bar-height
    【代码随想录】【算法训练营】【第27天】 [39]组合总和 [40] 组合总和II [131]分割回文串
    什么是快速失败(fail-fast)和安全失败(fail-safe)?
  • 原文地址:https://blog.csdn.net/weixin_44044929/article/details/126239956