前言(设计模式面试题)
1)有请使用UML类图画出原型模式核心角色
2)原型设计模式的深拷贝和浅拷贝是什么,并写出深拷贝的两种方式的源码(重写,clone方法实现深拷贝、使用序列化来实现深拷贝)
3)在Spring框架中哪里使用到原型模式,并对源码进行分析beans.xml,
<bean id="id01" class="com.atguigu.spring.bean.Monster" scope="prototype"/>
4)设计模式的七大原则:
要求说出:
1)七大设计原则核心思想
2)能够以类图的说明设计原则
3)在项目实际开发中,你在哪里使用到了ocp原则
设计模式常用的七大原则有:
1)单一职责原则
2)接口隔离原则
3)依糗倒转原则
4)里氏替换原则
5)开闭原则ocp
6)迪米特法则
7)合成复用原则
一、单例模式
1.饿汉模式
类加载时,会直接实例化单例对象,以后都返回该对象的引用。
缺点:类加载时,会直接实例化单例对象,不管是否使用到该单例对象,浪费内存。
优点:没有枷锁,执行效率高,线程安全的实例。
public class Singleton{
private Singleton{
}
//创建本类的私有构造方法
private static Singleton singleton = new Singleton();
public static Singleton getInstance(){
return singleton;
}
}
2.懒汉模式
不要直接在类加载时实例化,而是在调用方法时,再实例化。
优点:不会占用内存
缺点:安全方面 单线程情况下,是安全的,但是在多线程下,多个线程可能
同时执行singleton == null 都为true,会创建多个实例在内存中。
public class LazySingleton{
private LazySingleton(){
}
private static LazySingleton singleton;
public static LazySingleton getInstance(){
if(singleton == null){
singleton = new LazySingleton();
}
return singleton;
}
}
2.1懒汉模式(双重检验)
public class LazySingleton{
private LazySingleton(){
}
private static LazySingleton singleton;
public static LazySingleton getInstance(){
/*
双重检验
首先先判断实例是否为null,为null则使用synchronized锁住类,
然后在同步块中,再一次判断实例是否为null,为null则初始化实例。
synchronized(需要锁的对象){}
*/
if(singleton == null){
synchronized(LazySingleton .class){
if(singleton == null){
singleton = new LazySingleton();
}
}
}
return singleton;
}
}
3.内部类实现模式
通过静态内部类,完成单例模式的创建。
在外部类加载时,并不会加载内部类,也就是不会执行new 实例(),这属于懒加载。
只有第一次调用getInstance方法时,才会加载。
public class InnerSingleton{
private InnerSingleton(){
}
private static class Inner{
private static InnerSingleton instance = new InnerSingleton();
}
public static InnerSingleton getInstance(){
return Inner.instance;
}
}
4.枚举实现
通过枚举创建 单例模式。
实现单例的最佳方法。简洁,支持自动序列化机制,防止多次实例化,但目前还没有被广泛采用。
public class EnumSingleton{
private EnumSingleton(){
}
private static enum SinEnum{
//自定义的枚举值,如果没有该自定义枚举值,无法获取枚举对象
SIN;
private EnumSingleton es = new EnumSingleton();
}
public static EnumSingleton getInstance(){
SinEnum s = SinEnum.SIN;
return s.es;
}
}
二、工厂模式
使用者和对象的生产者进行分离。
在工厂模式中,几乎都有三种角色,工厂(抽象工厂、具体工厂) 产品(抽象产品、具体产品) 使用者。使用者想要使用产品,不用自己去生产产品,把生产的动作交给工厂去做,使用者只需要从工厂提供产品的位置(方法)去拿就好。
1.简单工厂模式–顾客需要给出清单。
变化点在产品对象上,所以我们会抽象产品,然后通过一个工厂,根据不同的情况产生不同的产品对象。
2.工厂方法模式–根据工厂能产生什么顾客拿什么。
工厂可以产生统一品牌的商品,会根据商品去抽象工厂,对每一个产品,提供一个工厂实现类。
3.抽象工厂模式–根据工厂能产生什么顾客拿什么,但是工厂能产生的产品会有多种品牌。
超级工厂,可以生产不同品牌的各种产品,抽象出超级工厂,也要抽象出产品,然后根据不同的品牌给出该品牌商品的工工厂实现类。
原文:https://blog.csdn.net/weixin_44002920/article/details/125943548
1.简单工厂
2.工厂方法
3.抽象工厂
三、代理模式
为某个对象提供一个代理,以控制对这个对象的访问。 代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。
从图中可以看出,代理接口(Subject)、代理类(ProxySubject)、委托类(RealSubject)形成一个“品”字结构。
根据代理类的生成时间不同可以将代理分为静态代理和动态代理两种。
1.静态代理
package org.example.a;
//代理接口
interface ExecuteTime {
public void dealTask(String taskName);
}
//被代理类
class ExecuteTimeImpl implements ExecuteTime {
//这里打印出任务名,并休眠500ms模拟任务执行了很长时间
@Override
public void dealTask(String taskName) {
System.out.println("Task is running: "+taskName);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//代理类
class ExecuteTimeProxy implements ExecuteTime {
private ExecuteTime delegate = null;
public ExecuteTimeProxy(ExecuteTimeImpl ExecuteTimeImpl) {
this.delegate = ExecuteTimeImpl;
}
@Override
public void dealTask(String taskName) {
long startTime = System.currentTimeMillis();
delegate.dealTask(taskName);
long endTime = System.currentTimeMillis();
System.out.println("Elapsed time: "+(endTime - startTime)+" ms");
}
}
//工厂方法获得代理对象
class StatiProxyFactory {
//对客户类来说,其并不知道返回的是代理类对象还是委托类对象。
public static ExecuteTime getInstance(){
return new ExecuteTimeProxy(new ExecuteTimeImpl());
}
}
public class Demo{
public static void main(String[] args) {
ExecuteTime proxy = StatiProxyFactory.getInstance();
proxy.dealTask("TestTask");
}
}
接下来看两种动态代理区别:
2.动态代理之JDK代理
1.创建需要被代理的接口(必须要有)和实现类
public interface service {
public void reduceStock();
}
public class ServiceImpl implements service {
//业务方法
@Override
public void reduceStock() {
System.out.println("扣减库存开始");
}
}
2.创建代理类
需要实现InvocationHandler接口,重写invoke方法,这里可以对方法进行增强。
public class Dynamicproxy implements InvocationHandler {
private Object targetObject;
public Dynamicproxy(Object targetObject) {
this.targetObject = targetObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("日志开始");
Object invoke = method.invoke(targetObject, args);
System.out.println("日志结束");
return invoke;
}
}
3.创建动态代理对象
//实现了接口的业务类
ServiceImpl iservice = new ServiceImpl();
//获取Class对象
Class<?> iserviceClass = iservice.getClass();
//代理类 实现需要实现InvocationHandler接口,重写invoke方法 传入业务实现类对象
Dynamicproxy dynamicproxy = new Dynamicproxy(iservice);
//创建代理类对象
service so = (service)Proxy.newProxyInstance(iserviceClass.getClassLoader(),
iserviceClass.getInterfaces(), dynamicproxy);
so.reduceStock();
这样简单的动态代理就实现了。
3.动态代理之Cglib代理
四、适配器模式
五、原型模式
前言:复制粘贴功能我们都用过,我们可以把一个文件从一个地方复制到另外一个地方,复制完成之后这个文件和之前的文件也没有一点差别,这就是原型模式的思想:首先创建一个实例,然后通过这个实例去拷贝创建新的实例。这篇文章就好好地分析一下原型模式。
5.1 认识原型模式
概念:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
我们拿电脑中复制粘贴的例子来演示一下原型模式.
上面这张图已经很明显了,首先我们需要一个文件,这个文件一定要有可以被克隆的功能,那么我们创建这个文件之后,就可以通过它克隆出无数个。
5.2 java类图分析
下面我们再从类图的角度来分析一下:
首先我们可以看到一共有三个角色:
(1)客户(Client)角色:客户类提出创建对象的请求;也就是我们用户使用复制粘贴的功能。
(2)抽象原型(Prototype)角色:此角色定义了的具体原型类所需的实现的方法。也就是定义一个文件,说明一下它有被克隆复制的功能。
(3)具体原型(Concrete Prototype)角色:实现抽象原型角色的克隆接口。就是我们的文件实现了可以被复制的功能。
我们会发现其实原型模式的核心就是Prototype(抽象原型),他需要继承Cloneable接口,并且重写Object类中的clone方法才能有复制粘贴的功能。
5.3 分类
既然我们知道原型模式的核心就是拷贝对象,那么我们能拷贝一个对象实例的什么内容呢?这就要区分深拷贝和浅拷贝之分了。
(1)浅拷贝:我们只拷贝对象中的基本数据类型(8种),对于数组、容器、引用对象等都不会拷贝
(2)深拷贝:不仅能拷贝基本数据类型,还能拷贝那些数组、容器、引用对象等,
下面我们就使用代码去实现一下原型模式。这里实现的是不仅有基本数据类型,还有数组和容器,所以实现的是深拷贝。
5.4 代码实现原型模式
第一步:定义抽象原型
//定义一个文件抽象原型
public class Prototype implements Cloneable{
private Integer fileid;
private String filename;
private Map<String, Double> scores;
//getter和setter方法
//有参构造方法
//toString方法
@Override
protected Prototype clone() {
Prototype filePrototype = null;
try {
//有了下面这句话,基本类型就能克隆了
filePrototype = (FilePrototype) super.clone();
//下面要对每一个复杂对象进行分别克隆
filePrototype.scores = (Map<String, Double>) ((HashMap)this.scores).clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return filePrototype;
}
}
第二步:定义具体原型
public class FileConcretePrototype extends Prototype{
public ConcretePrototype(Integer fileid, String filename,Map<String, Double> scores){
System.out.println("*********我是构造方法***********");
super(fileid, filename, scores);
}
public void show(){
System.out.println("====文件信息=======");
System.out.println("文件名:"+this.getFilename());
System.out.println("文件号:"+this.getFileid());
System.out.println("文件打分:"+this.getScores());
}
}
第三步:定义用户去模拟过程
public class User {
public static void main(String[] args) {
String fileName="重要文件";
int fileId=1;
Map<String, Double> fileScores=new HashMap<String, Double>();
fileScores.put("张三", 99.99);
//第一步创建出一个实例对象
ConcretePrototype fileA=new ConcretePrototype(fileId, fileName, fileScores);
//第二步克隆出来几个
ConcretePrototype fileB=(ConcretePrototype) fileA.clone();
ConcretePrototype fileC=(ConcretePrototype) fileA.clone();
//第三步:输出一下克隆出来的对象是否有变化
fileB.show();
fileC.show();
}
}
//输出是:
/*
*********我是构造方法***********
=========文件信息=============
文件名:重要文件
文件号:1
文件打分:{张三=99.99}
=========文件信息=============
文件名:重要文件
文件号:1
文件打分:{张三=99.99}
*/
我们可以看到,克隆出来的两个文件和之前的文件是一样的,而且我们实现了深拷贝,对于数组、引用等对象同样的适用。
5.5 原型模式分析
1、克隆对象不会调用构造方法
从上面的输出其实我们也可以发现,构造方法只在一开始我们创建原型的时候输出了,fileB和fileC都没有调用构造方法,这是因为执行clone方法的时候是直接从内存中去获取数据的,在第一次创建对象的时候就会把数据在内存保留一份,克隆的时候直接调用就好了
2、访问权限对原型模式无效
原理也很简单,我们是从内存中直接复制的,所以克隆起来也会直接无视,复制相应的内容就好了。
3、使用场景
(1)当我们的类初始化需要消耗很多的资源时,就可以使用原型模式,因为我们的克隆不会执行构造方法,避免了初始化占有的时间和空间。
(2)一个对象被其她对象访问,并且能够修改时,访问权限都无效了,什么都能修改。
OK,对于设计模式其实主要是理解其思想,然后根据自己的需求去灵活的使用。对于其他设计模式的相关文章也在持续更新当中。
原文连接:https://zhuanlan.zhihu.com/p/73675795