Java面试题--设计模式

news/2024/7/24 4:31:11 标签: java, 设计模式, 开发语言

一、Java 中有几种设计模式

Java 中一般认为有 23 种设计模式

分为三大类:

1. 创建型模式 5 种

① 工厂方法模式

② 抽象工厂模式

③ 单例模式

④ 建造者模式

⑤ 原型模式

2. 结构型模式 7 种

① 适配器模式

② 装饰器模式

③ 代理模式

④ 外观模式

⑤ 桥接模式

⑥ 组合模式

⑦ 享元模式

3. 行为型模式 11 种

① 策略模式

② 模板方法模式

③ 观察者模式

④ 迭代子模式

⑤ 责任链模式

⑥ 命令模式

⑦ 备忘录模式

⑧ 状态模式

⑨ 访问者模式

⑩ 中介者模式

⑪ 解释器模式

 二、什么是单例设计模式

1. 单例模式定义

单例模式确保某个类只有一个实例,而

且自行实例化并向整个系统提供这个实

在计算机系统中,线程池、缓存、日志

对象、对话框、打印机、显卡的驱动程

序对象常被设计成单例,选择单例模式

就是为了避免不一致状态

2. 单例模式的特点

① 单例类只能有一个实例

② 单例类必须自己创建自己的唯一实例

③ 单例类必须给所有其他对象提供这一

    实例

④ 单例模式保证了全局对象的唯一性

    比如系统启动读取配置文件就需要单

    例保证配置的一致性

3. 单例的四大原则

① 构造器私有化

② 以静态方法或者枚举返回实例

③ 确保实例只有一个,尤其是多线程

    环境

④ 确保反序列化时不会重新构建对象

4.  实现单例模式的方式

(1) 饿汉式 (立即加载):

饿汉式单例在类加载初始化时就创建

一个静态的对象供外部使用,除非系统

重启,这个对象不会改变,所以本身就

是线程安全的

Singleton 通过将构造方法限定为 private

避免了类在外部被实例化,在同一个虚拟

机范围内,Singleton 的唯一实例只能通

过 getInstance() 方法访问 (事实上,通过

Java 反射机制是能够实例化构造方法为

private 的类的,会使 Java 单例实现失效)

java">/**
 * 饿汉式(立即加载)
 */
public class Singleton1 {

    /**
     * 私有构造
     */
    private Singleton1() {
        System.out.println("构造函数Singleton1");
    }

    /**
     * 初始值为实例对象
     */
     private static Singleton1 single = new Singleton1();

     /**
      * 静态工厂方法
      * @return 单例对象
      */
     public static Singleton1 getInstance() {
        System.out.println("getInstance");
        return single;
     }

     public static void main(String[] args){
        System.out.println("初始化");
        Singleton1 instance = Singleton1.getInstance();
     }
}
(2) 懒汉式 (延迟加载):

该示例虽然用延迟加载方式实现了懒汉

式单例,但在多线程环境下会产生多个

Singleton 对象

java">
/**
 * 懒汉式(延迟加载)
 */
public class Singleton2 {

    /**
     * 私有构造
     */
    private Singleton2() {
        System.out.println("构造函数Singleton2");
    }

    /**
     * 初始值为null
     */
    private static Singleton2 single = null;

    /**
     * 静态工厂方法
     * @return 单例对象
     */
    public static Singleton2 getInstance() {
        if(single == null){
            System.out.println("getInstance");
            single = new Singleton2();
        }
        return single;
    }

    public static void main(String[] args){
        System.out.println("初始化");
        Singleton2 instance = Singleton2.getInstance();
    }
}
(3) 同步锁 (解决线程安全问题):

在方法上加 synchronized 同步锁或是

用同步代码块对类加同步锁,此种方

式虽然解决了多个实例对象问题,但

是该方式运行效率却很低下,下一个

线程想要获取对象,就必须等待上一

个线程释放锁之后,才可以继续运行

java">/**
 *
 * 同步锁(解决线程安全问题)
 */
public class Singleton3 {

    /**
     * 私有构造
     */
    private Singleton3() {}

