设计模式之委派模式与模板模式详解和应用

news2024/11/13 20:53:59

目录

  • 1 委派模式
    • 1.1 目标
    • 1.2 内容定位
    • 1.3 定义
    • 1.4 委派模式的应用场景
    • 1.5 委派模式在业务场景中的应用
    • 1.6 委派模式在源码中的体现
      • 1.6.1 双亲委派模型
      • 1.6.2 常用代理执行方法 invoke
      • 1.6.3 Spring loC中 在调用 doRegisterBeanDefinitions()
      • 1.6.4 SpringMVC 的DispatcherServlet
    • 1.7 委派模式的优缺点
  • 2 模板模式
    • 2.1 定义
    • 2.2 应用场景
    • 2.3 模板方法中的钩子方法
    • 2.4 利用模板方法模式重构 JDBC 操作业务场景
    • 2.5 模板方法模式在源码中的体现
    • 2.6 模板方法模式的优缺点


1 委派模式

1.1 目标

1 、掌握委派模式, 精简程序逻辑, 提升代码的可读性。

2、学会用模板方法模式梳理使用工作中流程标准化的业务场景。

1.2 内容定位

1 、希望通过对委派模式的学习,让自己写出更加优雅的代码的人群。

2、深刻了解模板方法模式的应用场景。

1.3 定义

委派模式(Delegate Pattern ) 又叫委托模式, 是一种面向对象的设计模式, 允许对象组合实现与 继承相同的代码重用。它的基本作用就是负责任务的调用和分配任务, 是一种特殊的静态代理, 可以理 解为全权代理, 但是代理模式注重过程,而委派模式注重结果。委派模式属于行为型模式, 不属于GO F 23 种设计模式中。

1.4 委派模式的应用场景

委派模式在 Spring 中应用非常多 , 大家常用的 DispatcherServlet 其实就是用到了委派模式。先来看一下类图:

img

从类图中我们可以看到, 委派模式有三个参与角色:

抽象任务角色( Task ) : 定义一个抽象接口, 它有若干实现类。

委派者角色( Delegate ) : 负责在各个具体角色实例之间做出决策并判断并调用具体实现的方法。

具体任务角色( Concrete ) 真正执行任务的角色。

现实生活中也室有委派的场景发生,例如:老板( Boss ) 给项目经理( Leader ) 下达任务,项目经 理会根据实际情况给每个员工派发工作任务, 待员工把工作任务完成之后, 再由项目经理汇报工作进度 和结果给老板。

img

1.5 委派模式在业务场景中的应用

我们用代码来模拟下这个业务场景 , 创建 IEmployee员工接口 :

public interface IEmployee {
    void doing(String task);
}

复制

创建员工 EmployeeA 类:

public class EmployeeA implements IEmployee {
    protected String goodAt = "编程";

    public void doing(String task) {
        System.out.println("我是员工A,我擅长" + goodAt + ",现在开始做" + task + "工作");
    }
}

复制

创建员工 Employees 类:

public class EmployeeB implements IEmployee {
    protected String goodAt = "平面设计";
    public void doing(String task) {
        System.out.println("我是员工B,我擅长" + goodAt + ",现在开始做" +task + "工作");
    }
}

复制

创建项目经理 Leader 类:

public class Leader implements IEmployee {

    private Map<String, IEmployee> employee = new HashMap<String, IEmployee>();

    public Leader() {
        employee.put("爬虫", new EmployeeA());
        employee.put("海报图", new EmployeeB());
    }

    public void doing(String task) {
        if (!employee.containsKey(task)) {
            System.out.println("这个任务" + task + "超出我的能力范围");
            return;
        }
        employee.get(task).doing(task);
    }
}

复制

创建 Boss 类下达命令:

public class Boss {
    public void command(String task,Leader leader){
        leader.doing(task);
    }
}

测试代码:

public class Test {
    public static void main(String[] args) {
        new Boss().command("海报图",new Leader());
        new Boss().command("爬虫",new Leader());
        new Boss().command("卖手机",new Leader());
    }
}

