面试专题之设计模式

对设计模式知识若有欠缺可以查看本人的设计模式专栏

题目

  1. 什么是设计模式?聊一聊设计模式
  2. 简述一下你了解的设计模式,并举例说明在jdk源码中哪些用到了你说的设计模式
  3. 用 Java 写一个***单例类?讲讲几种创建单例的方式?选择那种进行创建?
  4. Spring 的 IoC 容器可以为普通的类创建单例,它是怎么做到的呢?
  5. 除了单例模式,你在生产环境中还用过什么设计模式?
  6. 你能解释一下里氏替换原则吗?
  7. 什么情况下会违反迪米特法则?为什么会有这个问题?
  8. 适配器模式是什么?什么时候使用?
  9. 适配器模式和装饰器模式有什么区别?
  10. 适配器模式和代理模式之前有什么不同?
  11. 代理模式和装饰器模式有什么区别
  12. 什么是模板方法模式?
  13. 静态代理和动态代理的区别? 如何实现动态代理 ? 两种动态代理的区别?
  14. IO流熟悉吗,用的什么设计模式?
  15. java中什么叫做观察者模式(observer design pattern)?写一个瞧瞧
  16. 使用工厂模式最主要的好处是什么?在哪里使用?
  17. 举一个用 Java 实现的装饰模式(decorator design pattern)?它是作用于对象层次还是类层次?

答案

  1. 什么是设计模式?聊一聊设计模式

所谓设计模式,就是一套被反复使用的代码设计经验的总结(情境中一个问题经过证实的一个解决方案)。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式使人们可以更加简单方便的复用成功的设计和体系结构。将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。

在 GoF 的《Design Patterns: Elements of Reusable Object-OrientedSoftware》中给出了三类设计模式:
创建型:对类的实例化过程的抽象化
结构型:描述如何将类或对象结合在一起形成更大的结构
行为型:对在不同的对象之间划分责任和算法的抽象化
共 23 种设计模式,包括:Abstract Factory(抽象工厂模式),Builder(建造者模式),Factory Method(工厂方法模式),Prototype(原始模型模式),Singleton(单例模式);Facade(门面模式),Adapter(适配器模式),Bridge(桥梁模式),Composite(合成模式),Decorator(装饰模式),Flyweight(享元模式),Proxy(代理模式);Command(命令模式),Interpreter(解释器模式),Visitor(访问者模式),Iterator(迭代子模式),Mediator(调停者模式),Memento(备忘录模式),Observer(观察者模式),State(状态 模式 ),Strategy(策略 模式 ),Template Method(模板方法模式),Chain Of Responsibility(责任链模式)。
在这里插入图片描述
2. 简述一下你了解的设计模式,并举例说明在jdk源码中哪些用到了你说的设计模式

面试被问到关于设计模式的知识时,可以拣最常用的作答,下面把常用的几个设计模式列举一下:

(1)工厂模式:工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。
(2)代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。实际开发中,按照使用目的的不同,代理可以分为:远程代理、虚拟代理、保护代理、Cache 代理、防火墙代理、同步化代理、智能引用代理。
(3)适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起使用的类能够一起工作。
(4)模板方法模式:提供一个抽象类,将部分逻辑以具体方法或构造器的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法(多态实现),从而实现不同的业务逻辑。
(5)单例模式
保证一个类只有一个实例,并且提供一个访问该全局访问点(换种说法就是保证jvm中只有该类的一个实例)
(6)门面模式(又称外观模式),隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
(7)装饰模式:也成为包装模式,顾名思义,就是对已经存在的某些类进行装饰,以此来扩展一些功能。
(8)观察模式:是一种行为性模型,行为型模式关注的是系统中对象之间的相互交互,解决系统在运行时对象之间的相互通信和协作,进一步明确对象的职责。相比来说,创建型模式关注对象的创建过程,结构型模式关注对象和类的组合关系。

当然设计模式还有很多种,反正基本原则就是拣自己最熟悉的、用得最多的作答,以免言多必失。

源码中的应用
单例:ioc容器(默认情况下是单例的)
模板:ioc、springmvc
建造者模式:lombok
工厂:ioc
代理:aop
订阅/发布:消息队列,redis的pub/sub
装饰器:io

  1. 用 Java 写一个***单例类?讲讲几种创建单例的方式?选择那种进行创建?

(1)饿汉式单例

public class Singleton {
	private Singleton(){
	}
	private static Singleton instance = new Singleton();
	public static Singleton getInstance(){
		return instance;
	}
}

(2)懒汉式单例

public class Singleton {
	private static Singleton instance = null;
	private Singleton() {
	}
	public static synchronized Singleton getInstance(){
		if (instance == null) instance = new Singleton();
		return instance;
	}
}

(3)静态内部类

