📌秋招八股--设计模式
- 问你知道哪些设计模式?尝试手写一个?
- 发散问题:如果一个接口很慢,你需要排查问题,你觉得可能会有哪些方面的原因?
- 还知道哪些设计模式?
- 常用的设计模式
- 设计模式遵循6大原则
- 代理模式
- 观察者模式
- 适配器模式
- 策略模式
- 工厂模式
- 简单介绍集中设计模式
- 单例模式,写了双重校验锁和静态内部类的实现
- 单例模式
- 手写单例模式(双重判空+同步)
- 写一下双重检测单例模式,解释一下每一行,volatile的作用,不加会有什么问题,去掉第一层循环会有什么问题,去掉第二层循环会有什么问题。
- 为什么要双重判空?
- 为什么要加volatile?volatile原理?
- 这样的单例模式是否会被破坏?
- 这个单例模式,我使用反射可不可以打破
- 反射如何破坏的?(构造器)怎么防止反射破坏?
- 除了反射还有其他方法能够破坏单例模式吗?(序列化、克隆)
Java后端各科最全八股自用整理,获取方式见
:
问你知道哪些设计模式?尝试手写一个?
发散问题:如果一个接口很慢,你需要排查问题,你觉得可能会有哪些方面的原因?
还知道哪些设计模式?
常用的设计模式
java的设计模式分为三大类: — 23种经典的设计模式 GOF
- 创建型模式,共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
- 结构型模式,共7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
- 创建型模式与对象的创建有关;
- 结构型模式处理类或对象的组合;
- 行为型模式对类或对象怎样交互和怎样分配职责进行描述。
设计模式遵循6大原则
http://www.uml.org.cn/sjms/201211023.asp#2
1、单一职责原则
- 即一个类只负责一项职责。
2、开闭原则(Open Close Principle)
- 对扩展开放,对修改关闭。
3、里氏代换原则(Liskov Substitution Principle)
- 只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
- 类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。
4、依赖倒转原则(Dependence Inversion Principle)
- 这个是开闭原则的基础,对接口编程,依赖于抽象而不依赖于具体。
5、接口隔离原则(Interface Segregation Principle)
- 客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
- 建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。
6、迪米特法则(最少知道原则)(Demeter Principle)
- 一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
- 尽量降低类与类之间的耦合。
代理模式
观察者模式
又名发布-订阅(Publish/Subscribe)模式。GOF给观察者模式如下定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
观察者模式很好理解,类似于邮件订阅和 RSS订阅,当我们浏览一些博客或 wiki 时,经常会看到 RSS 图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。其实,简单来讲就一句话:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系。
以下是观察者模式的基本实现步骤:
[1] 定义主题接口(Subject Interface):主题是被观察的对象,需要定义一个接口,其中包含注册、注销和通知观察者的方法。通常,这个接口包括registerObserver、removeObserver和notifyObservers等方法。
[2] 定义观察者接口(Observer Interface):观察者是接收主题通知并进行相应操作的对象,需要定义一个接口,其中包含更新方法。
[3] 实现具体主题类(Concrete Subject):实现主题接口的具体类,负责维护观察者列表并在状态变化时通知观察者。
[4] 实现具体观察者类(Concrete Observer):实现观察者接口的具体类,负责在接收到通知时进行相应的更新操作。
适配器模式
想使用一个已经存在的类,而它的接口不符合要求描述的是适配器模式; 适配器(Adapter)模式将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式包括三个角色:目标抽象类(Target)、适配器类(Adapter)和被适配者类(Adaptee)。
- 目标抽象类(Target):定义了客户端使用的与特定领域相关的接口,也就是客户端需要的方法。
- 适配器类(Adapter):通过继承或者组合方式,将被适配者类的接口与目标抽象类的接口转换起来,使得客户端可以按照目标抽象类的接口进行操作。
- 被适配者类(Adaptee):已经存在的、功能稳定的类,在这里指的是需要适配的类。
https://zhuanlan.zhihu.com/p/371225977
适配器模式怎么实现的
适配器模式主要分为类适配器模式和对象适配器模式两种实现方式:
- 类适配器模式:通过继承来实现适配器功能;
- 对象适配器模式:通过组合来实现适配器功能。
https://open.alipay.com/portal/forum/post/125101070
策略模式
策略模式(Strategy)属于对象行为型设计模式,主要是定义一系列的算法,把这些算法一个个封装成拥有共同接口的单独的类,并且使它们之间可以互换。策略模式使这些算法在客户端调用它们的时候能够互不影响地变化。
策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。外部用户只需要决定用哪个算法即可。
工厂模式
https://www.cnblogs.com/yssjun/p/11102162.html
工厂模式主要是为创建对象提供过渡接口,实现了创建者与调用者的分离,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
工厂模式在《Java与模式》中分为三类:
1)简单工厂模式((静态工厂模式))
把实例化的操作单独放到简单工厂类类中,让简单工厂类决定应该实例化哪个具体子类,这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。
优点:
封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,避免了修改客户代码,实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:
增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。 【对扩展开放;对修改关闭 】
2)工厂方法模式(Factory Method)
有四个角色,抽象工厂,具体工厂,抽象产品,具体产品。
定义一个用于创建对象的接口(抽象工厂),让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。
优点:
• 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
• 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂(抽象工厂)进行任何修改,满足开闭原则;
缺点:
每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
3)抽象工厂模式(Abstract Factory)
是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
优点:
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。 【满足开闭原则!】
缺点:
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。 【抽象工厂+具体工厂!】
使用场景 【重要】
- 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
- 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
- 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
- 如:输入法换皮肤,一整套一起换。生成不同操作系统的程序。
工厂方法模式 | 抽象工厂模式 |
---|---|
针对的是单个产品等级结构 | 针对的是面向多个产品等级结构 |
一个抽象产品类 | 多个抽象产品类 |
可以派生出多个具体产品类 | 每个抽象产品类可以派生出多个具体产品类 |
一个抽象工厂类,可以派生出多个具体工厂类 | 一个抽象工厂类,可以派生出多个具体工厂类 |
每个具体工厂类只能创建一个具体产品类的实例 | 每个具体工厂类可以创建多个具体产品类的实例 |
简单介绍集中设计模式
单例模式,写了双重校验锁和静态内部类的实现
单例模式
手写单例模式(双重判空+同步)
简单点说,就是一个应用程序中,某个类的实例对象只有一个,没有办法去new,因为构造器是被private修饰的,一般通过getInstance()的方法来获取它们的实例。
1、饿汉式
饿汉式1:
class Singleton {
//1.私化类的构造器
private Singleton (){
}
//2.内部创建类的对象
//4.要求此对象也必须声明为静态的
private static Singleton instance = new Singleton ();
//3.提供公共的静态的方法,返回类的对象
public static Singleton getInstance(){
return instance;
}
}
饿汉式2:使用静态代码块
class Singleton {
//1.私化类的构造器
private Singleton (){
}
//2.声明当前类对象,没初始化
//4.此对象也必须声明为static的
private static Singleton instance = null;
static{
instance = new Singleton ();
}
//3.声明public、static的返回当前类对象的方法
public static Singleton getInstance(){
return instance;
}
}
2、懒汉式:
class Singleton {
//1.私化类的构造器
private Singleton (){
}
//2.声明当前类对象,没初始化
//4.此对象也必须声明为static的
private static Singleton instance = null;
//3.声明public、static的返回当前类对象的方法
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton ();
}
return instance;
}
}
两种方式的对比:
饿汉式:
- 坏处:对象加载时间过长。
- 好处:饿汉式是线程安全的
懒汉式:好处:延迟对象的创建。
- 目前的写法坏处:线程不安全。—>到多线程内容时,再修改
使用同步机制将单例模式中的懒汉式改写为线程安全的。
class Singleton {
private Singleton (){ }
private static Singleton instance = null;
public static Singleton getInstance(){
//方式一:效率稍差
// synchronized (Singleton .class) {
// if(instance == null){
//
// instance = new Singleton ();
// }
// return instance;
// }
//方式二:效率更高
if(instance == null){
synchronized (Singleton .class) {
if(instance == null){
instance = new Singleton ();
}
}
}
return instance;
}
}
面试题:写一个线程安全的单例模式?
- 饿汉式。
- 懒汉式:上面提供的。
3、双重校验锁
public class Singleton {
// 私有构造方法,防止外部实例化
private Singleton() { }
// 使用volatile关键字确保多线程环境下的可见性
private static volatile Singleton instance;
// 双重锁检测,确保只有一个实例被创建
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
// 再次检查,防止多个线程同时通过第一层检查,导致创建多个实例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
volatile关键字: 在Java中,volatile关键字用于确保多线程环境下的可见性。当一个变量被声明为volatile时,它保证了所有线程都能够看到该变量的最新值。
双重锁检测: 在getInstance()
方法中,首先检查instance是否为null,如果为null,则进入同步块。在同步块内部,再次检查 instance 是否为null,这是因为在多线程环境下,可能有多个线程同时通过了第一个检查,然后其中一个线程获取了锁并创建了实例,而其他线程在等待锁释放。第二次检查确保只有一个线程创建实例。
4、静态内部类
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
5、枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏。
手撕单例模式必须掌握!
写一下双重检测单例模式,解释一下每一行,volatile的作用,不加会有什么问题,去掉第一层循环会有什么问题,去掉第二层循环会有什么问题。
为什么要双重判空?
为什么要加volatile?volatile原理?
这样的单例模式是否会被破坏?
这个单例模式,我使用反射可不可以打破
https://www.cnblogs.com/wyb628/p/6371827.html
反射如何破坏的?(构造器)怎么防止反射破坏?
除了反射还有其他方法能够破坏单例模式吗?(序列化、克隆)
更多后端全部八股点击👉👉【闲鱼】https://m.tb.cn/h.5yHpgkY?tk=O8bhWpn1NBD CZ8908 「我在闲鱼发布了【京985计算机硕士自用后端八股文出售,不同于市面上的几块钱八】」
点击链接直接打开
Java后端各科最全八股自用整理,获取方式见
:
整理不易🚀🚀,关注和收藏后拿走📌📌欢迎留言🧐👋📣
欢迎专注我的公众号AdaCoding 和 Github:AdaCoding123