首页 » 99链接平台 » 「设计模式面试」这几个问题你能回答几个?(对象模式工厂实例方法)

「设计模式面试」这几个问题你能回答几个?(对象模式工厂实例方法)

神尊大人 2024-11-03 11:45:35 0

扫一扫用手机浏览

文章目录 [+]

在这里插入图片描述

【金三银四】设计模式篇1.谈谈你对设计模式的理解

1.首先谈设计模式的作用:经验的传承,提高了软件复用的水平,最终达到提高软件开发效率

2.设计模式的分类

「设计模式面试」这几个问题你能回答几个?(对象模式工厂实例方法) 99链接平台
(图片来自网络侵删)

image.png

3.创建型模式:都是用来帮助我们创建对象的!

image.png

4.结构性模式:关注对象和类的组织

image.png

5.行为型模式:关注系统中对象之间的相互交换,研究系统在运行时对象之间的相互通信和协作,进一步明确对象的职责,共有11中模式

image.png

2.谈谈你对单例模式的理解

作用:单例模式的核心是保证一个类只有一个实例,并且提供一个访问实例的全局访问点。

饿汉式

也就是类加载的时候立即实例化对象,实现的步骤是先私有化构造方法,对外提供唯一的静态入口方法,实现如下

/ 单例模式:饿汉式 @author 波波烤鸭 /public class SingletonInstance1 { // 声明此类型的变量,并实例化,当该类被加载的时候就完成了实例化并保存在了内存中 private final static SingletonInstance1 instance = new SingletonInstance1(); // 私有化所有的构造方法,防止直接通过new关键字实例化 private SingletonInstance1(){} // 对外提供一个获取实例的静态方法 public static SingletonInstance1 getInstance(){ return instance; }}

饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。
虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。
因此,可以省略synchronized关键字

问题:如果只是加载本类,而不是要调用getInstance(),甚至永远没有调用,则会造成资源浪费!

/ 单例模式:饿汉式 @author 波波烤鸭 /public class SingletonInstance1 { private byte[] b1 = new byte[10241024]; private byte[] b2 = new byte[10241024]; private byte[] b3 = new byte[10241024]; // 声明此类型的变量,并实例化,当该类被加载的时候就完成了实例化并保存在了内存中 private final static SingletonInstance1 instance = new SingletonInstance1(); // 私有化所有的构造方法,防止直接通过new关键字实例化 private SingletonInstance1(){} // 对外提供一个获取实例的静态方法 public static SingletonInstance1 getInstance(){ return instance; }}双重检测锁

/ 单例模式:静态内部类

/ 静态内部类实现方式 @author 波波烤鸭 /public class SingletonInstance4 { // 静态内部类 public static class SingletonClassInstance{ // 声明外部类型的静态常量 public static final SingletonInstance4 instance = new SingletonInstance4(); } // 私有化构造方法 private SingletonInstance4(){} // 对外提供的唯一获取实例的方法 public static SingletonInstance4 getInstance(){ return SingletonClassInstance.instance; }}枚举单例

/ 单例模式:枚举方式实现 @author dengp /public enum SingletonInstance5 { // 定义一个枚举元素,则这个元素就代表了SingletonInstance5的实例 INSTANCE; public void singletonOperation(){ // 功能处理 }}3.怎么解决反射爆破单例

  在单例中我们定义的私有的构造器,但是我们知道反射是可以操作私有的属性和方法的,这时我们应该怎么处理?

public static void main(String[] args) throws Exception, IllegalAccessException { SingletonInstance1 s1 = SingletonInstance1.getInstance(); // 反射方式获取实例 Class c1 = SingletonInstance1.class; Constructor constructor = c1.getDeclaredConstructor(null); constructor.setAccessible(true); SingletonInstance1 s2 = (SingletonInstance1)constructor.newInstance(null); System.out.println(s1); System.out.println(s2);}

输出结果

com.dpb.single.SingletonInstance1@15db9742com.dpb.single.SingletonInstance1@6d06d69c

产生了两个对象,和单例的设计初衷违背了。
解决的方式是在无参构造方法中手动抛出异常控制,或者声明一个全局变量来控制。

// 私有化所有的构造方法,防止直接通过new关键字实例化private SingletonInstance2(){ if(instance != null){ // 只能有一个实例存在,如果再次调用该构造方法就抛出异常,防止反射方式实例化 throw new RuntimeException("单例模式只能创建一个对象"); }}

上面这种方式我们还可以通过反序列化的方式来破解

public static void main(String[] args) throws Exception, IllegalAccessException { SingletonInstance2 s1 = SingletonInstance2.getInstance(); // 将实例对象序列化到文件中 ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("c:/tools/a.txt")); oos.writeObject(s1); oos.flush(); oos.close(); // 将实例从文件中反序列化出来 ObjectInputStream ois = new ObjectInputStream( new FileInputStream("c:/tools/a.txt")); SingletonInstance2 s2 = (SingletonInstance2) ois.readObject(); ois.close(); System.out.println(s1); System.out.println(s2);}

我们只需要在单例类中重写readResolve方法并在该方法中返回单例对象即可,如下:

package com.dpb.single;import java.io.ObjectStreamException;import java.io.Serializable;/ 单例模式:4.说说你在哪些框架中看到了单例的设计

1.Spring中的Bean对象,默认是单例模式

2.相关的工厂对象都是单例,比如:MyBatis中的SqlSessionFactory,Spring中的BeanFactory

3.保存相关配置信息的都是单例,比如:MyBatis中的Configuration对象,SpringBoot中的各个XXXAutoConfiguration对象等

4.应用程序的日志应用,一般都会通过单例来实现

5.数据库连接池的设计也是单例模式

5.谈谈你对工厂模式的理解

  工厂模式的作用是帮助我们创建对象,我们不用自己来创建,根据需要创建的对象的复杂度我们可以把工厂模式分为简单工厂,工厂方法和抽象工厂。

image.png

5.1 简单工厂

  简单工厂模式又称为静态工厂方法,他可以根据不同的参数而返回不同的实例,简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

JDK中的简单工厂应用:DataFormat

image.png

自己写一个简单工厂的案例

image.png

/ 简单工厂 /public class SimpleFactory { public static void main(String[] args) { // 根据对应的类型返回相关产品 CarFactory.createCar("奥迪").run(); CarFactory.createCar("Byd").run(); }}// 定义公共的接口interface Car{ void run();}class Audi implements Car{ @Override public void run() { System.out.println("奥迪在跑..."); }}class Byd implements Car{ @Override public void run() { System.out.println("Byd在跑..."); }}// 创建对应的简单工厂类class CarFactory{ public static Car createCar(String type){ if("奥迪".equals(type)){ return new Audi(); }else if("Byd".equals(type)){ return new Byd(); }else{ throw new RuntimeException("该产品不能生产"); } }}

我们可以发现简单工厂对于新增产品是无能为力的!
不修改原有代码根本就没办法扩展!!!

5.2 工厂方法

  针对于简单工厂的短板,引出了工厂方法模式,定义一个用户创建对象的接口,让子类决定实例化哪个类,工厂方法使一个类的实例化延迟到了其子类中。

image.png

代码实现:

/ 工厂方法模式 /public class FactoryMethod { public static void main(String[] args) { new AudiCarFactory().createCar().run(); new BydCarFactory().createCar().run(); } public static interface Car{ public void run(); } public static class Byd implements Car{ @Override public void run() { System.out.println("比亚迪..."); } } public static class Audi implements Car{ @Override public void run() { System.out.println("奥迪..."); } } public static interface CarFactory{ public Car createCar(); } // 扩展的工厂 public static class AudiCarFactory implements CarFactory{ @Override public Car createCar() { return new Audi(); } } public static class BydCarFactory implements CarFactory{ @Override public Car createCar() { return new Byd(); } }}

简单工厂和工厂方法模式的对比

简单工厂只有一个工厂,而工厂方法有多个工厂简单工厂不支持扩展,而工厂方法支持扩展,扩展的方式就是添加对应的工厂类即可简单工厂代码复杂度低,工厂方法代码复杂度高...5.3 抽象工厂

  上面的两种方式实现的工厂都是生产同一大类的产品,如果要实现生产不同类型的产品这时我们就可以用抽象工厂模式来实现。

image.png

代码实现:

/ 抽象工厂:多个产品族 /public class AbstractFactory { public static void main(String[] args) { Car car = new LuxuryEngineCarFacory().createCar(); Engine engine = new LuxuryEngineCarFacory().createEngine(); car.run(); engine.run(); } // 抽象工厂 public static interface AbstarctComponentFactory{ Car createCar(); Engine createEngine(); } public static class LuxuryEngineCarFacory implements AbstarctComponentFactory{ @Override public Engine createEngine() { return new LuxuryEngineFactory().createEngine(); } @Override public Car createCar() { return new BydCarFactory().createCar(); } } public static class LowEngineCarFacory implements AbstarctComponentFactory{ @Override public Car createCar() { return new AudiCarFactory().createCar(); } @Override public Engine createEngine() { return new LowEngineFactory().createEngine(); } } // 汽车产品族 public static interface Car{ public void run(); } public static class Byd implements Car { @Override public void run() { System.out.println("比亚迪..."); } } public static class Audi implements Car { @Override public void run() { System.out.println("奥迪..."); } } public static interface CarFactory{ public Car createCar(); } // 扩展的工厂 public static class AudiCarFactory implements CarFactory { @Override public Car createCar() { return new Audi(); } } public static class BydCarFactory implements CarFactory{ @Override public Car createCar() { return new Byd(); } } // 发动机产品族 public static interface Engine{ public void run(); } public static class LuxuryEngine implements Engine{ @Override public void run() { System.out.println("豪华版发动机..."); } } public static class LowEngine implements Engine{ @Override public void run() { System.out.println("低配版发动机..."); } } public static interface EngineFactory{ public Engine createEngine(); } public static class LuxuryEngineFactory implements EngineFactory{ @Override public Engine createEngine() { return new LuxuryEngine(); } } public static class LowEngineFactory implements EngineFactory{ @Override public Engine createEngine() { return new LowEngine(); } }}

三者的对比:

简单工厂模式(静态工厂模式) :虽然某种程度不符合设计原则,但实际使用最多。
工厂方法模式:不修改已有类的前提下,通过增加新的工厂类实现扩展。
抽象工厂模式:不可以增加产品,可以增加产品族!
6.谈谈你对建造者模式的理解

  实际开发中,我们所需要的对象构建时非常复杂,且有很多步骤需要处理时,这时建造者模式就很适合。
比如MyBatis中的SqlSessionFactory对象的创建,我们不光要创建SqlSessionFactory本身的对象,还有完成MyBatis的全局配置文件和映射文件的加载解析操作,之后把解析出来的信息绑定在SqlSessionFactory对象中,

image.png

直接参考MyBatis的代码即可

image.png

所以建造者模式的作用就是帮助我们解决了复杂对象的创建

7.谈谈你对原型模式的理解

  在java中我们知道通过new关键字创建的对象是非常繁琐的(类加载判断,内存分配,初始化等),在我们需要大量对象的情况下,原型模式就是我们可以考虑实现的方式。
  原型模式我们也称为克隆模式,即一个某个对象为原型克隆出来一个一模一样的对象,该对象的属性和原型对象一模一样。
而且对于原型对象没有任何影响。
原型模式的克隆方式有两种:浅克隆和深度克隆.

7.1 浅克隆

  被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。
换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
Object类提供的方法clone=只是拷贝本对象= , =其对象内部的数组、引用对象等都不拷贝= ,还是指向原生对象的内部元素地址.

  被克隆的对象必须Cloneable,Serializable这两个接口;

package com.bobo.prototype;import java.io.Serializable;import java.util.Date;public class User implements Cloneable, Serializable { private String name; private Date birth; private int age; / 实现克隆的方法 @return @throws CloneNotSupportedException / @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirth() { return birth; } public void setBirth(Date birth) { this.birth = birth; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public static void main(String[] args) throws Exception { // 创建一个普通对象 Date date = new Date(666666); User user = new User(); user.setName("波波烤鸭"); user.setAge(18); user.setBirth(date); System.out.println("原型对象的属性:" + user); // 克隆对象 User cloneUser = (User) user.clone(); System.out.println("克隆的对象的属性:" + cloneUser); // 修改原型对象的属性 date.setTime(12345677); // 修改克隆对象的属性 cloneUser.setName("波哥"); System.out.println("原型对象的属性:" + user); System.out.println("克隆的对象的属性:" + cloneUser); } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", birth=" + birth + ", age=" + age + '}'; }}

输出结果

image.png

浅克隆的问题:虽然产生了两个完全不同的对象,但是被复制的对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。

7.2 深度克隆

  被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。
那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。
换言之,深复制把要复制的对象所引用的对象都复制了一遍。
实现的效果是:

image.png

深度克隆(deep clone)有两种实现方式,第一种是在浅克隆的基础上实现,第二种是通过序列化和反序列化实现,我们分别来介绍

方式一:在浅克隆的基础上实现

/ 实现克隆的方法 @return @throws CloneNotSupportedException / @Override protected Object clone() throws CloneNotSupportedException { User user = (User) super.clone(); // 实现深度克隆 user.birth = (Date) this.birth.clone(); return user; }

image.png

方式二:序列化和反序列化

名称 说明 序列化 把对象转换为字节序列的过程。
反序列化 把字节序列恢复为对象的过程。

public static void main(String[] args) throws CloneNotSupportedException, Exception { Date date = new Date(1231231231231l); User user = new User(); user.setName("波波烤鸭"); user.setAge(18); user.setBirth(date); System.out.println("-----原型对象的属性------"); System.out.println(user); //使用序列化和反序列化实现深复制 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(user); byte[] bytes = bos.toByteArray(); ByteArrayInputStream bis = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bis); //克隆好的对象!
User user1 = (User) ois.readObject(); // 修改原型对象的值 date.setTime(221321321321321l); System.out.println(user.getBirth()); System.out.println("------克隆对象的属性-------"); System.out.println(user1);}

标签:

相关文章