    /**
     * 初始值为null
     */
    private static Singleton3 single = null;
    
    public static Singleton3 getInstance() {
        // 等同于 synchronized public static Singleton3 getInstance()
        synchronized(Singleton3.class){
            // 注意:里面的判断是一定要加的,否则出现线程安全问题
            if(single == null){
                single = new Singleton3();
            }
        }
        return single;
    }
}
(4) 双重检查锁 (提高同步锁的效率):

使用双重检查锁进一步做了优化,可

以避免整个方法被锁,只对需要锁的

代码部分加锁,可以提高执行效率

java">/**
 * 双重检查锁(提高同步锁的效率)
 */
public class Singleton4 {

    /**
     * 私有构造
     */
    private Singleton4() {}

    /**
     * 初始值为null
     */
    private static Singleton4 single = null;

    /**
     * 双重检查锁
     * @return 单例对象
     */
    public static Singleton4 getInstance() {
        if (single == null) {
            synchronized (Singleton4.class) {
                if (single == null) {
                    single = new Singleton4();
                }
            }
        }
        return single;
    }
}
(5) 静态内部类:

引入了一个内部静态类 (static class),静

态内部类只有在调用时才会加载,它保证

了 Singleton 实例的延迟初始化,又保证

了实例的唯一性

它把 singleton 的实例化操作放到一个静

态内部类中,在第一次调用 getInstance()

方法时,JVM 才会去加载 InnerObject 类,

同时初始化 singleton 实例,所以能让

getInstance() 方法线程安全

   特点:即能延迟加载,也能保证线程安全

静态内部类虽然保证了单例在多线程并发

下的线程安全性,但是在遇到序列化对象

时,默认的方式运行得到的结果就是多例

java">/**
 *
 * 静态内部类(延迟加载,线程安全)
 */
public class Singleton5 {
    /**
     * 私有构造
     */
    private Singleton5() {}

    /**
     * 静态内部类
     */
    private static class InnerObject{
        private static Singleton5 single = new Singleton5();
    }

    public static Singleton5 getInstance() {
        return InnerObject.single;
    }
}
(6) 内部枚举类实现 (防止反射攻击):

事实上,通过 Java 反射机制是能够实例

化构造方法为 private 的类的,这也就是

我们现在需要引入的枚举单例模式

java">public class SingletonFactory {
    /**
     * 内部枚举类
     */
    private enum EnumSingleton{
        Singleton;
        private Singleton6 singleton;

        //枚举类的构造方法在类加载是被实例化
        private EnumSingleton(){
            singleton = new Singleton6();
        }

        public Singleton6 getInstance(){
            return singleton;
        }
    }
 
    public static Singleton6 getInstance() {
        return EnumSingleton.Singleton.getInstance();
    }
}

class Singleton6 {
    public Singleton6(){}
}

三、什么是工厂设计模式

工厂设计模式就是用来生产对象的,在

java 中,万物皆对象,这些对象都需要

创建,如果创建的时候直接 new 该对象,

就会对该对象耦合严重,假如我们要更

换对象,所有 new 对象的地方都需要修

改一遍,这显然违背了软件设计的开闭

原则,如果我们使用工厂来生产对象,

我们就只和工厂打交道就可以了,彻底

和对象解耦,如果要更换对象,直接在

工厂里更换该对象即可,达到了与对象

解耦的目的;所以说,工厂模式最大的

优点就是:解耦

1. 简单工厂 (Simple Factory)

定义:

一个工厂方法,依据传入的参数,生成对

应的产品对象;

角色:

① 抽象产品
② 具体产品
③ 具体工厂
④ 产品使用者

使用说明:

先将产品类抽象出来,比如,苹果和梨都属

于水果,抽象出来一个水果类 Fruit,苹果和

梨就是具体的产品类,然后创建一个水果工

厂,分别用来创建苹果和梨

代码如下:

java">// 水果接口:
public interface Fruit {
    void whatIm();
}

// 苹果类:
public class Apple implements Fruit {
    @Override
    public void whatIm() {
        System.out.println("苹果");
    }
}

