设计模式(含7大原则)面试题

目录

主要参考文章

设计模式的目的

设计模式的七大原则

设计模式的三大分类及关键点

1、创建型模式(用于解耦对象的实例化过程)

2、结构型模式

3、行为型模式

23种设计模式(乱序--现学现写,不全面--应付面试为主)

单例模式

模板模式

哈哈哈哈哈


声明

此文只针对我个人的目前的理解程度来记录的,有的我知道的或者觉得没有必要写的内容我就会省略掉。并且主要针对面试,介绍的可能不是很全面,甚至有的地方不是很正确,请包涵。

主要参考文章

设计模式23模式介绍-CSDN博客

12-模板方法模式_12(模板方法模式)-CSDN博客

设计模式的目的

  • ++可重用性++ (即:相同功能的代码,不用多次编写)
  • ++可扩展性++ (即:当需要增加新的功能时,非常的方便,称为可维护)
  • ++可靠性++ (即:当我们增加新的功能后,对原来的功能没有影响)
  • ++可读性++ (即:编程规范性, 便于其他程序员的阅读和理解)
  • 使程序呈现++高内聚,低耦合++的特性。

设计模式的七大原则

固定记忆:单 开 里 依 接 合 迪。

设计模式的三大分类及关键点

1、创建型模式(++用于解耦对象的实例化过程++)

单例模式:某个类只能有一个实例,提供一个全局的访问点。
工厂模式:一个工厂类根据传入的参量决定创建出哪一种产品类的实例。
抽象工厂模式:创建相关或依赖对象的家族,而无需明确指定具体类。
建造者模式:封装一个复杂对象的创建过程,并可以按步骤构造。
原型模式:通过复制现有的实例来创建新的实例。

2、结构型模式

装饰器模式:动态的给对象添加新的功能。
代理模式:不修改原始对象,通过代理对象来间接访问原始对象,并在访问前后执行额外的操作。
桥接模式:将抽象部分和它的实现部分分离,使它们都可以独立的变化。
适配器模式:将一个类的方法接口转换成客户希望的另一个接口。
组合模式:将对象组合成树形结构以表示"部分-整体"的层次结构。
外观模式:对外提供一个统一的方法,来访问子系统中的一群接口。
享元模式:通过共享技术来有效的支持大量细粒度的对象。

3、行为型模式

策略模式:定义一系列算法,把他们封装起来,并且使它们可以相互替换。
模板模式:抽象父类定义一个算法,子类在不改变该算法结构的情况下重定义该算法的某些步骤。
命令模式:将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
迭代器模式:一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构。
观察者模式:对象间的一对多的依赖关系。
仲裁者模式:用一个中介对象来封装一系列的对象交互。
备忘录模式:在不破坏封装的前提下,保持对象的内部状态。
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器。
建造者模式:允许一个对象在其对象内部状态改变时改变它的行为。
责任链模式:将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
访问者模式:不改变数据结构的前提下,增加作用于一组对象元素的新功能。


23种设计模式(乱序--现学现写,不全面--应付面试为主)

单例模式--创建型

某个类只能有一个实例,提供一个全局的访问点。

单例模式要素:

  • 私有构造方法;
  • 私有静态引用指向自己实例 ;
  • 以自己实例为返回值的公有静态方法;

单例模式好处:

  • 单例模式只允许创建一个对象,因此节省内存;
  • 避免了频繁的对象创建,可以加快对象访问速度。

单例模式场景:

  • 需要频繁实例化然后销毁的对象;
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象;
  • 有状态的工具类对象;

单例模式的实现方式分类:

饿汉式单例:

java 复制代码
//线程安全的
public class Singleton {
    //单例模式,构造器要私有化。
    private Singleton() {}

    //私有的静态的常量--私有静态引用指向自己实例。
    //【因为是static修饰的属性,所以是在类加载的时候就被创建,
    //后期不会再改变,所以线程是安全的!】
    private static final Singleton single = new Singleton();

    //以自己实例为返回值的公有静态方法。
    public static Singleton getInstance() {
        return single;
    }
}

懒汉式单例:

java 复制代码
//有线程安全的问题,不推荐使用
//就算是使用了[加锁+双重判断]的解救办法,性能还是被损耗了。
public class SingletonTest {
    public static SingletonTest singleton = null;
    public static SingletonTest getInstance(){
        if(singleton == null){
            singleton = new SingletonTest();
        }
        return singleton;
    }
    //单例模式,构造器要私有化
    private SingletonTest {
    }
}

DCL(Double Check双重判断 + Lock加锁)懒汉式单例但线程安全,推荐使用:

java 复制代码
//线程安全的
public class Singleton {
    private volatile static Singleton singleton;

    public static Singleton getSingleton(){
        //双重判断之一
        if(singleton==null){
            //加锁
            synchronized (Singleton.class) {
                //双重判断之二
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
    
    //构造函数私有化
    private Singleton(){
    }
}

静态内部类(懒汉式单例但线程安全):

java 复制代码
//线程安全的
//外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。
//第一次调用getInstance()方法会导致虚拟机加载SingleTonHoler类且初始化INSTANCE。
//在创建时是否有并发问题? => 没有没有,类加载时jvm会保证线程安全性!
public class Singleton {
    //静态内部类
    private static class SingleTonHoler{
        private static Singleton INSTANCE = new Singleton();
    }
 
    public static Singleton getInstance(){
        return SingleTonHoler.INSTANCE;
    }

    //单例模式,构造器要私有化
    private Singleton {
    }
}

枚举类(饿汉式):

java 复制代码
//枚举单例属于懒汉式还是饿汉式:饿汉式,内部枚举类相当于静态成员变量,
//类加载时就会创建,因此也是线程安全的。
public class SingletonObject7 {
 
    private SingletonObject7(){
    }
 
    /**
     * 枚举类型是线程安全的,并且只会装载一次
     */
    private enum Singleton{
        INSTANCE;
 
        private final SingletonObject7 instance;
 
        Singleton(){
            instance = new SingletonObject7();
        }
 
        private SingletonObject7 getInstance(){
            return instance;
        }
    }
 
    public static SingletonObject7 getInstance(){
        //内部枚举类相当于静态成员变量
        return Singleton.INSTANCE.getInstance();
    }
}

破坏单例模式的场景及解决办法:

1、反射是通过调用构造方法生成新对象的,++除枚举方式外,其他方式都会被反射破坏单例++,所以如果我们想要阻止单例破坏,可以在构造方法中进行判断,若已有实例,则阻止生成新的实例,解决办法如下。

java 复制代码
private SingletonObject(){
    if (instance != null) {
        throw new RuntimeException("实例已经存在,请通过 getInstance()方法获取");
    }
}

2、如果单例类实现了序列化接口Serializable, 就可以通过反序列化破坏单例(反序列化不会调用构造函数--反序列化会用到反射但不是通过反射调用构造函数而是ObjectInputStream 类的私有的++readObject()方法++ --使用ObjectInputStream.readObject()读取进来之后,如果是多次读取,就会创建多个实例),所以我们可以不实现序列化接口,如果非得实现序列化接口,可以在单例类中定义反序列化方法readResolve(),在反序列化时直接返回单例对象。

java 复制代码
import java.io.Serializable;

// Singleton Box:
class Box implements Serializable {
  private static Box instance = new Box("TEST");
  public static Box getInstance() {
    return Box.instance;
  }
  private Box(String name) {
    this.name = name;
  }
  private String name;
  @Override
  public String toString() {
    return "Box " + name;
  }
  
  //【readResolve()方法】,如果不写此方法反序列化时会破坏单例
  private Object readResolve() {
    return Box.getInstance();
  }
}
java 复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Foo {
  public static void main(String[] args) {
    Box box = Box.getInstance();
    System.out.println(box.toString());
    try {
      ObjectOutputStream o = new ObjectOutputStream(
              new FileOutputStream("e:/box.out"));
      o.writeObject(box);
      o.close();
    } catch(Exception e) {
      e.printStackTrace();
    }

    Box box1 = null, box2 = null;

    try {
      ObjectInputStream in =new ObjectInputStream(
              new FileInputStream("e:/box.out"));
      box1 = (Box)in.readObject();
      in.close();
    } catch(Exception e) {
      e.printStackTrace();
    }

    try {
      ObjectInputStream in =new ObjectInputStream(
              new FileInputStream("e:/box.out"));
      box2 = (Box)in.readObject();
      in.close();
    } catch(Exception e) {
      e.printStackTrace();
    }

    System.out.println("box1.equals(box2) : " + box1.equals(box2));
    System.out.println(box1);
    System.out.println(box2);
  }
}

模板模式--行为型

抽象父类定义一个算法,子类在不改变该算法结构的情况下重定义该算法的某些步骤。

算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中,提高了代码复用性。

自定义lamabda表达式也符合此设计模式。

案例:炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。

java 复制代码
/**
 * @ClassName: AbstractClass
 * @Description: 抽象类(定义模板方法和基本方法)
 * @Author: Sevenyear
 */
public abstract class AbstractClass {

    //模板方法定义
    //【为防止恶意操作,模板方法一般都加上 final 关键词】
    public final void cookProcess() {
        pourOil();
        heatOil();
        pourVegetable();
        pourSauce();
        fry();
    }

    public void pourOil() {
        System.out.println("倒油");
    }

    //第二步:热油是一样的,所以直接实现
    public void heatOil() {
        System.out.println("热油");
    }

    //第三步:倒蔬菜是不一样的(一个下包菜,一个是下菜心)
    public abstract void pourVegetable();

    //第四步:倒调味料是不一样
    public abstract void pourSauce();


    //第五步:翻炒是一样的,所以直接实现
    public void fry(){
        System.out.println("炒啊炒啊炒到熟啊");
    }
}
java 复制代码
/**
 * @ClassName: ConcreteClass_BaoCai
 * @Description: 炒包菜类
 * @Author: Sevenyear
 */
public class ConcreteClass_BaoCai extends AbstractClass {
    @Override
    public void pourVegetable() {
        System.out.println("下锅的蔬菜是包菜");
    }

    @Override
    public void pourSauce() {
        System.out.println("下锅的酱料是辣椒");
    }
}
java 复制代码
/**
 * @ClassName: ConcreteClass_BaoCai
 * @Description: 炒菜心类
 * @Author: Sevenyear
 */
public class ConcreteClass_CaiXin extends AbstractClass {
    @Override
    public void pourVegetable() {
        System.out.println("下锅的蔬菜是菜心");
    }

    @Override
    public void pourSauce() {
        System.out.println("下锅的酱料是蒜蓉");
    }
}
java 复制代码
public class Client {
    public static void main(String[] args) {
        //炒包菜
        //创建对象
        ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
        //调用炒菜的功能
        baoCai.cookProcess();
    }
}

jdk案例:++InputStream抽象父类++ 中已经定义好了读取字节数组数据的方法是每次读取一个字节,并将其存储到数组的第一个空闲索引位置,循环读取len个字节数据。++具体如何读取一个字节数据?是由子类实现的++。

java 复制代码
public abstract class InputStream implements Closeable {
    //抽象方法,要求子类必须重写
    public abstract int read() throws IOException;

    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read(); //调用了无参的read方法,该方法是每次读取一个字节数据
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }
}

在InputStream类中定义了多个 read() 方法,从上面代码可以看到,++无参的 read() 方法是抽象方法,要求子类必须实现++。而 read(byte b[]) 方法调用了 read(byte b[], int off, int len) 方法,所以在此处重点看的方法是带三个参数的方法。 在该方法中可以看到调用了无参的抽象的 read() 方法。


享元模式--结构型

java常量池(字符串常量池、Integer常量池)的实现其实是基于享元模式(通过共享技术来有效的支持大量细粒度的对象)的思想,可以节省创建的时间,并且节省空间。

++字符串常量池在java1.8中是在Java堆中的++ 。在编译期就存在的字符串将会直接存入这个池中,在不同代码地方的++字面量形式的相同的字符串++将会直接引用同一个字符串,为什么能这样引用是因为字符串的不可变性。


策略模式--行为型

comparable和comparator

相关推荐
魔道不误砍柴功3 分钟前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2343 分钟前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨6 分钟前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
测开小菜鸟2 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity3 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天3 小时前
java的threadlocal为何内存泄漏
java
caridle3 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^3 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋33 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
秋の花3 小时前
【JAVA基础】Java集合基础
java·开发语言·windows