// 静态内部类方式
public class SingletonDemo03 {
	private SingletonDemo03() {
           System.out.println("初始化..");
	}

	public static class SingletonClassInstance {
		private static final SingletonDemo03 singletonDemo03 = new SingletonDemo03();
	}

	// 方法没有同步
	public static SingletonDemo03 getInstance() {
		System.out.println("getInstance");
		return SingletonClassInstance.singletonDemo03;
	}

	public static void main(String[] args) {
		SingletonDemo03 s1 = SingletonDemo03.getInstance();
		SingletonDemo03 s2 = SingletonDemo03.getInstance();
		System.out.println(s1 == s2);
	}
}

(4)枚举

public class User {
	public static User getInstance() {
		return SingletonDemo04.INSTANCE.getInstance();
	}

	private static enum SingletonDemo04 {
		INSTANCE;
		// 枚举元素为单例
		private User user;

		private SingletonDemo04() {
			System.out.println("SingletonDemo04");
			user = new User();
		}

		public User getInstance() {
			return user;
		}
	}

	public static void main(String[] args) {
		User u1 = User.getInstance();
		User u2 = User.getInstance();
		System.out.println(u1 == u2);
	}
}

(5)双重检验锁

public class Test05Singleton {
    private static Test05Singleton test05Singleton;

    public Test05Singleton() {
        System.out.println("类初始化");
    }
    //线程安全,效率底
    public static Test05Singleton getInstance(){
        if (test05Singleton == null){
            synchronized (Test05Singleton.class){
                if (test05Singleton == null){
                    test05Singleton = new Test05Singleton();
                }
            }
        }
        return test05Singleton;
    }

    public static void main(String[] args) {
        Test05Singleton instance = Test05Singleton.getInstance();
        Test05Singleton instance2 = Test05Singleton.getInstance();
        System.out.println(instance == instance2);
    }
}

如果不需要延迟加载单例,可以使用枚举或者饿汉式,相对来说枚举性好于饿汉式。
如果需要延迟加载,可以使用静态内部类或者懒韩式,相对来说静态内部类好于懒韩式。

注意:实现一个单例有两点注意事项,①将构造器私有,不允许外界通过构造器创建对象;②通过公开的静态方法向外界返回类的唯一实例。

  1. Spring 的 IoC 容器可以为普通的类创建单例,它是怎么做到的呢?
    当一个bean的作用域为singleton, 那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。注意:Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:
<bean id="empServiceImpl"class="cn.csdn.service.EmpServiceImpl" 
scope="singleton">
  1. 除了单例模式,你在生产环境中还用过什么设计模式?
    这需要根据你的经验来回答。一般情况下,你可以说依赖注入,工厂模式,装饰模式或者观察者模式,随意选择你使用过的一种即可。不过你要准备回答接下的基于你选择的模式的问题。

  2. 你能解释一下里氏替换原则吗?
    任何基类可以出现的地方,子类一定可以出现,首先,这是编译器的要求,如果不这么做,无法通过编译。其次,面向对象的编程,其中继承有个大原则,任何子类的对象都可以当成父类的对象使用。

  3. 什么情况下会违反迪米特法则?为什么会有这个问题?
    迪米特法则可以简单说成:talk only to your immediate friends。 对于OOD来说,又被解释为下面几种方式:一个软件实体应当尽可能少的与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
    迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。
    迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。

  4. 适配器模式是什么?什么时候使用?
    适配器模式提供对接口的转换。如果你的客户端使用某些接口,但是你有另外一些接口,你就可以写一个适配去来连接这些接口。

  5. 适配器模式和装饰器模式有什么区别?
    虽然适配器模式和装饰器模式的结构类似,但是每种模式的出现意图不同。适配器模式被用于桥接两个接口,而装饰模式的目的是在不修改类的情况下给类增加新的功能。

  6. 适配器模式和代理模式之前有什么不同?
    这个问题与前面的类似,适配器模式和代理模式的区别在于他们的意图不同。由于适配器模式和代理模式都是封装真正执行动作的类,因此结构是一致的,但是适配器模式用于接口之间的转换,而代理模式则是增加一个额外的中间层,以便支持分配、控制或智能访问。

  7. 代理模式和装饰器模式有什么区别
    代理模式,注重对对象某一功能的流程把控和辅助。它可以控制对象做某些事,重心是为了借用对象的功能完成某一流程,而非对象功能如何。
    装饰模式,注重对对象功能的扩展,它不关心外界如何调用,只注重对对象功能的加强,装饰后还是对象本身。
    推荐阅读:代理模式和装饰器模式的区别

  8. 什么是模板方法模式?
    模板方法提供算法的框架,你可以自己去配置或定义步骤。例如,你可以将排序算法看做是一个模板。它定义了排序的步骤,但是具体的比较,可以使用Comparable 或者其语言中类似东西,具体策略由你去配置。列出算法概要的方法就是众所周知的模板方法。

  9. 静态代理和动态代理的区别? 如何实现动态代理 ? 两种动态代理的区别?

