***常用哪些设计模式及使用场景
所谓设计模式,是被多数人验证过的、可重用性强、可靠性强, 容易被人理解的经验的总结。话说 Java 常用的设计模式有 23 种(也有说 24 种) 之多,而我们普通的工作中最经常用到的有:单例模式、工厂模式、代理模式、策略模式等。
单例模式:一般有常用的有“懒汉式”和“饥饿式”两种写法(写法在笔试部分会提到),但无论哪种想法都不要忽略一点,线程安全问题(单例模式也很容易线程不安全,而且不容易重易错误);使用场景:通常用于系统启动时一些配置文件的初始化管理,或者实例化一个工厂。
工厂模式:工厂模式一般分为静态工厂和动态工厂或者分为简单工厂和复杂工厂甚至是抽象工厂(详见附属文档),也是我们开发中最常用的设计模式之一;使用场景:创建一系列相互依赖的结构相似的对象(比如 Hibernate 中的 Sesson就是工厂类生产出来的)。
代理模式:代理模式为静态代理,动态代理;而动代理又分为 JDK 动态代理和CGLIB 动态代理。除 cglib 实现的动态代理外,都需要基于接口实现。使用场景:业务逻辑的接口不希望直接暴露给客户端调用,采用委托的方式来隐藏真正的实现,可以使用代理模式。
策略模式:使用场景,多个类只区别在表现行为不同,在运行时动态选择具体要执行的行为,隐藏具体策略(算法)的实现细节,彼此完全独立等。
***单例模式
解析:单例模式作为系统设计者一般都会用到的设计模式,即一个类只有一个实例,而 且这个实例通常一旦创建就一直活着,不会被 GC。单例模式通常有两种写法,就是我们常说的懒汉式和饿汉式。
参考答案:
单例模式指一个类通常只被实例化一次,通常用于系统资源实始化加载;
1 //常用两种写法为懒汉式和饿汉式。
2
3 //懒汉式
4 public class SingletonLazy {
5 private static SingletonLazy instance=null;
6 private SingletonLazy() {}
7 public static SingletonLazy getInstance() {
8 if (instance == null) { //在第一次调用时初始化,只实例化一次
9 instance = new SingletonLazy();
10 }
11 return instance;
12 }
13 }
14 //饿汉式
15 public class SingletonHungry {
16 //系统加载时初始化,只初始化一次
17 private static SingletonHungry instance = newSingletonHungry();
18 private SingletonHungry() {}
19 public static SingletonHungry getInstance() {
20 return instance;
21 }
22 }
23 //JDK 中有哪些类是基于单例实现的,大家看一下 Runtime
扩展:如果在并发环境下使用单例模式,以上代码肯定会存在并发问题,可能创建多个 实例出来,大家结合多线程锁的概念,自己完善一下安全的单例写法(加下锁)。
***工厂模式
解析:工厂模式应该是 Java 学习者接触的第一个设计模式,工厂模式主要分为“简单工厂模式”、“工厂方法模式”、“抽象工厂模式”,后面两种工厂模式在【24 种设计模式介绍及6 大设计原则.pdf】文档中已经有很详细的描述,请参考,这里省略示例代码。
参考答案:
根据工作中常用设计模式归类,工厂模式可以分为三类“简单工厂模式”、 “工厂方法模式”、“抽象工厂模式”,其中“简单工厂模式”并未归到 23 种 GOF 设计模式之一,它是另外两种工厂模式的基础,而且在小项目开发中使用广泛。
简单工厂模式:属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族 中最简单实用的模式。
优点:容易实现、使用者只需要关注调用的接口,不需要关心具体实现。
缺点:实现类的增减都会牵扯到工厂类的修改,工厂类偶合了实例的业务逻辑,不符合高内聚低耦合的原则,也违背了开放封闭原则。
工厂方法模式:就是我们通常简称的工厂模式,它属于类创建型模式,也是处理在不指定对象具体类型的情况下创建对象的问题。实质(使用)定义一个创建对象的接口, 但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。”
优点:实现简单,符合开闭原则 。
缺点:新增实现,会导到工厂类大量增加,不符合优化原则 。
抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
优点:隔离了具体类的生成,使得客户并不需要知道什么被创建,而且每次可以通 过具体工厂类创建一个产品族中的多个对象,增加或者替换产品族比较方便。
缺点: 结构复杂,实现难度相对来说高。
说明:各位单看上面的概念,是不是云里雾里,如果单看概念肯定一点实质感没有,建议结合实例理解的基础参考上面的概念性东西,组织一下在面试中用自己理解的话来回答。
***代理模式
解析:代理模式分为”静态代理”和“动态代理”,而动态代理又分为“JDK 动态代理”和“CGLIB 动态代理”。静态代理在给大家的文档中,看过的对潘金莲那章节应该印象深刻,这里主要说一下 JDK 和 GCLIB 两种方式(在前面 AOP 中已经提到过,这里再强调一下),也是 AOP 的核心。
参考答案:
动态代理可以分为”静态代理”和“动态代理”,动态代理又分为“JDK 动态代理” 和“CGLIB 动态代理”实现。
静态代理:(如上图)代理对象和实际对象都继续了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object。
优点:可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。
缺点:不同的接口要有不同的代理类实现,会很冗余。
JDK 动态代理:为了解决静态代理中,生成大量的代理类造成的冗余;JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现,从代理类的Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInte rfaces(), this);获取方式来看,必须传入实现的接口。
优点:解决了静态代理中冗余的代理实现类问题。
缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,但会抛异常。
CGLIB 代理:由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来 完成动态代理的实现。实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过Enhancer 类的回调方法来实现。
优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
缺点:技术实现相对难理解些。
***适配模式
解析:本想介绍一下策略模式,但策略模式很 Easy 大家看一眼就能理解了,参见一下我们附件中【24 种设计模式介绍及 6 大设计原则.pdf】,结合实际工作需要来说,适配器模式用到的还是比较多,尤其是老项目扩充新功能中常遇见。
场景举例:我们管理中心对外暴露一个用户上传的接口给企业上报用户使用,但企业用户的信息存储与我们的不符合,比如我们地域存的是省市县三级三个字段,他们采用的是空格隔开的一个字段,我们要不修改业务逻辑情况下保存他们上报过来的数据,就需要在我们原有的接口之上加一级适配,将他们的数据转换成我们业务可以使用的格式。
参考答案:
适配模式可以分为“类适配”和“对象适配”两种方式,这里要介绍场景举例说明,以上场景为例:管理中心的接口 target(源接口),对外提供的用户上传接口为 outOjb 接口,实现类 adaptee,适配类 adapter。
类适配:适配类 adapter 需要继承 adaptee 类同时实现 target 源接口,在适配类中完成数据格式转化。
对象适配:与类适配不一样的是,不需要继续 adaptee,适配类 adapter 只要实现 target源接口,在 adapter 类中传入 outojb 的实现实例 adaptee 对象便可。