// 梨类:
public class Pear implements Fruit {
    @Override
    public void whatIm() {
        System.out.println("梨");
    }
}

//水果工厂:

public class FruitFactory {
    public Fruit createFruit(String type) {
        if (type.equals("apple")) {//生产苹果
            return new Apple();
        } else if (type.equals("pear")) {//生产梨
            return new Pear();
        }
        return null;
    }
}

// 使用工厂生产产品:
public class FruitApp {
    public static void main(String[] args) {
        FruitFactory mFactory = new FruitFactory();
        Apple apple = (Apple) mFactory.createFruit("apple");//获得苹果
        Pear pear = (Pear) mFactory.createFruit("pear");//获得梨
        apple.whatIm();
        pear.whatIm();
    }
}

以上的这种方式,每当添加一种水果,就必

然要修改工厂类,违反了开闭原则;

所以简单工厂只适合于产品对象较少,且产

品固定的需求,对于产品变化无常的需求来

说显然不合适

2. 工厂方法 (Factory Method)

定义:

将工厂提取成一个接口或抽象类,具体生

产什么产品由子类决定

角色:

① 抽象产品
② 具体产品
③ 抽象工厂
④ 具体工厂

使用说明:

和上例中一样,产品类抽象出来,这次我们

把工厂类也抽象出来,生产什么样的产品由

子类来决定

代码如下:

java">// 水果接口、苹果类和梨类:代码和上例一样

// 抽象工厂接口:
public interface FruitFactory {
    Fruit createFruit();//生产水果
}

// 苹果工厂:
public class AppleFactory implements FruitFactory {
    @Override
    public Apple createFruit() {
        return new Apple();
    }
}

// 梨工厂:
public class PearFactory implements FruitFactory {
    @Override
    public Pear createFruit() {
        return new Pear();
    }
}

// 使用工厂生产产品:
public class FruitApp {
    public static void main(String[] args){
        AppleFactory appleFactory = new AppleFactory();
        PearFactory pearFactory = new PearFactory();
        Apple apple = appleFactory.createFruit();//获得苹果
        Pear pear = pearFactory.createFruit();//获得梨
        apple.whatIm();
        pear.whatIm();
    }
}

以上这种方式,虽然解耦了,也遵循了开闭

原则,但是如果我需要的产品很多的话,需

要创建非常多的工厂,所以这种方式的缺点

也很明显

3. 抽象工厂 (Abstract Factory)

定义:

为创建一组相关或者是相互依赖的对象提供

的一个接口,而不需要指定它们的具体类

角色:

① 抽象产品
② 具体产品

③ 抽象工厂

④ 具体工厂

使用说明:

抽象工厂和工厂方法的模式基本一样,区别

在于,工厂方法是生产一个具体的产品,而

抽象工厂可以用来生产一组相同,有相对关

系的产品

用抽象工厂来实现:

java">// cpu接口和实现类:
public interface Cpu {
    void run();

    class Cpu650 implements Cpu {
        @Override
        public void run() {
            System.out.println("650 也厉害");
        }
    }

    class Cpu825 implements Cpu {
        @Override
        public void run() {
            System.out.println("825 更强劲");
        }
    }
}

// 屏幕接口和实现类:
public interface Screen {
    void size();
    class Screen5 implements Screen {
        @Override
        public void size() {
            System.out.println("" + "5寸");
        }
    }

    class Screen6 implements Screen {
        @Override
        public void size() {
            System.out.println("6寸");
        }
    }
}

// 抽象工厂接口:
public interface PhoneFactory {
    Cpu getCpu();//使用的cpu
    Screen getScreen();//使用的屏幕
}

// 小米手机工厂:
public class XiaoMiFactory implements PhoneFactory {
    @Override
    public Cpu.Cpu825 getCpu() {
        return new Cpu.Cpu825();//高性能处理器
    }
 
    @Override
    public Screen.Screen6 getScreen() {
        return new Screen.Screen6();//6寸大屏
    }
}

//红米手机工厂:
public class HongMiFactory implements PhoneFactory {
    @Override
    public Cpu.Cpu650 getCpu() {
        return new Cpu.Cpu650();//高效处理器
    }

