设计模式面试题详解
- 请写出您熟悉的几种设计模式,并做简单介绍。
答:
工厂设计模式:程序在接口和子类之间加入了一个过渡端,通过此过渡端可以动态取得
实现了共同接口的子类实例化对象。
代理设计模式:指由一个代理主题来操作真实主题,真实主题执行具体的业务操作,而
代理主题负责其他相关业务的处理。比如生活中的通过代理访问网络,客户通过网络代
理连接网络(具体业务),由代理服务器完成用户权限和访问限制等与上网相关的其他操
作(相关业务)
适配器模式:如果一个类要实现一个具有很多抽象方法的接口,但是本身只需要实现接
口中的部分方法便可以达成目的,所以此时就需要一个中间的过渡类,但此过渡类又不
希望直接使用,所以将此类定义为抽象类最为合适,再让以后的子类直接继承该抽象类
便可选择性的覆写所需要的方法,而此抽象类便是适配器类。 - 写出你用过的设计模式,并至少写出 2 种模式的类图或关键代码。
工厂设计模式:
思路说明:由一个工厂类根据传入的参数(一般是字符串参数),动态决定应该
创建哪一个产品子类(这些产品子类继承自同一个父类或接口)的实例,并以父类
形式返回
优点:客户端不负责对象的创建,而是由专门的工厂类完成;客户端只负责对象的
调用,实现了创建和调用的分离,降低了客户端代码的难度;
缺点:如果增加和减少产品子类,需要修改简单工厂类,违背了开闭原则;如果产
品子类过多,会导致工厂类非常的庞大,违反了高内聚原则,不利于后期维护。
public class SimpleFactory {
public static Product createProduct(String pname){
Product product=null;
if("p1".equals(pname)){
product = new Product1();
}else if("p2".equals(pname)){
product = new Product2();
}else if("pn".equals(pname)){
product = new ProductN();
}
return product;
} }
单例模式
/**
- 饿汉式的单例模式
- 在类加载的时候创建单例实例,而不是等到第一次请求实例的时候的时候创
建 - 1、私有 的无参数构造方法Singleton(),避免外部创建实例
- 2、私有静态属性instance
- 3、公有静态方法getInstance()
/
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){ }
public static Singleton getInstance(){
return instance; } }
/* - 懒汉式的单例模式
在类加载的时候不创建单例实例,只有在第一次请求实例的时候的时候创建
/
public class Singleton {
private static Singleton instance;
private Singleton(){ }
/ - 多线程情况的单例模式,避免创建多个对象
*/
public static Singleton getInstance(){
if(instance null){//避免每次加锁,只有第一次没有创建对象时才加
锁
synchronized(Singleton.class){//加锁,只允许一个线程进入
if(instancenull){ //只创建一次对象
instance = new Singleton();
} } }
return instance;
}}
- 列出除 Singleton 外的常用的 3 种设计模式,并简单描述
答:
工厂模式:工厂模式是 Java 中最常用的设计模式之一。这种类型的设计模式属于
创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不
会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
适配器模式:适配器模式是作为两个不兼容的接口之间的桥梁。这种类型的设计模
式属于结构型模式,它结合了两个独立接口的功能。这种模式涉及到一个单一的类,该
类负责加入独立的或不兼容的接口功能。
模板模式:在模板模式中,一个抽象类公开定义了执行它的方法的方式/模板。它的
尚学堂 Java 面试题大全及参考答案
子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。 - Action 是单实例还是多实例,为什么?
答:
struts2 中 action 是多例的,即一个 session 产生一个 action
背景:
- Struts2 会对每一个请求,产生一个 Action 的实例来处理.
- Spring 的 Ioc 容器管理的 bean 默认是单实例的.
首先从数据安全性的问题上考虑,我们的 Action 应该保证是多例的,这样才不会出现数
据问题。但是如果有的 action 比如只有 admin 才能操作,或者某些 action,全站公用
一个来提高性能,这样的话,就可以使用单例模式。
不过幸好,Spring 的 bean 可以针对每一个设置它的 scope,所以,上面的问题就不是
问题了。如果用单例,就在 spring 的 action bean 配置的时候设置 scope=”prototype”
如果是单例的话,若出现两个用户都修改一个对象的属性值,则会因为用户修改时间不同,
两个用户访问得到的属性不一样,操作得出的结果不一样.
举个例子:有一块布长度 300cm,能做一件上衣(用掉 100cm)和一件裤子(用掉 200cm);
甲和乙同时访问得到的长度都是 300cm,
甲想做上衣和裤子,他先截取100cm去做上衣,等上衣做完再去做裤子,而乙这时正好也拿
100cm 去做上衣,那好,等甲做完上衣再做裤子的时候发现剩下的布(100cm)已经不够做
裤子了…..这就是影响系统的性能,解决的办法就是给甲和乙一人一块300cm的布,就不会
出现布被别人偷用的事情,也是就单实例和多实例的区别
如果设置成单例,那么多个线程会共享一个 ActionContext 和 ValueStack,这样并发访
问的时候就会出现问题了
struts 2 的 Action 是多实例的并非单例,也就是每次请求产生一个 Action 的对象。原
因是:struts 2 的 Action 中包含数据,例如你在页面填写的数据就会包含在 Action 的
成员变量里面。如果 Action 是单实例的话,这些数据在多线程的环境下就会相互影响,
例如造成别人填写的数据被你看到了。所以 Struts2 的 Action 是多例模式的。
问题出现了,可以让 Struts2 的 action 变成单例模式么?
Struts2 中,可以使用注解开发,在 Action 上@Scope(“prototype”) 指定为多例 , 默
认为 singleton()单例)
基本上 action 的 scope 需要是 prototype,就是每次请求都建立新的线程
不写的话,默认是 singleton 了
- 写一个单例类
答:单例模式主要作用是保证在 Java 应用程序中,一个类只有一个实例存在。下面给出
两种不同形式的单例:
第一种形式:饿汉式单例
package com.bjsxt;
public class Singleton {
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
第二种形式:懒汉式单例
package com.bjsxt;
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance(){
if (instance==null) instance=newSingleton();
return instance;
}
}
单例的特点:外界无法通过构造器来创建对象,该类必须提供一个静态方法向外界提供
该类的唯一实例。
【补充】用 Java 进行服务器端编程时,使用单例模式的机会还是很多的,服务器上的资
源都是很宝贵的,对于那些无状态的对象其实都可以单例化或者静态化(在内存中仅有
唯一拷贝),如果使用了 spring 这样的框架来进行对象托管,Spring 的 IoC 容器在默认
情况下对所有托管对象都是进行了单例化处理的。 - 说说你所熟悉或听说过的设计模式以及你对设计模式的看法
答:在 GoF 的《Design Patterns: Elements of Reusable Object-Oriented Software》
中给出了三类(创建型[对类的实例化过程的抽象化]、结构型[描述如何将类或对象结合
在一起形成更大的结构]、行为型[对在不同的对象之间划分责任和算法的抽象化])共 23
种设计模式,包括:Abstract Factory(抽象工厂模式),Builder(建造者模式),Factory
Method(工厂方法模式),Prototype(原始模型模式),Singleton(单例模式);Facade
(门面模式),Adapter(适配器模式),Bridge(桥梁模式),Composite(合成模式),
Decorator(装饰模式),Flyweight(享元模式),Proxy(代理模式);Command(命
令模式),Interpreter(解释器模式),Visitor(访问者模式),Iterator(迭代子模式),
Mediator(调停者模式),Memento(备忘录模式),Observer(观察者模式),State
(状态模式),Strategy(策略模式),Template Method(模板方法模式), Chain Of
Responsibility(责任链模式)。
所谓设计模式,就是一套被反复使用的代码设计经验的总结(情境中一个问题经过证实
的一个解决方案)。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代
码可靠性。设计模式使人们可以更加简单方便的复用成功的设计和体系结构。将已证实
的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。
【补充】设计模式并不是像某些地方吹嘘的那样是遥不可及的编程理念,说白了设计模
式就是对面向对象的编程原则的实践,面向对象的编程原则包括:
• 单一职责原则:一个类只做它该做的事情。(单一职责原则想表达的就是“高内聚”,
写代码最终极的原则只有六个字“高内聚、低耦合”,就如同葵花宝典或辟邪剑谱的
中心思想就八个字“欲练此功必先自宫”,所谓的高内聚就是一个代码模块只完成一
项功能,在面向对象中,如果只让一个类完成它该做的事,而不涉及与它无关的领域
就是践行了高内聚的原则,这个类就只有单一职责。我们都知道一句话叫“因为专注,
所以专业”,一个对象如果承担太多的职责,那么注定它什么都做不好。这个世界上
任何好的东西都有两个特征,一个是功能单一,好的相机绝对不是电视购物里面卖的
那种一个机器有一百多种功能的,它基本上只能照相;另一个是模块化,好的自行车
是组装车,从减震叉、刹车到变速器,所有的部件都是可以拆卸和重新组装的,好的
乒乓球拍也不是成品拍,一定是底板和胶皮可以拆分和自行组装的,一个好的软件系
统,它里面的每个功能模块也应该是可以轻易的拿到其他系统中使用的,这样才能实
现软件复用的目标。)
• 开闭原则:软件实体应当对扩展开放,对修改关闭。(在理想的状态下,当我们需要为
一个软件系统增加新功能时,只需要从原来的系统派生出一些新类就可以,不需要修
324
尚学堂 Java 面试题大全及参考答案
改原来的任何一行代码。要做到开闭有两个要点:①抽象是关键,一个系统中如果没
有抽象类或接口系统就没有扩展点;②封装可变性,将系统中的各种可变因素封装到
一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱,如果不清
楚如何封装可变性,可以参考《设计模式精解》一书中对桥梁模式的讲解的章节。)
• 依赖倒转原则:面向接口编程。(该原则说得直白和具体一些就是声明方法的参数类型、
方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,因为抽
象类型可以被它的任何一个子类型所替代,请参考下面的里氏替换原则。)
• 里氏替换原则:任何时候都可以用子类型替换掉父类型。(关于里氏替换原则的描述,
Barbara Liskov 女士的描述比这个要复杂得多,但简单的说就是能用父类型的地方就
一定能使用子类型。里氏替换原则可以检查继承关系是否合理,如果一个继承关系违
背了里氏替换原则,那么这个继承关系一定是错误的,需要对代码进行重构。例如让
猫继承狗,或者狗继承猫,又或者让正方形继承长方形都是错误的继承关系,因为你
很容易找到违反里氏替换原则的场景。需要注意的是:子类一定是增加父类的能力而
不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对
象来用当然没有任何问题。)
• 接口隔离原则:接口要小而专,绝不能大而全。(臃肿的接口是对接口的污染,既然接
口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。例如,
琴棋书画就应该分别设计为四个接口,而不应设计成一个接口中的四个方法,因为如
果设计成一个接口中的四个方法,那么这个接口很难用,毕竟琴棋书画四样都精通的
人还是少数,而如果设计成四个接口,会几项就实现几个接口,这样的话每个接口被
复用的可能性是很高的。Java 中的接口代表能力、代表约定、代表角色,能否正确
的使用接口一定是编程水平高低的重要标识。)
• 合成聚合复用原则:优先使用聚合或合成关系复用代码。(通过继承来复用代码是面向
对象程序设计中被滥用得最多的东西,因为所有的教科书都无一例外的对继承进行了
鼓吹从而误导了初学者,类与类之间简单的说有三种关系,IS-A 关系、HAS-A 关系、
USE-A 关系,分别代表继承、关联和依赖。其中,关联关系根据其关联的强度又可
以进一步划分为关联、聚合和合成,但说白了都是 HAS-A 关系,合成聚合复用原则
想表达的是优先考虑 HAS-A 关系而不是 IS-A 关系复用代码,原因嘛可以自己从百度
上找到一万个理由,需要说明的是,即使在 Java 的 API 中也有不少滥用继承的例子,
例如 Properties 类继承了 Hashtable 类,Stack 类继承了 Vector 类,这些继承明显
就是错误的,更好的做法是在 Properties 类中放置一个 Hashtable 类型的成员并且
将其键和值都设置为字符串来存储数据,而 Stack 类的设计也应该是在 Stack 类中放
一个 Vector 对象来存储数据。记住:任何时候都不要继承工具类,工具是可以拥有
并可以使用的(HAS/USE),而不是拿来继承的。)
• 迪米特法则:迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的
了解。(迪米特法则简单的说就是如何做到“低耦合”,门面模式和调停者模式就是
对迪米特法则的践行。对于门面模式可以举一个简单的例子,你去一家公司洽谈业务,
你不需要了解这个公司内部是如何运作的,你甚至可以对这个公司一无所知,去的时
候只需要找到公司入口处的前台美女,告诉她们你要做什么,她们会找到合适的人跟
你接洽,前台的美女就是公司这个系统的门面。再复杂的系统都可以为用户提供一个
简单的门面,Java Web 开发中作为前端控制器的 Servlet 或 Filter 不就是一个门面
吗,浏览器对服务器的运作方式一无所知,但是通过前端控制器就能够根据你的请求
得到相应的服务。调停者模式也可以举一个简单的例子来说明,例如一台计算机,CPU、
内存、硬盘、显卡、声卡各种设备需要相互配合才能很好的工作,但是如果这些东西
都直接连接到一起,计算机的布线将异常复杂,在这种情况下,主板作为一个调停者
的身份出现,它将各个设备连接在一起而不需要每个设备之间直接交换数据,这样就
减小了系统的耦合度和复杂度。迪米特法则用通俗的话来将就是不要和陌生人打交道,
如果真的需要,找一个自己的朋友,让他替你和陌生人打交道。) - Java 企业级开发中常用的设计模式有哪些?
答: 按照分层开发的观点,可以将应用划分为:表示层、业务逻辑层和持久层,每一层
都有属于自己类别的设计模式。
表示层设计模式:
- Interceptor Filter:拦截过滤器,提供请求预处理和后处理的方案,可以对请求和响
应进行过滤。 - Front Controller:通过中央控制器提供请求管理和处理,管理内容读取、安全性、
视图管理和导航等功能。Struts 2 中的 StrutsPrepareAndExecuteFilter、Spring MVC
中的 DispatcherServlet 都是前端控制器,后者如下图所示: - View Helper:视图帮助器,负责将显示逻辑和业务逻辑分开。显示的部分放在视图
组件中,业务逻辑代码放在帮助器中,典型的功能是内容读取、验证与适配。 - Composite View:复合视图。
业务逻辑层设计模式: - Business Delegate:业务委托,减少表示层和业务逻辑层之间的耦合。
- Value Object:值对象,解决层之间交换数据的开销问题。
- Session Façade:会话门面,隐藏业务逻辑组件的细节,集中工作流程。
- Value Object Assembler:灵活的组装不同的值对象
- Value List Handler:提供执行查询和处理结果的解决方案,还可以缓存查询结果,
从而达到提升性能的目的。 - Service Locator:服务定位器,可以查找、创建和定位服务工厂,封装其实现细节,
减少复杂性,提供单个控制点,通过缓存提高性能。
持久层设计模式:
Data Access Object:数据访问对象,以面向对象的方式完成对数据的增删改查。
【补充】如果想深入的了解 Java 企业级应用的设计模式和架构模式,可以参考这些书
籍: 《Pro Java EE Spring Patterns》、《POJO in Action》、《Patterns of Enterprise
Application Architecture》。
- 你在开发中都用到了那些设计模式?用在什么场合?
答:面试被问到关于设计模式的知识时,可以拣最常用的作答,例如:
- 工厂模式:工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父
类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。
当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个
子类的实例。 - 代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。实际开
发中,按照使用目的的不同,代理可以分为:远程代理、虚拟代理、保护代理、Cache
代理、防火墙代理、同步化代理、智能引用代理。 - 适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口
不匹配而无法在一起使用的类能够一起工作。 - 模板方法模式:提供一个抽象类,将部分逻辑以具体方法或构造器的形式实现,然后
声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些
抽象方法(多态实现),从而实现不同的业务逻辑。
除此之外,还可以讲讲上面提到的门面模式、桥梁模式、单例模式、装潢模式(Collections
工具类里面的 synchronizedXXX 方法把一个线程不安全的容器变成线程安全容器就是
对装潢模式的应用,而 Java IO 里面的过滤流(有的翻译成处理流)也是应用装潢模式
的经典例子)等,反正原则就是拣自己最熟悉的用得最多的作答,以免言多必失。
- 什么是设计模式,设计模式的作用。
设计模式是一套被反复使用的、多数人知晓、经过分类编目的优秀代码设计经验的总结。
特定环境下特定问题的处理方法。
1)重用设计和代码 重用设计比重用代码更有意义,自动带来代码重用
2)提高扩展性 大量使用面向接口编程,预留扩展插槽,新的功能或特性很容易加
入到系统中来
3)提高灵活性 通过组合提高灵活性,可允许代码修改平稳发生,对一处修改不会
波及到其他模块
4) 提高开发效率 正确使用设计模式,可以节省大量的时间 - 23 种经典设计模式都有哪些,如何分类。
- 写出简单工厂模式的示例代码
package com.bjsxt;
public class SimpleFactory {
public static Product createProduct(String pname){
Product product=null;
面向对象设计原
则 •单一职责原则
•开闭原则
•里氏替代原则
•依赖注入原则
•接口分离原则
•迪米特原则
•组合/聚合复用原
则
创建型模式 5+1
•简单工厂模式
•工厂方法模式
•抽象工厂模式
•创建者模式
•原型模式
•单例模式
结构型模式 7 •外观模式
•适配器模式
•适配器模式
•代理模式
•装饰模式
•桥接模式
•组合模式
•享元模式
行为型模式 11
•模板方法模式
•观察者模式
•状态模式
•策略模式
•职责链模式
•命令模式
•访问者模式
•调停者模式
•备忘录模式
•迭代器模式
•解释器模式
if("p1".equals(pname)){
product = new Product();
}else if("p2".equals(pname)){
product = new Product();
}else if("pn".equals(pname)){
product = new Product();
}
return product; } }
基本原理:由一个工厂类根据传入的参数(一般是字符串参数),动态决定应该创建哪一
个产品子类(这些产品子类继承自同一个父类或接口)的实例,并以父类形式返回
优点:客户端不负责对象的创建,而是由专门的工厂类完成;客户端只负责对象的调用,
实现了创建和调用的分离,降低了客户端代码的难度;
缺点:如果增加和减少产品子类,需要修改简单工厂类,违背了开闭原则;如果产品子
类过多,会导致工厂类非常的庞大,违反了高内聚原则,不利于后期维护 - 请对你所熟悉的一个设计模式进行介绍
分析:建议挑选有一定技术难度,并且在实际开发中应用较多的设计模式。可以挑选装
饰模式和动态代理模式。此处挑选动态代理设计模式。
讲解思路:生活案例引入、技术讲解、优缺点分析、典型应用。
1、生活案例引入:送生日蛋糕:
MM 们要过生日了,怎么也得表示下吧。最起码先送个蛋糕。蛋糕多种多样了。巧克力,
冰淇淋,奶油等等。这都是基本的了,再加点额外的装饰,如蛋糕里放点花、放贺卡、
放点干果吃着更香等等。
分析:
方案 1:如果采用继承会造成大量的蛋糕子类
方案 2:蛋糕作为主体,花,贺卡,果仁等是装饰者,需要时加到蛋糕上。要啥我就加
啥。
技术讲解
装饰模式(别名 Wrapper)是在不必改变原类文件和使用继承的情况下,动态的扩展一
个对象的功能。它通过创建一个包装对象,也就是装饰来包裹真实对象,提供了比继承
更具弹性的代替方案。
装饰模式一般涉及到的角色
抽象构建角色(Component):给出一个抽象的接口,以规范准备接受附加责任的对象。
具体的构建角色(ConcreteComponent):定义一个将要接受附加责任的类。
抽象的装饰角色 (Decorator):持有一个抽象构建(Component)角色的引用,并定义一个
与抽象构件一致的接口。
具体的装饰角色(ConcreteDecorator):负责给构建对象“贴上”附加的责任。
3、优缺点分析
优点
1)Decorator 模式与继承关系的目的都是要扩展对象的功能,但是 Decorato 更多
的灵活性。
2)把类中的装饰功能从类中搬移出去,这样可以简化原有的类。有效地把类的核心
功能和装饰功能区分开了。
3)通过使用不同的具体装饰类以及这些装饰类的排列组合,可创造出很多不同行为
的组合。
缺点
这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。
装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。
符合的设计原则:
多用组合,少用继承。利用继承设计子类的行为是在编译时静态决定的,且所有的
子类都会继承到相同的行为。如能够利用组合扩展对象的行为,就可在运行时动态进行
扩展。
类应设计的对扩展开放,对修改关闭。
4、典型应用
java IO 中需要完成对不同输入输出源的操作,如果单纯的使用继承这一方式,无疑需要
很多的类。比如说,我们操作文件需要一个类,实现文件的字节读取需要一个类,实现
文件的字符读取又需要一个类....一次类推每个特定的操作都需要一个特定的类。这无疑
会导致大量的 IO 继承类的出现。显然对于编程是很不利的。而是用装饰模式则可以很好
的解决这一问题,在装饰模式中:节点流(如 FileInputStream)直接与输入源交互,
之后通过过滤流(FilterInputStream)进行装饰,这样获得的 io 对象便具有某几个的功
能,很好的拓展了 IO 的功能。
本文来自博客园,作者:与乐i,转载请注明原文链接:https://www.cnblogs.com/linanana/p/12546073.html