设计模式理论
1.什么是设计模式?你是否在你的代码里面使用过任何设计模式?
在软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。
平时用的比较多有单例模式(在内存中仅实例化一个对象时使用),适配器模式(典型的就是ListView和GridView的适配器),建造者模式(AlertDialog.Builder),观察者模式可能比较隐蔽,在Android源码中BaseAdapater的NotifyDataSetChanged的实现(?)。
2.你可以说出几个在JDK库中使用的设计模式吗?
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | Birdge 桥接模式: 这个模式将抽象和抽象操作的实现进行了解耦,这样使得抽象和实现可以独立地变化。 GOF在提出桥梁模式的时候指出,桥梁模式的用意是 "将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化" 。这句话有三个关键词,也就是抽象化、实现化和脱耦。 在Java应用中,对于桥接模式有一个非常典型的例子,就是应用程序使用JDBC驱动程序进行开发的方式。所谓驱动程序,指的是按照预先约定好的接口来操作计算机系统或者是外围设备的程序。 Adapter 适配器模式: 用来把一个接口转化成另一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。 java.util.Arrays#asList() java.io.InputStreamReader(InputStream) java.io.OutputStreamWriter(OutputStream) Composite 组合模式: 又叫做部分-整体模式,使得客户端看来单个对象和对象的组合是同等的。换句话说,某个类型的方法同时也接受自身类型作为参数。 avax.swing.JComponent#add(Component) java.util.Map#putAll(Map) java.util.List#addAll(Collection) java.util.Set#addAll(Collection) 装饰者模式: 动态的给一个对象附加额外的功能,这也是子类的一种替代方式。可以看到,在创建一个类型的时候,同时也传入同一类型的对象。这在JDK里随处可见,你会发现它无处不在,所以下面这个列表只是一小部分。 java.io.BufferedInputStream(InputStream) java.io.DataInputStream(InputStream) java.io.BufferedOutputStream(OutputStream) java.util.zip.ZipOutputStream(OutputStream) java.util.Collections#checkedList|Map|Set|SortedSet|SortedMap Facade 门面模式,即外观模式: 给一组组件,接口,抽象,或者子系统提供一个简单的接口。 java.lang.Class javax.faces.webapp.FacesServlet Flyweight 享元模式: 使用缓存来加速大量小对象的访问时间。 java.lang.Integer#valueOf( int ) java.lang.Boolean#valueOf( boolean ) java.lang.Byte#valueOf( byte ) java.lang.Character#valueOf( char ) Proxy 代理模式: 代理模式是用一个简单的对象来代替一个复杂的或者创建耗时的对象。 java.lang.reflect.Proxy RMI Abstract Factory 抽象工厂模式: 抽象工厂模式提供了一个协议来生成一系列的相关或者独立的对象,而不用指定具体对象的类型。它使得应用程序能够和使用的框架的具体实现进行解耦。这在JDK或者许多框架比如Spring中都随处可见。它们也很容易识别,一个创建新对象的方法,返回的却是接口或者抽象类的,就是抽象工厂模式了。 java.util.Calendar#getInstance() java.util.Arrays#asList() java.util.ResourceBundle#getBundle() java.sql.DriverManager#getConnection() java.sql.Connection#createStatement() java.sql.Statement#executeQuery() java.text.NumberFormat#getInstance() javax.xml.transform.TransformerFactory#newInstance() 抽象工厂模式 抽象工厂模式提供了一个协议来生成一系列的相关或者独立的对象,而不用指定具体对象的类型。它使得应用程序能够和使用的框架的具体实现进行解耦。这在JDK或者许多框架比如Spring中都随处可见。它们也很容易识别,一个创建新对象的方法,返回的却是接口或者抽象类的,就是抽象工厂模式了。 java.util.Calendar#getInstance() java.util.Arrays#asList() java.util.ResourceBundle#getBundle() java.sql.DriverManager#getConnection() java.sql.Connection#createStatement() java.sql.Statement#executeQuery() java.text.NumberFormat#getInstance() javax.xml.transform.TransformerFactory#newInstance() Builder 建造者模式: 定义了一个新的类来构建另一个类的实例,以简化复杂对象的创建。建造模式通常也使用方法链接来实现。 java.lang.StringBuilder#append() java.lang.StringBuffer#append() java.sql.PreparedStatement javax.swing.GroupLayout.Group#addComponent() 工厂方法: 就是一个返回具体对象的方法。 java.lang.Proxy#newProxyInstance() java.lang.Object#toString() java.lang.Class#newInstance() java.lang.reflect.Array#newInstance() java.lang.reflect.Constructor#newInstance() java.lang.Boolean#valueOf(String) java.lang.Class#forName() 原型模式 使得类的实例能够生成自身的拷贝。如果创建一个对象的实例非常复杂且耗时时,就可以使用这种模式,而不重新创建一个新的实例,你可以拷贝一个对象并直接修改它。 java.lang.Object#clone() java.lang.Cloneable 单例模式 用来确保类只有一个实例。Joshua Bloch在Effetive Java中建议到,还有一种方法就是使用枚举。 java.lang.Runtime#getRuntime() java.awt.Toolkit#getDefaultToolkit() java.awt.GraphicsEnvironment#getLocalGraphicsEnvironment() java.awt.Desktop#getDesktop() 责任链模式 通过把请求从一个对象传递到链条中下一个对象的方式,直到请求被处理完毕,以实现对象间的解耦。 java.util.logging.Logger#log() javax.servlet.Filter#doFilter() 命令模式 将操作封装到对象内,以便存储,传递和返回。 java.lang.Runnable javax.swing.Action 解释器模式 这个模式通常定义了一个语言的语法,然后解析相应语法的语句。 java.util.Pattern java.text.Normalizer java.text.Format 迭代器模式 提供一个一致的方法来顺序访问集合中的对象,这个方法与底层的集合的具体实现无关。 java.util.Iterator java.util.Enumeration 中介者模式 通过使用一个中间对象来进行消息分发以及减少类之间的直接依赖。 java.util.Timer java.util.concurrent.Executor#execute() java.util.concurrent.ExecutorService#submit() java.lang.reflect.Method#invoke() 备忘录模式 生成对象状态的一个快照,以便对象可以恢复原始状态而不用暴露自身的内容。Date对象通过自身内部的一个 long 值来实现备忘录模式。 java.util.Date java.io.Serializable 空对象模式 这个模式通过一个无意义的对象来代替没有对象这个状态。它使得你不用额外对空对象进行处理。 java.util.Collections#emptyList() java.util.Collections#emptyMap() java.util.Collections#emptySet() 观察者模式 它使得一个对象可以灵活的将消息发送给感兴趣的对象。 java.util.EventListener javax.servlet.http.HttpSessionBindingListener javax.servlet.http.HttpSessionAttributeListener javax.faces.event.PhaseListener 状态模式 通过改变对象内部的状态,使得你可以在运行时动态改变一个对象的行为。 java.util.Iterator javax.faces.lifecycle.LifeCycle#execute() 策略模式 使用这个模式来将一组算法封装成一系列对象。通过传递这些对象可以灵活的改变程序的功能。 java.util.Comparator#compare() javax.servlet.http.HttpServlet javax.servlet.Filter#doFilter() 模板方法模式 让子类可以重写方法的一部分,而不是整个重写,你可以控制子类需要重写那些操作。 java.util.Collections#sort() java.io.InputStream#skip() java.io.InputStream#read() java.util.AbstractList#indexOf() 访问者模式 提供一个方便的可维护的方式来操作一组对象。它使得你在不改变操作的对象前提下,可以修改或者扩展对象的行为。 javax.lang.model.element.Element and javax.lang.model.element.ElementVisitor javax.lang.model.type.TypeMirror and javax.lang.model.type.TypeVisitor |
3.Java中什么是单例设计模式?用Java写出线程安全的单例。
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
使用单例模式最核心的一点是体现了面向对象封装特性中的“单一职责”和“对象自治”原则。
并且可以节省系统开销。
4.使用工厂模式最主要的好处是什么?你在哪里使用?
各模式的理解:
简单工厂:把对象的创建放到一个工厂类中,通过参数来创建不同的对象。
工厂方法:每种产品由一种工厂来创建。(不这样会有什么问题?)
抽象工厂:感觉只是工厂方法的复杂化,产品系列复杂化的工厂方法。
面向接口编程:设计模式的一个重要原则是 针对接口编程,不要依赖实现类。工厂模式遵循了这一个原则。
开闭原则(Open-Closed Principle,OCP) “Software entities should be open for extension,but closed for modification”。翻译过来就是:“软件实体应当对扩展开放,对修改关闭”。这句话说得略微有点专业,我们把它讲得更通俗一点,也就是:软件系统中包含的各种组件,例如模块(Modules)、类(Classes)以及功能(Functions)等等,应该在不修改现有代码的基础上,引入新功能。开闭原则中“开”,是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的;开闭原则中“闭”,是指对于原有代码的修改是封闭的,即不应该修改原有的代码。
使用工厂的理由:
A.把对象的创建集中在一个地方(工厂中),在增加新的对象类型的时候,只需要改变工厂方法;否则在应用中四处散布对象创建逻辑,如果创建方法改变时则需要四处修改,维护量增加.
B.应用的场合是新的对象类型很可能经常被添加进来.
C.你所关心的仅仅是工厂方法返回的接口方法,不必关心实现细节
5.在Java中,什么叫观察者设计模式(observer design pattern)?
观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
一个软件系统常常要求在某一个对象的状态发生变化的时候,某些其它的对象做出相应的改变。做到这一点的设计方案有很多,但是为了使系统能够易于复用,应该选择低耦合度的设计方案。减少对象之间的耦合有利于系统的复用,但是同时设计师需要使这些低耦合度的对象之间能够维持行动的协调一致,保证高度的协作。
6.举一个用Java实现的装饰模式(decorator design pattern)?它是作用于对象层次还是类层次?
动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。装饰模式是一种用于替代继承的技术,使用对象之间的关联关系取代类之间的继承关系。在装饰模式中引入了装饰类,在装饰类中既可以调用待装饰的原有类的方法,还可以增加新的方法,以扩充原有类的功能。
装饰原有对象、在不改变原有对象的情况下扩展增强新功能/新特征.。当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使用装饰模式。
7.什么是MVC设计模式?举一个MVC设计模式的例子?
MVC是一个设计模式,它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件:模型、视图、控制器。它们各自处理自己的任务。
8.Java中什么是表示层设计模式(FrontController design controller pattern)的例子?
FrontController(前端控制器)模式
控制器不仅是接触请求的起点,它还是我们编程量最多和设计自由度最大的地方。前端控制器(Front Controller)模式主张构建一个单独的控制器执行每个请求中都需要的公共功能,而将其他的功能委托给与页面相关的控制器。前端控制器(Front Controller)提供了一个统一的位置来封装公共请求处理。前端控制器模式中的主要参与者是控制器本身。它的工作相当简单:执行公共的任务,然后把控制转移给与页面相关的控制器。
页面控制器执行模型更新和视图选择,实际上有些类似MVC模式中的控制器角色。页面控制器可以是完全独立的servlet,但是它们通常都按照GOF提出的命令(Command)模式实现为许多个更简单的类。在这种设计中,许多不同种类的页面控制器共享一个简单的公共接口,而且通常被称作“动作”(源于Command模式)。因为前端控制器选择了页面控制器,所以页面控制器最终负责选择正确的动作和视图。在一个Web应用中,前端控制器几乎总是用一个Servlet来实现。虽然使用JSP页面在技术上也是可行的,但这通常是一个坏主意——JSP页面不应该用于实现复杂的逻辑。第二种更引人注目的选择是使用一个servlet过滤器作为前端控制器。
9.什么是责任链模式(Chain of Responsibility)?
模式的定义
一个请求沿着一条“链”传递,直到该“链”上的某个处理者处理它为止。
模式的使用场景
一个请求可以被多个处理者处理或处理者未明确指定时。
责任链模式非常简单异常好理解,相信我它比单例模式还简单易懂,其应用也几乎无所不在,甚至可以这么说,从你敲代码的第一天起你就不知不觉用过了它最原始的裸体结构:分支语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class SimpleResponsibility { public static void main(String[] args) { int request = ( int ) (Math.random() * 3 ); switch (request) { case 0 : System.out.println( "SMBother handle it: " + request); break ; case 1 : System.out.println( "Aige handle it: " + request); break ; case 2 : System.out.println( "7Bother handle it: " + request); break ; default : break ; } } } |
10.什么是适配器模式?举用Java实现适配器模式的例子?
好文:一个示例让你明白适配器模式
A.适配器必须实现原有的旧的接口
B.适配器对象中持有对新接口的引用,当调用旧接口时,将这个调用委托给实现新接口的对象来处理,也就是在适配器对象中组合一个新接口。
11.什么是代理模式?
代理(proxy)模式:指目标对象给定代理对象,并由代理对象代替真实对象控制客户端对真实对象的访问。
代理模式模式有以下角色:
抽象主题(subject)角色:声明真实主题和代理主题的共同接口。
真实主题(real subject)角色:定义代理对象需要代理的真实对象。
代理主题(proxy subject)角色:代替真实对象来控制对真实对象的访问,代理对象持有真实对象的应用,从而可以随时控制客户端对真实对象的访问。
12.策略模式有什么好处?
策略模式:
举例:Java.util.List就是定义了一个增(add)、删(remove)、改(set)、查(indexOf)策略,至于实现这个策略的ArrayList、LinkedList等类,只是在具体实现时采用了不同的算法。但因为它们策略一样,不考虑速度的情况下,使用时完全可以互相替换使用。
简介:定义了一系列封装了算法、行为的对象,他们可以相互替换。
结合软件设计等的面试问题
//这是一些和设计模式还有软件设计都相关的问题。这些问题需要一些思考和经验来回答。在大多数情况下,面试官并不是需要一个确切的答案,而是希望听到你的想法,你对这个问题是怎么考虑的,你能不能想通这个问题,能不能挖掘一些没有告诉你的潜在信息。在解决一个问题时你应该考虑什么等等可以使你经验大涨。总的来说,这些设计问题会让你动起脑来。有时面试官也会让你写代码,所以还是准备一下吧。如果你知道编程和设计技巧中的概念,例子和程序,你应该可以在这些问题中有突出的表现。
1.举出一个例子,在这种情况你会更倾向于使用抽象类,而不是接口?
接口和抽象类都遵循”面向接口而不是实现编码”设计原则,它可以增加代码的灵活性,可以适应不断变化的需求。下面有几个点可以帮助你回答这个问题:
在Java中,你只能继承一个类,但可以实现多个接口。所以一旦你继承了一个类,你就失去了继承其他类的机会了。
接口通常被用来表示附属描述或行为如:Runnable、Clonable、Serializable等等,因此当你使用抽象类来表示行为时,你的类就不能同时是Runnable和Clonable(译者注:这里的意思是指如果把Runnable等实现为抽象类的情况),因为在Java中你不能继承两个类,但当你使用接口时,你的类就可以同时拥有多个不同的行为。
在一些对时间要求比较高的应用中,倾向于使用抽象类,它会比接口稍快一点。
如果希望把一系列行为都规范在类继承层次内,并且可以更好地在同一个地方进行编码,那么抽象类是一个更好的选择。有时,接口和抽象类可以一起使用,接口中定义函数,而在抽象类中定义默认的实现。
2.设计一个贩卖机,可以接收不同的货币,出售不同的产品?
这是一个开放设计问题,你可以作为练习,尝试着写出设计文档、代码和JUnit测试而不是只是解决这个问题,看看它花了你多少时间得到解决方案和得到需要的原形。理想情况下,这个问题应该可以在3个小时内解决,至少应该得到一个可以运行的版本。
3.你有一个Smartphone类,可以派生如IPhone、AndroidPhone、WindowsMobilePhone类
它还可以是一些有着品牌的手机名称,你会怎么设计这个类系统呢。
这是另外一个设计模式练习,你可以应用你的面向对象设计技巧来得到一个设计,这个设计需要足够灵活能够支持未来产品的扩展,足够稳定能够支持在现有模型进行修改。
4.在Java中,什么时候用重载,什么时候用重写?
对有经验的Java设计师来说,这是一个相当简单的问题。
如果你看到一个类的不同实现有着不同的方式来做同一件事,那么就应该用重写(overriding),而重载(overloading)是用不同的输入做同一件事。在Java中,重载的方法签名不同,而重写并不是。
5.设计一个ATM机?
我们所有人都使用ATM(自动柜员机)。想想你会怎么设计一个ATM?就设计金融系统来说,必须知道它们应该在任何情况下都能够如期工作。不管是断电还是其他情况,ATM应该保持 正确的状态(事务) , 想想 加锁(locking)、事务(transaction)、错误条件(error condition)、边界条件(boundary condition) 等等。尽管你不能想到具体的设计,但如果你可以指出非功能性需求,提出一些问题,想到关于边界条件,这些都会是很好的一步。
6.你正在写一些类提供市场数据,你知道你可以不定时切换不同的厂商如Reuters、wombat或者直接的批发商, 你会如何设计你的市场数据系统。
这是一个非常有趣的设计面试问题,并且真的在一家大的投资银行问到过,如果你是用Java编码的话这是一个相当平常的场景。最主要的一点是你要有一个MarketData接口,它会有调用端需要的方法如:getBid()、getPrice()、getLevel()等等,而MarketData应该由一个MarketDataProvider通过 依赖注入(dependency injection) 组成。因此,当你修改你的MarketData 提供器(MarketDataProvider)时,调用端不会受影响,因为它们是通过MarketData接口或类的方法来访问的。
7.在Java中,为什么不允许从静态方法中访问非静态变量?
你在Java中不能从静态上下文访问非静态数据只是因为非静态变量是跟具体的对象实例关联的,而静态的却没有和任何实例关联。
有这样一段话,“由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。” 怎么深层次理解这句话呢?
深层次的解释是,“静态方法可以不通过对象进行调用”也就是说这个方法在对象尚未创建的时候就可以调用,而此时对象尚未创建,(非静态)成员变量根本都还不存在,何谈访问?
如果允许调用其他非静态变量,会引起什么后果么?
不是允许不允许的问题,是这个时候非静态成员变量都还不存在(他是伴随着对象的创建而创建的),根本无法访问。
8.在Java中设计一个并发规则的pipeline?
并发编程或并发设计这些天很火,它可以充分利用现在不断提升的高级处理器的处理能力,而Java成为一个多线程语言也从这种情况获益良多。设计一个并发系统需要记住的最关键的点是线程安全,不可变性,本地变量和避免使用static或者类变量(instance variables)。你只需要想着每一类都可以同时被多个线程同时执行,所以最好的做法就是每一个线程都处理自己的数据 ,不跟其他数据交互,并且运行时只需要最小的同步保证。这个问题可以涉及到从最初的讨论到完整的类和接口编码,但只要你记住并发中最重要的点和问题如,竞争条件(race condition)、死锁(deadlock)、内存交互问题(memory interference)、原子性、ThreadLocal变量等,你都可以回答它。
9.工厂模式在开发中的运用
面试题:写一个简单的计算器。
10.Windows Media Player和RealPlayer是常用的媒体播放器,它们的API结构和调用方法非常不同,现在你的应用需要同时支持调用这2种播放器的API。你要怎么设计?