java-设计模式面试题集(一)
1、设计模式有哪些原则?
开闭原则: OOP 中最基础的原则,指⼀个软件实体(类、模块、⽅法等)应该对扩展开放,对修改关闭。 强调⽤抽象构建框架,⽤实现扩展细节,提⾼代码的可复⽤性和可维护性。
单⼀职责原则: ⼀个类、接⼝或⽅法只负责⼀个职责,降低代码复杂度以及变更引起的⻛险。
依赖倒置原则: 程序应该依赖于抽象类或接⼝,⽽不是具体的实现类。
接⼝隔离原则:将不同功能定义在不同接⼝中实现接⼝隔离,避免了类依赖它不需要的接⼝,减少了接⼝之间依赖的冗余性和复杂性。
⾥⽒替换原则:开闭原则的补充,规定了任何⽗类可以出现的地⽅⼦类都⼀定可以出现,可以约束继承泛滥,加强程序健壮性。
迪⽶特原则:也叫最少知道原则,每个模块对其他模块都要尽可能少地了解和依赖,降低代码耦合度。
合成/聚合原则:尽量使⽤组合(has-a)/聚合(contains-a)⽽不是继承(is-a)达到软件复⽤的⽬的,避免滥⽤继承带来的⽅法污染和⽅法爆炸,⽅法污染指⽗类的⾏为通过继承传递给⼦类,但⼦类并不具备执⾏此⾏为的能⼒;⽅法爆炸指继承树不断扩⼤,底层类拥有的⽅法过于繁杂,导致很容易选择错误。
2、设计模式的分类,你知道哪些设计模式?
创建型: 在创建对象的同时隐藏创建逻辑,不使⽤ new 直接实例化对象,程序在判断需要创建哪些对象时更灵活。 包括⼯⼚/抽象⼯⼚/单例/建造者/原型模式。
结构型: 通过类和接⼝间的继承和引⽤实现创建复杂结构的对象。 包括适配器/桥接模式/过滤器/组合/装饰器/外观/享元/代理模式。
⾏为型: 通过类之间不同通信⽅式实现不同⾏为。 包括责任链/命名/解释器/迭代器/中介者/备忘录/观察者/状态/策略/模板/访问者模式。
3、简单工厂模式?
简单⼯⼚模式指 **由⼀个⼯⼚对象来创建实例,客户端不需要关注创建逻辑,只需提供传⼊⼯⼚的参数。
** 适⽤于⼯⼚类负责创建对象较少的情况,缺点是如果要增加新产品,就需要修改⼯⼚类的判断逻辑,违背开闭原则,且产品多的话会使⼯⼚类⽐较复杂。
Calendar 抽象类的 getInstance ⽅法,调⽤ createCalendar ⽅法根据不同的地区参数创建不同的⽇历对象。Spring 中的 BeanFactory 使⽤简单⼯⼚模式,根据传⼊⼀个唯⼀的标识来获得 Bean 对象。
4、说⼀说⼯⼚⽅法模式?
⼯⼚⽅法模式指定义⼀个创建对象的接⼝,让接⼝的实现类决定创建哪种对象,让类的实例化推迟到⼦类中进⾏。
客户端只需关⼼对应⼯⼚⽽⽆需关⼼创建细节,主要解决了产品扩展的问题,在简单⼯⼚模式中如果产品种类变多,⼯⼚的职责会越来越多,不便于维护。
Collection 接⼝这个抽象⼯⼚中定义了⼀个抽象的 iterator ⼯⼚⽅法,返回⼀个 Iterator 类的抽象产品。该⽅法通过 ArrayList 、HashMap 等具体⼯⼚实现,返回 Itr、KeyIterator 等具体产品。
Spring 的 FactoryBean 接⼝的 getObject ⽅法也是⼯⼚⽅法。
5、抽象工厂模式?
抽象⼯⼚模式指提供⼀个创建⼀系列相关或相互依赖对象的接⼝,⽆需指定它们的具体类。
客户端不依赖于产品类实例如何被创建和实现的细节,主要⽤于系统的产品有多于⼀个的产品族,⽽系统只消费其中某⼀个产品族产品的情况。
抽象⼯⼚模式的缺点是不⽅便扩展产品族,并且增加了系统的抽象性和理解难度。
java.sql.Connection 接⼝就是⼀个抽象⼯⼚,其中包括很多抽象产品如 Statement、Blob、Savepoint等。
6、单例模式的特点?
单例模式属于创建型模式,⼀个单例类在任何情况下都只存在⼀个实例,构造⽅法必须是私有的、由⾃⼰创建⼀个静态变量存储实例,对外提供⼀个静态公有⽅法获取实例。
优点是内存中只有⼀个实例,减少了开销,尤其是频繁创建和销毁实例的情况下并且可以避免对资源的多重占⽤。
缺点是没有抽象层,难以扩展,与单⼀职责原则冲突。
Spring 的 ApplicationContext 创建的 Bean 实例都是单例对象,还有 ServletContext、数据库连接池等也都是单例模式。
7、单例模式有哪些实现?
饿汉式:在类加载时就初始化创建单例对象,线程安全,但不管是否使⽤都创建对象可能会浪费内存。
public class HungrySingleton {
private HungrySingleton(){}
private static HungrySingleton instance = new HungrySingleton();
public static HungrySingleton getInstance() {
return instance;
}
}
懒汉式:在外部调⽤时才会加载,线程不安全,可以加锁保证线程安全但效率低。
public class LazySingleton {
private LazySingleton(){}
private static LazySingleton instance;
public static LazySingleton getInstance() {
if(instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
双重检查锁:使⽤ volatile 以及多重检查来减⼩锁范围,提升效率。
public class DoubleCheckSingleton {
private DoubleCheckSingleton(){}
private volatile static DoubleCheckSingleton instance;
public static DoubleCheckSingleton getInstance() {
if(instance == null) {
synchronized (DoubleCheckSingleton.class)
{
if (instance == null) {
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
静态内部类:同时解决饿汉式的内存浪费问题和懒汉式的线程安全问题。
public class StaticSingleton {
private StaticSingleton(){}
public static StaticSingleton getInstance() {
return StaticClass.instance;
}
private static class StaticClass {
private static final StaticSingleton instance = new StaticSingleton();
}
}
枚举:《Effective Java》提倡的⽅式,不仅能避免线程安全问题,还能防⽌反序列化重新创建新的对象,绝对防⽌多次实例化,也能防⽌反射破解单例的问题。
public enum EnumSingleton {
INSTANCE;
}
8、代理模式?
代理模式属于结构型模式,为其他对象提供⼀种代理以控制对这个对象的访问。 优点是可以增强⽬标对象的功能,降低代码耦合度,扩展性好。缺点是在客户端和⽬标对象之间增加代理对象会导致请求处理速度变慢,增加系统复杂度。
Spring 利⽤动态代理实现 AOP,如果 Bean 实现了接⼝就使⽤ JDK 代理,否则使⽤ CGLib 代理。
静态代理:**代理对象持有被代理对象的引⽤,调⽤代理对象⽅法时也会调⽤被代理对象的⽅法,但是会在被代理对象⽅法的前后增加其他逻辑。需要⼿动完成,在程序运⾏前就已经存在代理类的字节码⽂件,代理类和被代理类的关系在运⾏前就已经确定了。 缺点是⼀个代理类只能为⼀个⽬标服务,如果要服务多种类型会增加⼯作量。
动态代理:动态代理在程序运⾏时通过反射创建具体的代理类,代理类和被代理类的关系在运⾏前是不确定的。**动态代理的适⽤性更强,主要分为 JDK 动态代理和 CGLib 动态代理。
JDK 动态代理:通过 Proxy 类的 newInstance ⽅法获取⼀个动态代理对象,需要传⼊三个参数,被代理对象的类加载器、被代理对象实现的接⼝,以及⼀个 InvocationHandler 调⽤处理器来指明具体的逻辑,相⽐静态代理的优势是接⼝中声明的所有⽅法都被转移到InvocationHandler 的 invoke ⽅法集中处理。
CGLib 动态代理:JDK 动态代理要求实现被代理对象的接⼝,⽽ CGLib 要求继承被代理对象,如果⼀个类是 final 类则不能使⽤ CGLib 代理。两种代理都在运⾏期⽣成字节码,JDK 动态代理直接写字节码,⽽ CGLib 动态代理使⽤ ASM 框架写字节码,ASM 的⽬的是⽣成、转换和分析以字节数组表示的已编译 Java 类。 JDK 动态代理调⽤代理⽅法通过反射机制实现,⽽ GCLib 动态代理通过 FastClass 机制直接调⽤⽅法,它为代理类和被代理类各⽣成⼀个类,该类为代理类和被代理类的⽅法分配⼀个 int 参数,调⽤⽅法时可以直接定位,因此调⽤效率更⾼。