静态代理:由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。代理类必须要实现被代理类的接口,这是非常不利于扩展的。
相较于静态代理,动态代理代理对象则不需要实现代理接口,而动态之所以为动态就是代码运行前代理类和委托类的关系是不确定的,因为动态代理使用了类加载器和动态代理进行实现。
jdk动态代理

// 每次生成动态代理类对象时,实现了InvocationHandler接口的调用处理器对象 
public class InvocationHandlerImpl implements InvocationHandler {
	private Object target;// 这其实业务实现类对象,用来调用具体的业务方法
	// 通过构造函数传入目标对象
	public InvocationHandlerImpl(Object target) {
		this.target = target;
	}

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object result = null;
		System.out.println("调用开始处理");
		result = method.invoke(target, args);
		System.out.println("调用结束处理");
		return result;
	}

	public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
			IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		// 被代理对象
		IUserDao userDao = new UserDao();
		InvocationHandlerImpl invocationHandlerImpl = new InvocationHandlerImpl(userDao);
		ClassLoader loader = userDao.getClass().getClassLoader();
		Class<?>[] interfaces = userDao.getClass().getInterfaces();
		// 主要装载器、一组接口及调用处理动态代理实例
		IUserDao newProxyInstance = (IUserDao) Proxy.newProxyInstance(loader, interfaces, invocationHandlerImpl);
		newProxyInstance.save();
	}

}

cglib动态代理

public class CglibProxy implements MethodInterceptor {
	private Object targetObject;
	// 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理
	public Object getInstance(Object target) {
		// 设置需要创建子类的类
		this.targetObject = target;
		//生成目标代理对象虚拟的子类
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(target.getClass());
		enhancer.setCallback(this);
		return enhancer.create();
	}

	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		System.out.println("开启事物");
		Object result = proxy.invoke(targetObject, args);
		System.out.println("关闭事物");
		// 返回代理对象
		return result;	
	}
	public static void main(String[] args) {
		CglibProxy cglibProxy = new CglibProxy();
		UserDao userDao = (UserDao) cglibProxy.getInstance(new UserDao());	
		userDao.save();
	}
}

两种动态代理的区别:
(1)java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
(2)而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
(3)Spring中:
a. 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
b. 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
c. 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
(4)JDK动态代理只能对实现了接口的类生成代理,而不能针对类 。
CGLIB是针对类实现代理,主要是对指定的类生成一个虚拟子类子类,覆盖其中的方法 。

注:因为是继承,所以目标代理类或方法最好不要声明成final ,final可以阻止继承和多态,否则会出现如下错误:

推荐阅读:
动态代理部分

  1. IO流熟悉吗,用的什么设计模式?

IO中用到的适配器模式
在IO中,如将字符串数据转变成字节数据保存到文件中,将字节数据转变成流数据等都用到了适配器模式,下面以InputStreamReader和OutputStreamWriter类为例介绍适配器模式。
InputStreamReader和OutputStreamWriter类分别继承了Reader和Writer接口,但要创建它们必须在构造函数中传入一个InputStream和OutputStream的实例,InputStreamReader和OutputStreamWriter的作用也就是将InputStream和OutputStream适配到Reader和Writer。
InputStreamReader实现了Reader接口,并且持有了InputStream的引用,这是通过StreamDecoder类间接持有的,因为byte到char要经过编码。
这里,适配器就是InputStreamReader类,而源角色就是InputStream代表的实例对象,目标接口就是Reader类,OutputStreamWriter类也是类似的方式。
在IO中类似的还有,如StringReader将一个String类适配到Reader接口,ByteArrayInputStream适配器将byte数组适配到InputStream流处理接口。

IO中用到的装饰模式
装饰模式就是对一个类进行装饰,增强其方法行为,在装饰模式中,作为原来的这个类使用者还不应该感受到装饰前与装饰后有什么不同,否则就破坏了原有类的结构了,所以装饰器模式要做到对被装饰类的使用者透明,这是对装饰器模式的一个要求。总之装饰器设计模式就是对于原有功能的扩展
在IO中有许多不同的功能组合情况,这些不同的功能组合都是使用装饰器模式实现的,下面以FilterInputStream为例介绍装饰器模式的使用。
InputStream类就是以抽象组件存在的,而FileInputStream就是具体组件,它实现了抽象组件的所有接口,FilterInputStream类就是装饰角色,它实现了InputStream类的所有接口,并持有InputStream的对象实例的引用,BufferedInputStream是具体的装饰器实现者,这个装饰器类的作用就是使得InputStream读取的数据保存在内存中,而提高读取的性能。类似的还有LineNumberInputStream类,它的作用是提高按行读取数据的功能。