    @Override
    public Screen.Screen5 getScreen() {
        return new Screen.Screen5();//小屏手机
    }
}

// 使用工厂生产产品:
public class PhoneApp {
    public static void main(String[] args){
        HongMiFactory hongMiFactory = new HongMiFactory();
        XiaoMiFactory xiaoMiFactory = new XiaoMiFactory();
        Cpu.Cpu650 cpu650 = hongMiFactory.getCpu();
        Cpu.Cpu825 cpu825 = xiaoMiFactory.getCpu();
        cpu650.run();
        cpu825.run();
        Screen.Screen5 screen5 = hongMiFactory.getScreen();
        Screen.Screen6 screen6 = xiaoMiFactory.getScreen();
        screen5.size();
        screen6.size();
    }
}

以上例子可以看出,抽象工厂可以解决一

系列的产品生产的需求,对于大批量,多

系列的产品,用抽象工厂可以更好地管理

和扩展

4. 三种工厂方式总结

① 对于简单工厂和工厂方法来说,两者的

    使用方式实际上是一样的,如果对于产

    品的分类和名称是确定的,数量是相对

    固定的,推荐使用简单工厂模式;

② 抽象工厂用来解决相对复杂的问题,适用于

    一系列、大批量的对象生产


http://www.niftyadmin.cn/n/4953978.html

相关文章

算法通关村——字符串反转问题解析

1. 反转字符串 反转字符串 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 1.1 交换 这一题的思路还是简单的&…

路由跳转--编程式导航

简介 除了使用 创建 a 标签来定义导航链接&#xff0c;我们还可以通过编程式导航实现导航。所谓编程式导航指的是不通过router-link跳转&#xff0c;而是借助 router 的实例&#xff0c;通过代码的方式跳转。 示例&#xff1a; App.vue <template><div id"ap…

前端面试:【this】解锁上下文之谜

嗨&#xff0c;亲爱的代码探险家&#xff01;在JavaScript的冒险旅程中&#xff0c;有一个神秘的关键字&#xff0c;那就是this。this就像是一面魔镜&#xff0c;它的含义会根据代码的上下文而变化&#xff0c;有时令人困惑&#xff0c;但掌握了它&#xff0c;你就能更好地控制…

系统架构设计专业技能 · 信息安全技术

系列文章目录 系统架构设计专业技能 网络技术&#xff08;三&#xff09; 系统架构设计专业技能 系统安全分析与设计&#xff08;四&#xff09;【系统架构设计师】 系统架构设计高级技能 软件架构设计&#xff08;一&#xff09;【系统架构设计师】 系统架构设计高级技能 …

ES6 Promise/Async/Await使用

Promise应用 在工作中, 我们经常会遇到用异步请求数据, 查询一个结果, 然后把返回的参数放入到下一个执行的异步函数像这样: $.ajax({..., success(resp)>{$.ajax({..., resp.id, success(resp)>{$.ajax({..., resp.name success(resp)>{//多层嵌套的情况, 看着是不…

【C++学习】模板进阶

目录 一、非类型模板参数 二、模板特化 2.1 概念 2.2函数模板特化 2.3类模板特化 2.3.1全特化 2.3.2偏特化 2.3.3 类模板特化应用示例 三、模板分离编译 3.1 什么是分离编译 3.2 模板的分离编译 3.3解决方法 四、模板总结 一、非类型模板参数 模板参数分类型形参…

File详解

目录 1. 概述 2. File的常见成员方法(判断、获取) 3. File的常见成员方法(创建、删除) 4. File的常见成员方法(获取并遍历) 1. 概述 File对象就表示一个路径&#xff0c;可以是文件的路径、也可以是文件夹的路径 这个路径可以是存在的&#xff0c;也允许是不存在的 方法名称…

css整体使用

文章目录 html与csshtml、css与排版响应式与自适应布局自适应布局响应式布局 css规则class、id、以及默认的标签名的优先级 css书写位置flex整体逻辑 bootstrap资源 html与css html负责网页功能&#xff0c;css负责网页美化&#xff1b;浏览器本身有一套默认的css样式&#xf…