运行结果:

我是员工B,我擅长平面设计,现在开始做海报图工作
我是员工A,我擅长编程,现在开始做爬虫工作
这个任务卖手机超出我的能力范围

通过上面的代码 , 生动地还原了项目经理分配工作的业务场景 ,也是委派模式的生动体现。下面来 看一下类图:

img

1.6 委派模式在源码中的体现

1.6.1 双亲委派模型

JDK 中有一个典型的委派 ,众所周知 JVM 在加载类是用的双亲委派模型 ,这又是什么呢?—类加载器在加载类时 , 先把这个请求委派给自己的父类加载器去执行 ,如果父类加载器还存在父类加载器 , 就继续向上委派,直到顶层的启动类加载器。如果父类加载器能够完成类加载,就成功返回,如果父类 加载器无法完成加载,那么子加载器才会尝试自己去加载。从定义中可以看到双亲加载模型一个类加载 器加载类时 , 首先不是自己加载 ,而是委派给父加载器。下面我们来看看 loadClass()方法的源码 ,此 方法在 Classloader 中。在这个类里就定义了一个双亲,用于下面的类加载。

public abstract class ClassLoader {
    private final ClassLoader parent;
    
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
}

1.6.2 常用代理执行方法 invoke

/*
同样在反射里的 Method 类里我们常用代理执行方法 invoke也存在类似的机制。

public final class Method extends Executable {
    @CallerSensitive
    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }
}

看完代码 , 相信小伙伴们对委派和代理区别搞清楚了吧。

1.6.3 Spring loC中 在调用 doRegisterBeanDefinitions()

下面来看一下委派模式在Spring 中的应用在Spring loC中 在调用 doRegisterBeanDefinitions() 方法时即 BeanDefinition进行注册的过程中,会设置 BeanDefinitionParserDelegate类型的 Delegate对象传给 this.delegate 并将这个对象作为一个参数传给 :parseBeanDefinitions(root, this.delegate) 中 ,然后主要的解析的工作就是通过 delegate作为主要角色来完成的, 可以看到下方代码 :

public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
    // 判断节点是否属于同一令名空间 , 是则执行后续的解析 
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		if (delegate.isDefaultNamespace(root)) {
			NodeList nl = root.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
				if (node instanceof Element) {
					Element ele = (Element) node;
					if (delegate.isDefaultNamespace(ele)) {
						parseDefaultElement(ele, delegate);
					}
					else {
                        //注解定义的 context 的 namespace 进入到这个分支中
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}
}   

其中最终能够走到 bean 注册部分的是 ,会进入到 parseDefault Element(ele, delegate)中,然后 针对不同的节点类型针对 bean 的节点进行真正的主册操作而在这个过程中,delegate 会对element 进行 parseBeanDefinitionElement , 得到了一个 BeanDefinitionHolder 类型的对象,之后通过这个 对象完成真正的注册到 Factory 的操作。

1.6.4 SpringMVC 的DispatcherServlet

下面我们再来还原一下 SpringMVC 的 DispatcherServlet 是如何实现委派模式的。创建业务类 MemberController :

public class MemberController {
    public void getMemberById(String mid) {
    }
}

复制

OrderController 类 :

public class OrderController {
    public void getOrderById(String mid) {
    }
}

复制

SystemController 类:

public class SystemController {
    public void logout() {
    }
}

创建 DispatcherServlet 类:

public class DispatcherServlet extends HttpServlet {

    private Map<String, Method> handlerMapping = new HashMap<String, Method>();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatch(req, resp);
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
        String url = req.getRequestURI();
        Method method = handlerMapping.get(url);
        // method.invoke();
    }

    @Override
    public void init() throws ServletException {
        try {
            handlerMapping.put("/web/getMemeberById.json", MemberController.class.getMethod("getMemberById", String.class));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

配置 web.xml 文件 :

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4">
    <display-name>Gupao Web Application</display-name>

    <servlet>
        <servlet-name>delegateServlet</servlet-name>
        <servlet-class>com.gupaoedu.vip.pattern.delegate.mvc.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>delegateServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

一个完整的委派模式就实现出来了。当然,在 Spring 中运用到委派模式不仅千此,还有很多。小伙伴们可以通过命名就可以识别。在 Spring 源码中 , 只要以 Delegate 结尾的都是实现了委派模式。例 如: BeanDefinitionParserDelegate 根据不同类型委派不同的逻辑解析 BeanDefinition。

1.7 委派模式的优缺点

优点:

  • 通过任务委派能够将—个大型的任务细化,然后通过统—管理这些子任务的完成情况实现任务的跟进,能够加快任务执行的效率。

缺点:

  • 任务委派方式需要根据任务的复杂程度进行不同的改变,在任务比较复杂的情况下可能需要进行多 重委派,容易造成索乱。

2 模板模式

2.1 定义

模板模式 ( Template Method Pattern) 又叫模板方法模式,是指定义一个操作中的算法的框 架,而将一些步骤延迟到子类中。使得子类可以不沈变一个算法的结构即可重定义该算法的某些特定步 骤,属于行为型设计模式。

模板方法模式实际上是封装了一个固定流程,该流程由几个步骤组成,具体步骤可以由子类进行不 同实现,从而让固定的流程产生不同的结果。它非第简单, 其实就是类的继承机制,但它却是一个应用 非第广泛的模式。模板方法模式的本质是抽象封装流程,具体进行实现。

2.2 应用场景

当完成一个操作具有固定的流程时 , 由抽象固定流程步骤 , 具体步骤交给子类进行具体实现(固定 的流程 , 不同的实现 ) 。

在我们日室生活中模板方法模式也很室见。 比如我们平时办理入职流程填写入职登记表一打印简 历一复印学历一复印身份证一签订劳动合同一建立花名册一办理工牌一安排工位等;再比 如 , 我平时在家里炒菜:洗锅一点火 一热锅一上油一下原料一翻炒 一放调料一出锅;再 比如有个小品 , 赵本山问宋丹丹: “如何把大象放进冰箱? ” 宋丹丹回答: “第—步:打开冰箱门, 第二 步:把大象哀进冰箱 , 第三步:关闭冰箱门” 。赵本山再问: “怎么把长劲鹿放进冰箱? ” 宋f]f]答: “第—步:打开冰箱门, 第二步:把大象拿出来 , 第三步:把长劲鹿哀进去 , 第四步:关闭冰箱门” (如 下图所示 ), 这些都是模板方法模式的体现。

img

模板方法模式适用于以下应用场景:

1、 —次性实现一个算法的不变的部分 , 并将可变的行为留给子类来实现。

2、 各子类中公共的行为被提取出来并集中到一个公共的父类中 ,从而避免代码重复。

首先来看下模板方法模式的通用 UML 类图:

img

2.3 模板方法中的钩子方法

我们还是以咕泡学院的课程创建流程为例:发布预习资料→ 制作课件 PPT → 在线直播→ 提交 课堂笔记→ 提交源码→ 布置作业→ 检查作业。 首先我们来创建 AbastractCourse 抽象类:

public abstract class AbastractCourse {
    
    public final void createCourse(){
        //1、发布预习资料
        postPreResoucse();
        
        //2、制作课件
        createPPT();
        
        //3、直播授课
        liveVideo();
        
        //4、上传课后资料
        postResource();
        
        //5、布置作业
        postHomework();
        
        if(needCheckHomework()){
            checkHomework();
        }
    }

    protected abstract void checkHomework();

    //钩子方法
    protected boolean needCheckHomework(){return  false;}

    protected void postHomework(){
        System.out.println("布置作业");
    }

    protected void postResource(){
        System.out.println("上传课后资料");
    }

    protected void liveVideo(){
        System.out.println("直播授课");
    }

    protected void createPPT(){
        System.out.println("制作课件");
    }

    protected void postPreResoucse(){
        System.out.println("发布预习资料");
    }

}

上面的代码中有个钩子方法可能有些小伙伴还不是太理解 , 在此我稍作解释。设计钩子方法的主要 目的是用来干预执行流程 , 使得我们控制行为流程更加灵活, 更符合实际业务的需求。 钩子方法的返回 值—般为适合条件分支语句的返回值 (如 boolean、 int等 )。小伙伴们可以根据自己的业务场景来决 定是否需要使用钩子方法。 接下来创建 JavaCourse 类:

public class JavaCourse extends AbastractCourse {
    private boolean needCheckHomework = false;

    public void setNeedCheckHomework(boolean needCheckHomework) {
        this.needCheckHomework = needCheckHomework;
    }

    @Override
    protected boolean needCheckHomework() {
        return this.needCheckHomework;
    }

    protected void checkHomework() {
        System.out.println("检查Java作业");
    }
}

创建 PythonCourse 类:

public class PythonCourse extends AbastractCourse {
    protected void checkHomework() {
        System.out.println("检查Python作业");
    }
}

客户端测试代码:

public class Test {
    public static void main(String[] args) {
        System.out.println("=========架构师课程=========");
        JavaCourse java = new JavaCourse();
        java.setNeedCheckHomework(false);
        java.createCourse();

        System.out.println("=========Python课程=========");
        PythonCourse python = new PythonCourse();
        python.createCourse();
    }
}

复制

通过这样一个案例, 相信下伙伴们对模板方法模式有了一个基本的印象。为了加深理解 , 下面我们 来结合—个室见的业务场景。

2.4 利用模板方法模式重构 JDBC 操作业务场景

创建—个模板类 JdbcTemplate , 封装所有的 JDBC 操作。以奎询为例, 每次奎询的表不同, 返回 的数据结构也就不一样。 我们针对不同的数据 , 都要封装成不同的实体对象。而每个实体封装的逻辐都 是不一样的 ,但封装前和封装后的处理流程是不变的 ,因此, 我们可以使用模板方法模式来设计这样的 业务场景。 先创建约束 ORM 逻韬的接口 RowMapper :

public interface RowMapper<T> {
    T mapRow(ResultSet rs,int rowNum) throws Exception;
}

在创建封装了所有处理流程的抽象类 JdbcTemplate :

public abstract class JdbcTemplate {
    private DataSource dataSource;

    public JdbcTemplate(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public final List<?> executeQuery(String sql,RowMapper<?> rowMapper,Object[] values){
        try {
            //1、获取连接
            Connection conn = this.getConnection();
            //2、创建语句集
            PreparedStatement pstm = this.createPrepareStatement(conn,sql);
            //3、执行语句集
            ResultSet rs = this.executeQuery(pstm,values);
            //4、处理结果集
            List<?> result = this.parseResultSet(rs,rowMapper);
            //5、关闭结果集
            rs.close();
            //6、关闭语句集
            pstm.close();
            //7、关闭连接
            conn.close();
            return result;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    private List<?> parseResultSet(ResultSet rs, RowMapper<?> rowMapper) throws Exception {
        List<Object> result = new ArrayList<Object>();
        int rowNum = 0;
        while (rs.next()){
            result.add(rowMapper.mapRow(rs,rowNum++));
        }
        return result;
    }

    private ResultSet executeQuery(PreparedStatement pstm, Object[] values) throws SQLException {
        for (int i = 0; i < values.length; i++) {
            pstm.setObject(i,values[i]);
        }
        return pstm.executeQuery();
    }

    private PreparedStatement createPrepareStatement(Connection conn, String sql) throws SQLException {
        return conn.prepareStatement(sql);
    }

    private Connection getConnection() throws SQLException {
        return this.dataSource.getConnection();
    }
}

创建实体对象 Member 类:

@Data
public class Member {

    private String username;
    private String password;
    private String nickname;
    private int age;
    private String addr;
}

创建数据库操作类 MemberDao:

public class MemberDao extends JdbcTemplate {
    public MemberDao(DataSource dataSource) {
        super(dataSource);
    }

    public List<?> selectAll(){
        String sql = "select * from t_member";
        return super.executeQuery(sql, new RowMapper<Member>() {
            public Member mapRow(ResultSet rs, int rowNum) throws Exception {
            Member member = new Member();
            //字段过多,原型模式
            member.setUsername(rs.getString("username"));
            member.setPassword(rs.getString("password"));
            member.setAge(rs.getInt("age"));
            member.setAddr(rs.getString("addr"));
            return member;
            }
        },null);
    }
}

客户端测试代码:

public class Test {
    public static void main(String[] args) {
        MemberDao memberDao = new MemberDao(null);
        List<?> result = memberDao.selectAll();
    }
}

希望通过这两个案例的业务场景分析 , 能够帮助小伙们对模板方法模式有更深的理解。

2.5 模板方法模式在源码中的体现

先来看 JDK 中的 Abstractlist , 来看代码:

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    abstract public E get(int index);
}

我们看到 get()一个抽象方法 , 那么它的逻辑就是交给子类来实现,我们大家所熟知的 Arraylist 就是 Abstractlist 的子类。同理,有 Abstractlist就有 Abstractset 和 AbstractMap , 有兴趣的小伙伴可以去看看这些的源码实现。还有一个每天都在用的 HttpServlet, 有三个方法 service()和 doGet()、 doPost()方法,都是模板方法的抽象实现。

在 MyBatis框架也有—些经典的应用 ,我们来一下 BaseExecutor 类,它是—个基础的 SQL 执行类, 实现了大部分的 SQL 执行逻辑 ,然后把几个方法交给子类定制化完成 , 源码如下:

public abstract class BaseExecutor implements Executor {
  protected abstract int doUpdate(MappedStatement ms, Object parameter)
      throws SQLException;

  protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
      throws SQLException;

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

  protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
      throws SQLException;
}

如 doUpdate、 doFlushStatements、 doQuery、 doQueryCursor 这几个方法就是交由子类来实 现, 那么 BaseExecutor 有哪些子类呢?我们来看一下它的类图:

我们一起来看—下 SimpleExecutor 的 doUpdate 实现:

public class SimpleExecutor extends BaseExecutor {
  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }
}

再来对比—下 BatchExecutor 的 doUpate 实现:

public class BatchExecutor extends BaseExecutor {
  @Override
  public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
    final Configuration configuration = ms.getConfiguration();
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
    final BoundSql boundSql = handler.getBoundSql();
    final String sql = boundSql.getSql();
    final Statement stmt;
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      applyTransactionTimeout(stmt);
     handler.parameterize(stmt);//fix Issues 322
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    } else {
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);    //fix Issues 322
      currentSql = sql;
      currentStatement = ms;
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
  // handler.parameterize(stmt);
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }
}

细心的小伙伴—定看出来了差异。当然 , 我们芷型旦就暂时不对 MyBatis 源码进行深入分析 , 感兴 趣的小伙伴可以继续关注我们后面的课程。

2.6 模板方法模式的优缺点

优点:

1、 利用模板方法将相同处理逻编的代码放到抽象父类中 , 可以提高代码的复用性。

2、 将不同的代码不同的子类中 , 通过对子类的扩展墙加新的行为 , 提高代码的扩展性。

3、 把不变的行为写在父类上 , 去除子类的重复代码 , 提供了一个很好的代码复用平台 , 符合开闭原则。

缺点:

1、 类数目的培加 , 每一个抽象类都需要一个子类来实现 , 这样导致类的个数谧加。

2、 类数呈的培加 , 间接地墙加了系统实现的复杂度。

3、 继承关系自身缺点 , 如果父类添加新的抽象方法 , 所有子类都要改—遍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/355542.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python基于vue微信小程序的校园闲置二手跳蚤商城的设计与实现

在当今社会的高速发展过程中,产生的劳动力越来越大,提高人们的生活水平和质量,尤其计算机科技的进步,数据和信息以人兴化为本的目的,给人们提供优质的服务,其中网上购买二手商品尤其突出,使我们的购物方式发生巨大的改变。而线上购物,不仅需要在硬件上为人们提供服务网上购物,而…

尚医通 (十七)手机登录

目录一、登录需求分析二、搭建service-user模块三、登录接口实现1、添加service接口与实现2、添加Mapper接口3、添加Controller方法四、手机验证码登录&#xff08;生成token&#xff09;1、使用JWT进行跨域身份验证1.1 传统用户身份验证1.2 解决方案2、JWT介绍3、整合JWT4、单…

Minecraft服务端配置

✨✨前言 ✨✨ 我的世界大家肯定都不陌生&#xff0c;在网易拿下中国区的代理后&#xff0c;很多小伙伴也是都转向了网易版我的世界&#xff0c;网易版我的世界可以说已经做是的十分全面了&#xff0c;使用起来也十分方便&#xff0c;一部分小伙伴也是看重了网易庞大的玩家数量…

使用uniapp创建小程序和H5界面

uniapp的介绍可以看官网&#xff0c;接下来我们使用uniapp创建小程序和H5界面&#xff0c;其他小程序也是可以的&#xff0c;只演示创建这2个&#xff0c;其实都是一套代码&#xff0c;只是生成的方式不一样而已。 uni-app官网 1.打开HBuilder X 选择如图所示&#xff0c;下…

1. Unity的下载与安装

1. 下载 Unity Hub: unity hub是unity编辑器的一个管理工具&#xff0c;负责平时的unity项目创建和管理&#xff0c;以及unity编辑器的安装等 首先在unity官网网址链接&#xff0c;点击左下角的DownLoad Unity图标&#xff0c;如下图&#xff1a; 进入下一个页面&#xff0c;…

LinkedHashMap实现LRU算法

目录LRU 简介LinkedHashMap的使用手写LRU缓存淘汰算法LRU 简介 LRU 是 Least Recently Used 的缩写&#xff0c;这种算法认为最近使用的数据是热门数据&#xff0c;下一次很大概率将会再次被使用。而最近很少被使用的数据&#xff0c;很大概率下一次不再用到。当缓存容量的满时…

show profile和trance分析SQL

目录 一.show profile分析SQL 二.trance分析优化器执行计划 一.show profile分析SQL Mysql从5.0.37版本开始增加了对show profiles和show profile语句的支持。show profiles能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。。 通过have_profiling参数&#xff0c;能够…

J东滑块分析

内容仅供参考学习 欢迎朋友们V一起交流&#xff1a; zcxl7_7 目标 网址&#xff1a;案例地址 J东登录页面会有滑块&#xff0c;直接用来研究 分析 模拟一次触发滑块验证请求(如图) 有2个重要请求&#xff0c;一个是g.html&#xff0c;一个是s.html。其中很明确的就是g是获…

【100个 Unity实用技能】 | Unity 通过自定义菜单将资源导出

Unity 小科普 老规矩&#xff0c;先介绍一下 Unity 的科普小知识&#xff1a; Unity是 实时3D互动内容创作和运营平台 。包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者&#xff0c;借助 Unity 将创意变成现实。Unity 平台提供一整套完善的软件解决方案&#xff…

C++——二叉树的前序遍历||中序遍历||后序遍历 非递归算法

目录二叉树的前序遍历&#xff0c;非递归迭代实现二叉树的中序遍历 &#xff0c;非递归迭代实现二叉树的后序遍历 &#xff0c;非递归迭代实现二叉树的前序遍历&#xff0c;非递归迭代实现 题目链接 思路&#xff1a; 将任何一颗树分成两个部分&#xff0c;一部分是左路节点&a…

用Three.js搭建的一个艺术场景

本文翻译自于Medium&#xff0c;原作者用 Three.js 创建了一个“Synthwave 场景”&#xff0c;效果还不错&#xff0c;在此加上自己的理解&#xff0c;记录一下。在线Demo. 地形构建 作者想要搭建一个中间平坦、两侧有凹凸山脉效果并且能够一直绵延不断的地形&#xff0c;接下…

Quartz组件任务调度管理

Quartz什么是Quartzquartz:石英钟的意思是一个当今市面上流行的高效的任务调度管理工具所谓"调度"就是制定好的什么时间做什么事情的计划由OpenSymphony开源组织开发Symphony:交响乐是java编写的,我们使用时需要导入依赖即可为什么需要Quartz所谓"调度"就是…

18:CTK 总结篇(FAQ)

作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 经过了几个月的艰苦奋战,终于到了最后一节啦,是不是和我一样,心里有点儿小激动! 回顾之前的章节,从初级 -> 进阶 -> 高级,我们针对 CTK 做了详细的分类讲解。希望通过这些知识,大家能对模块化…

管理会计报告和财务报告的区别

财务会计报告是给投资人看的&#xff0c;可以反映公司总体的盈利能力。不过&#xff0c;我们回顾一下前面“第一天”里面提到的问题。如果你是公司的产品经理&#xff0c;目前有三个产品在你的管辖范围内。上级给你一笔新的资金&#xff0c;这笔资金应该投到哪个产品上&#xf…

c++容器

1、vector容器 1.1性质 a&#xff09;该容器的数据结构和数组相似&#xff0c;被称为单端数组。 b&#xff09;在存储数据时不是在原有空间上往后拓展&#xff0c;而是找到一个新的空间&#xff0c;将原数据深拷贝到新空间&#xff0c;释放原空间。该过程被称为动态拓展。 vec…

什么是猜疑心理?小猫测试网科普小作文

什么是猜疑心理&#xff1f;猜疑心理是说一个人心中想法偏离了客观事实&#xff0c;牵强附会&#xff0c;往往是指不好的一面&#xff0c;对别人的一言一行都充满了不良的解读&#xff0c;认为这些对自己都有针对性&#xff0c;目的性&#xff0c;对自己都是不利的。猜疑心理重…

算力引领 数“聚”韶关——第二届中国韶关大数据创新创业大赛圆满收官

为进一步促进数字经济领域创新创业发展&#xff0c;推动国家数据中心集群建设&#xff0c;构建大数据领域资源专业平台&#xff0c;促进大湾区大数据科技成果和创新创业人才转化落地&#xff0c;为韶关大数据领域创新型产业集群的打造、大数据科技成果和创新创业人才的转化落地…

如何选择合适的固态继电器?

如何选择合适的固态继电器&#xff1f; 在选择固态继电器&#xff08;SSR&#xff09;时&#xff0c;应根据实际应用条件和SSR性能参数&#xff0c;特别要考虑到使用中的过流和过压条件以及SSR的负载能力&#xff0c;这有助于实现固态继电器的长寿命和高可靠性。然后&#xff0…

九龙证券|最新评级情况出炉,机构扎堆这一板块!聚氨酯龙头获得最多关注

本周算计254家上市公司获组织“买入型”评级。 电子板块评级组织扎堆 证券时报数据宝计算&#xff0c;2月13日至17日&#xff0c;A股市场53家组织算计进行347次评级&#xff0c;254家上市公司获“买入型”评级&#xff08;包含买入、增持、强烈推荐、推荐&#xff09;。 从申…

ONNX yolov5导出 convert error --grid

使用的版本 https://github.com/ultralytics/yolov5/tree/v5.0 安装onnx torch.onnx.export(model, img, f, verboseFalse, opset_version12, input_names[images],output_names[classes, boxes] if y is None else [output],dynamic_axes{images: {0: batch, 2: height, 3:…