总结
这两种设计模式看起来都是起到包装一个类或对象的作用,但是使用它 们的目的却不尽相同。适配器模式主要在于将一个接口转变成另一个接口,它的目的是通过改变接口来达到重复使用的目的;而装饰器模式不是要改变被装饰对象的接口,而是保持原有的接口,但是增强原有对象的功能,或改变原有对象的方法而提高性能。

  1. java中什么叫做观察者模式(observer design pattern)?写一个瞧瞧
    观察者模式(Observer),是一种行为性模型,行为型模式关注的是系统中对象之间的相互交互,解决系统在运行时对象之间的相互通信和协作,进一步明确对象的职责。相比来说,创建型模式关注对象的创建过程,结构型模式关注对象和类的组合关系。
    观察者模式主要用于1对N的通知。当一个对象的状态变化时,他需要及时告知一系列对象,令他们做出相应。
    实现有两种方式:
    推:每次都会把通知以广播的方式发送给所有观察者,所有的观察者只能被动接收。
    拉:观察者只要知道有情况即可,至于什么时候获取内容,获取什么内容,都可以自主决定。
/**
 * @Auther: 洺润Star
 * @Date: 2020/3/6 10:46
 * @Description:观察者的接口,用来存放观察者共有方法
 */
public interface Observer {
    // 观察者方法
    void update(RealObserver realObserver);
}
/**
 * @Auther: 洺润Star
 * @Date: 2020/3/6 10:47
 * @Description:观察对象的父类
 */
public class Subjecct {
    //观察者的存储集合
    private List<Observer> list = new ArrayList<>();

    // 注册观察者方法
    public void registerObserver(Observer obs) {
        list.add(obs);
    }
    // 删除观察者方法
    public void removeObserver(Observer observer,RealObserver realObserver) {
        list.remove(observer);
        this.notifyAllObserver(realObserver);
    }

    // 通知所有的观察者更新
    public void notifyAllObserver(RealObserver realObserver) {
        for (Observer observer : list) {
            observer.update(realObserver);
        }
    }

}
/**
 * @Auther: 洺润Star
 * @Date: 2020/3/6 10:48
 * @Description:具体观察者对象的实现
 */
public class RealObserver extends Subjecct {
    //被观察对象的属性
    private int state;
    public int getState(){
        return state;
    }
    public void  setState(int state){
        this.state=state;
        //主题对象(目标对象)值发生改变
        this.notifyAllObserver(this);
    }

}
/**
 * @Auther: 洺润Star
 * @Date: 2020/3/6 10:50
 * @Description:观察者
 */
public class ObserverA implements Observer {
    private int state;
    @Override
    public void update(RealObserver realObserver) {
        this.state= realObserver.getState();
    }

    public int getMyState(){
        return state;
    }
}

public class Client {

 public static void main(String[] args) {
        // 目标对象
        RealObserver subject = new RealObserver();
        // 创建多个观察者
        ObserverA obs1 = new ObserverA();
        ObserverA obs2 = new ObserverA();
        ObserverA obs3 = new ObserverA();
        // 注册到观察队列中
        subject.registerObserver(obs1);
        subject.registerObserver(obs2);
        subject.registerObserver(obs3);
        // 改变State状态
        subject.setState(300);
        System.out.println(obs1.getMyState());
        System.out.println(obs2.getMyState());
        System.out.println(obs3.getMyState());
        // 改变State状态
        subject.setState(400);
        System.out.println(obs1.getMyState());
        System.out.println(obs2.getMyState());
        System.out.println(obs3.getMyState());
        //删除观察者
        subject.removeObserver(obs1,subject);
        System.out.println("删除一个观察者");
        // 改变State状态
        subject.setState(500);
        System.out.println(obs1.getMyState());//值将不会再变化
        System.out.println(obs2.getMyState());
        System.out.println(obs3.getMyState());
    }
}
  1. 使用工厂模式最主要的好处是什么?在哪里使用?
    工厂模式的最大好处是增加了创建对象时的封装层次。如果你使用工厂来创建对象,之后你可以使用更高级和更高性能的实现来替换原始的产品实现或类,这不需要在调用层做任何修改。
  2. 举一个用 Java 实现的装饰模式(decorator design pattern)?它是作用于对象层次还是类层次?
    装饰模式增加强了单个对象的能力。 Java IO 到处都使用了装饰模式,典型例子就是Buffered 系列类如 BufferedReader 和 BufferedWriter,它们增强了 Reader 和 Writer 对象,以实现提升性能的 Buffer 层次的读取和写入。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值