1、什么是设计模式(Design Patterns)?
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。总体来说设计模式分为三大类:
创建型模式五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
singleton:单例用来减少垃圾对象和缓存
factory:工厂模式用来解耦
facade和decorator:封装接口
command:命令模式,传递
Iterator:用来遍历对象
Observer:观察者模式用来监听状态变化(现在习惯用listener机制替代)
templete:模板模式用来处理相同的操作步骤
strategy:策略模式,策略选择
proxy:用来附加功能,属性或隐蔽。
bridge:用来解耦工厂与产品搭配之类的选择
2、哪些设计模式可以增加系统的可扩展性?
工厂模式
抽象工厂模式
观察者模式:方便增加观察者和系统扩展
模板方法模式:方便实现不稳定的扩展点,完成功能的重用
适配器模式:方便适配其他接口
代理模式:方便在原来功能基础上增加功能或逻辑
责任链模式:方便增加拦截器/过滤器,实现对数据的处理,比如struts2的责任链
策略模式:通过新增策略从而改变原来的执行策略
3、除了单例模式,你在生产环境中还用过什么设计模式?
依赖注入、代理模式、策略模式、工厂模式,装饰模式或观察者模式。
4、写出几种Singleton单例模式
饿汉法:在第一次引用该类时就创建对象实例,不管实际是否需要创建。
public class Singleton {
private static Singleton = new Singleton();
private Singleton() {}
public static getSignleton(){
return singleton;
}
}
单线程写法:由私有构造器和一个公有静态工厂方法构成,在工厂方法中对singleton进行null判断,如果是null就new一个出来,返回singleton对象。这种方法可以实现延时加载,但有一个致命弱点:线程不安全。如果有两条线程同时调用getSingleton()方法,很可能导致重复创建对象。
public class Singleton {
private static Singleton singleton = null;
private Singleton(){}
public static Singleton getSingleton() {
if(singleton == null) singleton = new Singleton();
return singleton;
}
}
考虑线程安全的写法:将对singleton的null判断以及new的部分使用synchronized进行加锁。同时对singleton对象使用volatile关键字进行限制,保证其对所有线程的可见性,并且禁止对其进行指令重排序优化。
缺点:效率低下,每次调用getSingleton()方法,都必须在synchronized这里进行排队,而真正需要new的情况非常少。
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton(){}
public static Singleton getSingleton(){
synchronized(Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
return singleton;
}
}
兼顾线程安全和效率的写法:在getSingleton()方法中,进行两次null检查。看似多此一举,实际上却极大提升了并发度,进而提升了性能。在单例中new的情况非常少,绝大多数都可以并行的读操作。在加锁前多进行一次null检查可以减少加锁操作,有效的提高了执行效率。
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton(){}
public static Singleton getSingleton(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
静态内部类法:把Singleton实例放到一个静态内部类中,避免了静态实例在Singleton类加载时就创建对象,并且由于静态内部类只会被加载一次,所以这种写法也是线程安全的。
public class Singleton {
private static class Holder {
private static Singleton singleton = new Singleton();
}
private Singleton(){}
public static Singleton getSingleton(){
return Holder.singleton;
}
}
枚举写法:
public enum Singleton {
INSTANCE;
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
5、单例模式的双检锁是什么?
Java里面一个线程对共享变量的修改,对另外一个线程是不可预知的,除非使用同步,否则没法掌控。双检锁里面必须要加volatile关键字,主要用来避免重排序问题导致其他的线程看到了一个已经分配内存和地址但没有初始化的对象,也就是说这个对象还处于不可用状态,就被其他线程引用了。
双检索模式:使用延迟加载的情况下,获取对象实例先判断实例对象是否为空,不为空时在同步块内再次判断实例对象,以此保证只创建一个单例,且性能高;
6、什么是类的单例模式?
保证类在内存中只能有一个实例对象
7、适配器模式是什么?
适配器模式提供对接口的转换。如果你的客户端使用某些接口,但你有另外一些接口,就可以写一个适配器来连接这些接口。
8、适配器模式和代理模式之前有什么不同?
适配器模式和代理模式的区别在于他们的意图不同。由于适配器模式和代理模式都是封装真正执行动作的类,因此结构是一致的,但是适配器模式用于接口之间的转换,而代理模式则是增加一个额外的中间层,以便支持分配、控制或智能访问。
9、适配器模式和装饰器模式有什么区别?
适配器模式和装饰器模式的结构类似,但出现意图不同。
适配器模式被用于桥接两个接口,而装饰器模式的目的是在不修改类的情况下给类增加新的功能。
10、什么时候使用享元模式?
享元模式通过共享对象来避免创建太多的对象。为了使用享元模式,需要确保你的对象是不可变的,这样才能安全的共享。
JDK中String池、Integer池以及Long池都是使用享元模式的例子。
11、什么时候使用组合模式?
组合模式使用树结构来展示部分与整体的继承关系。它允许客户端采用统一的形式来对待单个对象和对象容器。
12、什么时候使用访问者模式?
访问者模式用于解决在类的继承层次上,增加操作但不直接与之关联。这种模式采用双派发的形式来增加中间层。
13、什么是模板方法模式?
模板方法提供算法的框架,你可以自己去配置或定义步骤。
例如,你可以将排序算法看做是一个模板。它定义了排序的步骤,但是具体的比较,可以使用Comparable或者其它语言中类似东西,具体策略由你去配置。列出算法概要的方法就是模板方法。
14、请给出一个符合开闭原则的设计模式的例子
开闭原则要求你的代码对扩展开放,对修改关闭。意思就是说,如果想增加一个新的功能,可以很容易的在不改变已测试过的代码的前提下增加新的代码。
策略模式就是基于开闭原则的,如果你需要一个新的策略,只需要实现接口,增加配置,不需要改变核心逻辑。
Collections.sort()就是基于策略模式,遵循开闭原则的,不需为新的对象修改sort(),你需要做的仅仅是实现自己的Comparator接口。