Java岗大厂面试百日冲刺【Day30】—— 设计模式1
本文已获得原作者 _陈哈哈 授权并经过重新整理规划后发布。
本栏目Java开发岗高频面试题主要出自以下各技术栈:Java基础知识、集合容器、并发编程、JVM、Spring全家桶、MyBatis等ORMapping框架、MySQL数据库、Redis缓存、RabbitMQ消息队列、Linux操作技巧等。
面试题1:面向对象程序设计(OOP)的六大原则分别有哪几个
- 开闭原则(Open Close Principle)及“开放-封闭原则”
- 单一职责原则(Single Responsibility Principle)
- 里氏替换原则(Liskov Substitution Principle)
- 依赖倒置原则(Dependence Inversion Principle)
- 接口隔离原则(Interface Segregation Principle)
- 最少知识原则(Law Of Demeter)
面向对象程序设计中,需要遵守的原则主要有6个,称为六大原则。面向对象程序设计原则也是我们用于评价一个设计模式的重要指标之一。在设计模式中,很多设计模式都遵守了这些原则。
原则名称 | 内容说明 |
---|---|
开闭原则 | 对扩展开放,对修改关闭。 |
单一职责原则 | 即一个类只负责相应领域的职责,即不要存在多于一个导致类变更的原因。 |
里氏代换原则 | 子类型必须能够替换它们的父类型。一个软件实体如果使用的是一个父类,那么当把这个父类替换成继承该父类的子类,程序的行为不会发生任何变化。软件实体察觉不出父类对象和子类对象的区别。 |
依赖倒置原则 | 要依赖于抽象,不要依赖于具体。客户端依赖于抽象耦合。抽象不应当依赖于细节;细节应当依赖于抽象;要针对接口编程,不针对实现编程。 |
接口隔离原则 | 客户端不应该依赖它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上。 |
最少知识原则 | 对象与对象之间应该使用尽可能少的方法来关联,避免千丝万缕的关系。 |
面试题2:你说一下什么是设计模式
设计模式(Design pattern) 是解决软件开发某些特定问题而提出的一些解决方案也可以理解成解决问题的一些思路。通过设计模式可以帮助我们增强代码的可重用性、可扩充性、 可维护性、灵活性。我们使用设计模式最终的目的是实现代码的 高内聚 和低耦合。
追问1:那你怎么理解高内聚和低耦合?
耦合:
也称块间联系。指软件系统结构中各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差
。
内聚:
故名思议,表示内部间聚集、关联的程度,那么高内聚就是指要高度的聚集和关联。内聚是从功能角度来度量模块内的联系
,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。
高内聚低耦合
,是软件工程中的概念,是判断软件设计好坏的标准,主要用于程序的面向对象的设计,主要看类的内聚性是否高,耦合度是否低
。目的是使程序模块的可重用性、移植性大大增强。通常程序结构中各模块的内聚程度越高,模块间的耦合程度就越低。
面试题3:设计模式有哪几种?
总体来说设计模式分为三大类:
创建型模式
,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。结构型模式
,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。行为型模式
,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
除了23种常见设计模式外,其实还有两类:并发型模式和线程池模式。
模式类型 | 名称 | 定义 |
---|---|---|
创建型 | 工厂方法模式(Factory Pattern) | 定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法模式是一个类的实例化延迟到子类。 |
创建型 | 抽象工厂模式(Abstract Factory Pattern) | 提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。 |
创建型 | 单例模式(Singleton Pattern) | 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。 |
创建型 | 建造者模式(Builder Pattern) | 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 |
创建型 | 原型模式(Prototype Pattern) | 用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。 |
结构型 | 适配器模式(Adapter Pattern) | 将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作 |
结构型 | 桥接模式(Bridge Pattern) | 将抽象部分与它的实现部分分离,使它们都可以独立地变化。 |
结构型 | 组合模式(Composite Pattern) | 组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。使得用户对单个对象和组合对象的使用具有一致性。 |
结构型 | 装饰器模式(Decorator Pattern) | 动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。 |
结构型 | 外观模式(Facade Pattern) | 为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用 |
结构型 | 享元模式(Flyweight Pattern) | 运用共享技术有效地支持大量细粒度对象的复用。 |
结构型 | 代理模式(Proxy Pattern) | 为其他对象提供一种代理以控制对这个对象的访问。 |
行为型 | 责任链模式(Chain of Responsibility Pattern) | 使多个对象都有机会处理请求,从而避免请求发送者与接收者耦合在一起。将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。 |
行为型 | 命令模式(Command Pattern) | 将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。 |
行为型 | 解释器模式(Interpreter Pattern) | 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。 |
行为型 | 迭代器模式(Iterator Pattern) | 提供一种方法来访问聚合对象中的各个元素,而不用暴露这个对象的内部表示。 |
行为型 | 中介者模式(Mediator Pattern) | 用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。 |
行为型 | 备忘录模式(Memento Pattern) | 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。 |
行为型 | 观察者模式(Observer Pattern) | 定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。 |
行为型 | 状态模式(State Pattern) | 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。 |
行为型 | 策略模式(Strategy Pattern) | 定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化。 |
行为型 | 模板方法模式(Template Pattern) | 定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 |
行为型 | 访问者模式(Visitor Pattern) | 表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 |
说说适配器模式的原理
二狗不知道在哪听说华为手机就是牛,非要新买了一个华为手机,它高高兴兴的拿到新手机,想要插上耳机听歌,但发现手机没有通用的原型耳机孔,仔细看了说明书之后发现,华为手机是充电孔耳机孔在一起,在插耳机时需要一个耳机口转接器,才能插耳机。我们用程序员的眼观来看,这里相当于增加了一个转接器类用于适配耳机,这就类似于设计模式 —— 适配器模式(Adapter)。
适配器模式包
装目标类即适配者(Adaptee)成适配器。适配器提供客户端所需要的接口,把客户端的请求转化成对适配者的调用。也就是说,客户端访问适配器时,在适配器内部将调用适配者的方法。
- Client:客户端,调用自己需要的接口Target
- Target:定义客户端需要的跟特定需求相关的接口
- Apaptee:已存在接口,通常满足功能需求但与特定需求接口不一致
- Adapter:适配器,将Adaptee适配为Client需要的Target接口。
适配器模式的主要功能是进行转换匹配,用来复用已有的功能。适配器模式将某个类的接口转换成客户端期望的另一个接口,目的是消除由于接口不匹配所造成的类的兼容性问题。主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。
三种适配器模式有各自的应用场景:
- 类的适配器模式:将一个类转换成满足另一个新接口的类,创建一个新类,继承原有的类,实现新的接口即可。
- 对象的适配器模式:将一个对象转换成满足另一个新接口的对象,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法即可。
- 接口的适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。
适配器模式的本质是:转换匹配,复用功能。适配器模式中被适配的接口Adaptee与适配的接口Target没有关系,他们中的方法可以相同,也可以完全不同,适配器模式的实现方式是通过组合对象的方式进行的,将功能委托给被适配的对象进行的。适配器模式调用的序列图如下所示:
适配器模式的实现有以下几种:
- 常见适配:适配器类会实现接口,在实现过程中调用待适配的类中的方法
- 智能适配器:在适配器类中实现接口中定义的新方法,通常来说,适配器类中既可以通过借助继承类中的方法实现高层功能,也可以实现接口中定义的新方法,进行功能扩展。
- 缺省适配:即对接口的缺省实现,即接口适配器模式。
此外,在适配过程中,可能接口功能的实现需要多个待适配类中的方法交互才能满足需求,即同时适配多个类。适配实现的复杂度取决于待适配类与接口的相似度,相似程度越高,适配类的实现难度越低
。
在实际项目过程中,通常会存在两个版本共存的情况,这就是需要使用到双向适配器。
两个版本的实现代码:
public interface Targetable1 {
public void produce1();
}
public class Target1 implements Targetable1 {
@Override
public void produce1() {
System.out.println("Targetable1的produce1实现");
}
}
public interface Targetable2 {
public void produce2();
}
public class Target2 implements Targetable2 {
@Override
public void produce2() {
System.out.println("Targetable2的produce2实现");
}
}
适配器类的代码如下:
public class Adapter implements Targetable1, Targetable2 {
private Targetable1 target1;
private Targetable2 target2;
@Override
public void produce1() {
target1.produce1();
}
@Override
public void produce2() {
target2.produce2();
}
}
实际上,在使用适配器过程中存在一个问题:被适配的对象不兼容Adapter适配器类,这使得适配器类的适用范围受到限制。而双向适配器则解决了这样的问题,可以满足不同客户采用不同方式查看同一不同对象的需求。
适配器模式优缺点
优点:
- 更好的复用性。适配器模式可复用已实现接口的兼容。
- 更好的扩展性。实现适配器的过程中可以调用自己开发的功能,实现系统的扩展。
缺点:
- 过多使用适配器,系统会比较混乱,不易理解。