面试题:java源码中用到哪些设计模式,为什么这样用
Posted ispotu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试题:java源码中用到哪些设计模式,为什么这样用相关的知识,希望对你有一定的参考价值。
0.简单工厂模式(StaticFactory Method,非23种设计模式之一)
又叫做静态工厂方法(StaticFactory Method)模式,但不属于23种GOF设计模式之一。
简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。 spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
bean容器的启动阶段: 读取bean的xml配置文件,将bean元素分别转换成一个BeanDefinition对象。 然后通过BeanDefinitionRegistry将这些bean注册到beanFactory中,保存在它的一个ConcurrentHashMap中。 将BeanDefinition注册到了beanFactory之后,在这里Spring为我们提供了一个扩展的切口,允许我们通过实现接口BeanFactoryPostProcessor 在此处来插入我们定义的代码。典型的例子就是:PropertyPlaceholderConfigurer,我们一般在配置数据库的dataSource时使用到的占位符的值,就是它注入进去的。 容器中bean的实例化阶段: 实例化阶段主要是通过反射或者CGLIB对bean进行实例化,在这个阶段Spring又给我们暴露了很多的扩展点: 各种的Aware接口,比如 BeanFactoryAware,对于实现了这些Aware接口的bean,在实例化bean时Spring会帮我们注入对应的BeanFactory的实例。 BeanPostProcessor接口,实现了BeanPostProcessor接口的bean,在实例化bean时Spring会帮我们调用接口中的方法。 InitializingBean接口,实现了InitializingBean接口的bean,在实例化bean时Spring会帮我们调用接口中的方法。 DisposableBean接口,实现了BeanPostProcessor接口的bean,在该bean死亡时Spring会帮我们调用接口中的方法。
为什么这么用:
松耦合。可以将原来硬编码的依赖,通过Spring这个beanFactory这个工厂来注入依赖,也就是说原来只有依赖方和被依赖方,现在我们引入了第三方——spring这个beanFactory,由它来解决bean之间的依赖问题,达到了松耦合的效果. bean的额外处理。通过Spring接口的暴露,在实例化bean的阶段我们可以进行一些额外的处理,这些额外的处理只需要让bean实现对应的接口即可,那么spring就会在bean的生命周期调用我们实现的接口来处理该bean。
1.工厂方法(Factory Method)
实现了FactoryBean接口的bean是一类叫做factory的bean,spring会在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法。
建一个config.xm配置文件,将其纳入Spring容器来管理,需要通过factory-method指定静态方法名称
<bean id="random"
class="example.chapter3.StaticFactoryBean" factory-method="createRandom" //createRandom方法必须是static的,才能找到 scope="prototype"
/>
为什么这么用:
为了将对象的创建和使用相分离,采用工厂模式,即应用程序将对象的创建及初始化职责交给工厂对象。
当系统扩展需要添加新的产品对象时,仅仅需要添加一个具体对象以及一个具体工厂对象,原有工厂对象不需要进行任何修改,也不需要修改客户端,很好的符合了“开放-封闭”原则。
提供创建的产品接口给使用者就行,无论产品的类型如何变化,只要根据接口创建的产品的功能没有变化,使用者就无须做任何变动。
工厂类是整个模式的关键,包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象。通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了,而不必管这些对象究竟如何创建及如何组织的。明确了各自的职责和权利,有利于整个软件体系结构的优化。
2.单例模式(Singleton)
保证一个类仅有一个实例,并提供一个访问它的全局访问点。 spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但没有从构造器级别去控制单例,这是因为spring管理的是是任意的java对象。
-
Spring依赖注入Bean实例默认是单例的。
-
Spring的依赖注入(包括lazy-init方式)都是发生在AbstractBeanFactory的getBean里。getBean的doGetBean方法调用getSingleton进行bean的创建。
为什么这么用:
保证一个类仅有一个实例。
使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(garbage collection)。
3.适配器模式(Adapter)
(1)实现方式:SpringMVC中的适配器HandlerAdatper。 实现原理:HandlerAdatper根据Handler规则执行不同的Handler。 实现过程: DispatcherServlet根据HandlerMapping返回的handler,向HandlerAdatper发起请求,处理Handler。HandlerAdapter根据规则找到对应的Handler并让其执行,执行完毕后Handler会向HandlerAdapter返回一个ModelAndView,最后由HandlerAdapter向DispatchServelet返回一个ModelAndView。 实现意义: HandlerAdatper使得Handler的扩展变得容易,只需要增加一个新的Handler和一个对应的HandlerAdapter即可。因此Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类,让适配器代替controller执行相应的方法。这样在扩展Controller时,只需要增加一个适配器类就完成了SpringMVC的扩展了。
(2)另外,在Spring的Aop中,使用的Advice(通知)来增强被代理类的功能。
在Spring的Aop中,使用的Advice(通知)来增强被代理类的功能。Spring实现这一AOP功能的原理就使用代理模式(1、JDK动态代理。2、CGLib字节码生成技术代理。)对类进行方法级别的切面增强,即,生成被代理类的代理类, 并在代理类的方法前,设置拦截器,通过执行拦截器重的内容增强了代理方法的功能,实现的面向切面编程。
Adapter类接口:Target
public interface AdvisorAdapter
boolean supportsAdvice(Advice advice);
MethodInterceptor getInterceptor(Advisor advisor);
MethodBeforeAdviceAdapter类Adapter
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable
public boolean supportsAdvice(Advice advice)
return (advice instanceof MethodBeforeAdvice);
public MethodInterceptor getInterceptor(Advisor advisor)
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
为什么这么用:
1)完美实现解耦,通过增加适配器类将适配者与目标接口联系起来,无需修改原有实现;
2)提高复用性,适配器类可以在多个系统使用;
3)符合开闭原则;
主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
可以让任何两个没有关联的类一起运行,提高了类的复用,增加了类的透明度,灵活性好。
(1)适配器模式可以理解成是在原有基础上的封装,不需要对原有程序进行改动,即可实现特定功能。
(2)对象适配器可以服务于多个源角色,便于程序的扩展。
4.装饰器模式(Decorator/Wrapper)
Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。基本上都是动态地给一个对象添加一些额外的职责。
File file = new File ("hello.txt"); FileInputStream in=new FileInputStream (file); InputStreamReader inReader=new InputStreamReader (in,"UTF-8"); BufferedReader bufReader=new BufferedReader(inReader);
即:(一层包一层)
BufferedReader bufReader = new BufferedReader(new InputStreamReader (new FileInputStream (new File ("hello.txt")),"UTF-8") );
为什么这么用:
装饰器模式作用是针对目标方法进行增强,提供新的功能或者额外的功能。
- 扩展对象功能,比继承灵活,不会导致类个数急剧增加。
- 可以对一个对象进行多次装饰,创造出不同行为的组合,得到功能更加强大的对象。
- 具体构件类和具体装饰类可以独立变化,用户可以根据需要自己增加新的 具体构件子类和具体装饰子类。
- 装饰模式降低系统的耦合度,可以动态的增加或删除对象的责任,并使得需要装饰的具体构建类和具体装饰类可以独立变化,以便增加新的具体构建类和具体装饰类。
5.代理模式(Proxy)
为其他对象提供一种代理以控制对这个对象的访问。 从结构上来看和Decorator模式类似,但Proxy是控制,更像是一种对功能的限制,而Decorator是增加职责。 spring的Proxy模式在aop中有体现,比如JdkDynamicAopProxy和Cglib2AopProxy。
实现方式: AOP底层,就是动态代理模式的实现。 动态代理:在内存中构建的,不需要手动编写代理类 静态代理:需要手工编写代理类,代理类引用被代理对象。 实现原理: 切面在应用运行的时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象创建动态的创建一个代理对象。SpringAOP就是以这种方式织入切面的。 织入:把切面应用到目标对象并创建新的代理对象的过程。
为什么这么用:
职责清晰:真实角色只需关注业务逻辑的实现,非业务逻辑部分,后期通过代理类完成即可。
高扩展性:不管真实角色如何变化,由于接口是固定的,代理类无需做任何改动。
们想对外开放某些功能,就可以将这些功能在代理类中被引用,如此一来,屏蔽了我们不想外露的功能,只将我们想开放的功能开放出来。亦即委托类中其实是可以有很多方法的,很多功能的,我们可以酌情对外开放,代理类犹如一道大门,将委托类与外部调用者隔绝开来,只将部分功能赋予这个大门,来代替委托类行使这个功能,哪怕最终还是要牵扯到自身(因为最终还是要调用委托类的对应方法实现)。
6.观察者模式(Observer)
-
pring的事件驱动模型使用的是 观察者模式 ,Spring中Observer模式常用的地方是listener的实现。
-
具体实现:
事件机制的实现需要三个部分,事件源,事件,事件监听器。
ApplicationEvent抽象类[事件]
继承自jdk的EventObject,所有的事件都需要继承ApplicationEvent,并且通过构造器参数source得到事件源. 该类的实现类ApplicationContextEvent表示ApplicaitonContext的容器事件
ApplicationListener接口[事件监听器]
继承自jdk的EventListener,所有的监听器都要实现这个接口。 这个接口只有一个onApplicationEvent()方法,该方法接受一个ApplicationEvent或其子类对象作为参数,在方法体中,可以通过不同对Event类的判断来进行相应的处理。 当事件触发时所有的监听器都会收到消息。
ApplicationContext接口[事件源]
ApplicationContext是spring中的全局容器,翻译过来是”应用上下文”。 实现了ApplicationEventPublisher接口。 职责:负责读取bean的配置文档,管理bean的加载,维护bean之间的依赖关系,可以说是负责bean的整个生命周期,再通俗一点就是我们平时所说的IOC容器。
为什么这么用:
可方便监听与之相关的类的变化,类决定本类的动作,起事件通知的作用。
- 观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体观察者列表。
由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。如果被观察者和观察者都被扔到一起,那么这个对象必然跨越抽象化和具体化层次。
- 观察者模式支持广播通讯。被观察者会向所有的登记过的观察者发出通知。
7.策略模式(Strategy)
Comparator这个接口简直就是为策略模式而生的。Comparable和Comparator的区别一文中,详细讲了Comparator的使用。比方说Collections里面有一个sort方法,因为集合里面的元素有可能是复合对象,复合对象并不像基本数据类型,可以根据大小排序,复合对象怎么排序呢?基于这个问题考虑,Java要求如果定义的复合对象要有排序的功能,就自行实现Comparable接口或Comparator接口.
为什么这么用:
你可以动态的改变对象的行为。可方便的根据不同的情景选择不同的实现方法,具体实现方法放在子类,使程序更加灵活。
高内聚低耦合还有一个就是扩展性,也就是OCP原则。
- 上下文和具体策略是松耦合关系,因此,上下文只需要知道他要使用耨一个实现Strategy接口类的实例,但不需要知道具体是哪一个类。
- 策略模式满足“开-闭原则”,当增加新的具体策略时,不需要修改上下文类的代码,上下文就可以引用新的策略的实例。
- 一个类定义了多种行为,并且这些行为在这个类的方法中以多个条件语句的形式出现,那么使用策略模式避免在类中使用大量的条件语句。
- 程序的主类(相当于上下文角色),不希望暴露复杂的,与算法相关的数据结构,那么可以使用策略模式封装算法,即将算法分别封装到具体策略中。
- 策略模式的功能就是通过抽象、封装来定义一系列的算法,使得这些算法可以相互替换,所以为这些算法定义一个公共的接口,以约束这些算法的功能实现。如果这些算法具有公共的功能,可以将接口变为抽象类,将公共功能放到抽象父类里面。
- 策略模式的一系列算法是可以相互替换的、是平等的,写在一起就是if-else组织结构,如果算法实现里又有条件语句,就构成了多重条件语句,可以用策略模式,避免这样的多重条件语句。
- 扩展性更好:在策略模式中扩展策略实现非常的容易,只要新增一个策略实现类,然后在使用策略实现的地方,使用这个新的策略实现就好了。
8.模板方法(Template Method)
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 Template Method模式一般是需要继承的。spring中的JdbcTemplate。
Spring模板方法模式实质: 是模板方法模式和回调模式的结合,是Template Method不需要继承的另一种实现方式。Spring几乎所有的外接扩展都采用这种模式。
为什么JdbcTemplate没有使用继承? 因为这个类的方法太多,但是我们还是想用到JdbcTemplate已有的稳定的、公用的数据库连接,那么我们怎么办呢?我们可以把变化的东西抽出来作为一个参数传入JdbcTemplate的方法中。但是变化的东西是一段代码,而且这段代码会用到JdbcTemplate中的变量。怎么办?那我们就用回调对象吧。在这个回调对象中定义一个操纵JdbcTemplate中变量的方法,我们去实现这个方法,就把变化的东西集中到这里了。然后我们再传入这个回调对象到JdbcTemplate,从而完成了调用。
另外在在各种BeanFactory以及ApplicationContext实现中也都用到了。
定义模板,就是定义框架、结构、原型。定义一个我们共同遵守的约定。
定义了模板,我们的剩余工作就是对其进行充实、丰润,完善它的不足之处。
为什么这么用:
1.封装不变部分,扩展可变部分。
2.提取了公共代码,便于维护。
3.行为由父类控制,子类实现。
模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。
子类实现算法的某些细节,有助于算法的扩展。
通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”。
逻辑相同时,可以将这些方法抽出来放到一个模板抽象类中,代码复用;
固定程序主框架,避免程序流程差错,导致的运行结果出问题。
定义模板,就是定义框架、结构、原型。定义一个我们共同遵守的约定。
定义了模板,我们的剩余工作就是对其进行充实、丰润,完善它的不足之处。
9.外观模式(Facade)
外观模式又称为门面模式,它是一种对象结构型模式。外观模式是迪米特法则的一种具体实现,通过引入一个新的外观角色可以降低原有系统的复杂度,同时降低客户类与子系统的耦合度。
为子系统中的一组接口提供一个一致的接口。Façade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
ResponseFacade是一个Request的外观类,他实现了HttpServletRequest.
Request通过RequestFacade包装了这个Request类,因为Request和RequestFacade,都实现了HttpServletRequest, 所以在获取Request的时候,实际返回的是一个facade,因为他两都是他的子类,而这个RequestFacade,内部就可以根据 需要进行封装,把具体的他认为子系统的各种操作都封装到RequestFacade这里边,对外我们类似操作的是 HttpServletRequest这个类,实际我们使用的是RequestFacade这个类。
StandardSessionFacade是处理HttpSession的,那在TOMCAT源码里面.
StatementFacade是TOMCAT里面提供的,JDBC连接池.
为什么这么用:(主要优点)
它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简单,与之关联的对象也很少。 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可。 一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。 外观模式的主要缺点如下:
不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性。 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。 适用场景:
当要为访问一系列复杂的子系统提供一个简单入口时可以使用外观模式。 客户端程序与多个子系统之间存在很大的依赖性。引入外观类可以将子系统与客户端解耦,从而提高子系统的独立性和可移植性。 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。
10.原型模式(Prototype)
使用原型模式创建对象比直接new一个对象在性能上好得多,因为Object类的clone()方法是一个native方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。
为什么这么用:
使用原型模式创建对象比直接new一个对象在性能上好得多。
可定制对象。
用new新建对象不能获取当前对象运行时的状态,其次就算new了新对象,在将当前对象的值复制给新对象,效率也不如原型模式高。在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。
(1) 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
(2) 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响。
(3) 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
(4) 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。
11.迭代器模式(Iterator)
Iterable接口和Iterator接口 这两个都是迭代相关的接口,可以这么认为,实现了Iterable接口,则表示某个对象是可被迭代的;Iterator接口相当于是一个迭代器,实现了Iterator接口,等于具体定义了这个可被迭代的对象时如何进行迭代的
为什么这么用:
只需提供一个迭代器,即可遍历类中的数据结构。可在不知类的具体实现类的情况下,迭代并获取到它的所有实现类。
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
迭代器模式把游走的任务放在迭代器上,而不是聚合上。这样简化了聚合的接口和实现,也可以让责任各得其所。
优点
- 它支持以不同的方式遍历一个聚合对象。
- 迭代器简化了聚合类。
- 在同一个聚合上可以有多个遍历。
- 在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
缺点
对于比较简单的遍历(像数组或者有序列表),使用迭代器方式遍历较为繁琐。
12.享元模式
池技术如String常量池、数据库连接池、缓冲池、Integer.valueOf()方法如果值在-128~127之间等等都是享元模式的应用,所以说享元模式是池技术的重要实现方式。
比如我们每次创建字符串对象时,都需要创建一个新的字符串对象的话,内存开销会很大,所以如果第一次创建了字符串对象“adam“,下次再创建相同的字符串”adam“时,只是把它的引用指向”adam“,这样就实现了”adam“字符串再内存中的共享。
为什么这么用:
大大减少了对象的创建,降低了程序内存的占用,提高效率。
参考:https://blog.csdn.net/caoxiaohong1005/article/details/80039656
https://blog.csdn.net/u010775025/article/details/79480645
https://www.cnblogs.com/baizhanshi/p/6187537.html
https://blog.csdn.net/qq_34337272/article/details/90487768
以上是关于面试题:java源码中用到哪些设计模式,为什么这样用的主要内容,如果未能解决你的问题,请参考以下文章