设计模式之【模板方法模式】,模板方法和函数式回调,哪个才是趋势?

news2024/12/24 10:18:16

文章目录

  • 一、什么是模板方法模式
    • 1、主要角色
    • 2、应用场景
    • 3、优缺点
    • 4、注意事项及细节
  • 二、实例
    • 1、炒菜案例
      • (1)模板方法模式的钩子方法
    • 2、重构JDBC案例
  • 三、模板方法模式与Callback回调模式
    • 1、回调基本原理
    • 2、案例一:回调方式重构JDBC
    • 3、案例二:注册监听事件
    • 4、模板方法模式 VS 回调
  • 四、源码中的目标方法模式
    • 1、InputStream类
    • 2、AbstractList

一、什么是模板方法模式

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

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

例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。

1、主要角色

在这里插入图片描述

模板方法(Template Method)模式包含以下主要角色:

  • 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
  • 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。

抽象类包含模板方法和基本方法,模板方法定义了算法的骨架,按某种顺序调用其包含的基本方法。基本方法是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:

  • 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。
  • 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
  • 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型或int类型。

2、应用场景

1、一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
2、各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。
3、需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

3、优缺点

优点:

  • 提高代码复用性,将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。
  • 实现了控制反转,通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 ,并符合“开闭原则”。

缺点:

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
  • 继承关系自身的缺点,如果父类添加新的抽象方法,所有子类都要改一遍。

4、注意事项及细节

  • 模板方法模式的基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只需要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改。
  • 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
  • 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
  • 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大。
  • 一般模板方法都加上final关键字,防止子类重写模板方法。
  • 模板方法模式使用场景:当要完成某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理。

二、实例

1、炒菜案例

炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下:
在这里插入图片描述

// 抽象模板类
public abstract class AbstractClass {
	public final void cookProcess() {
		//第一步:倒油
		this.pourOil();
		//第二步:热油
		this.heatOil();
		//第三步:倒蔬菜
		this.pourVegetable();
		//第四步:倒调味料
		this.pourSauce();
		//第五步:翻炒
		this.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("炒啊炒啊炒到熟啊");
	}
}
// 炒包菜
public class ConcreteClass_BaoCai extends AbstractClass {
	@Override
	public void pourVegetable() {
		System.out.println("下锅的蔬菜是包菜");
	}
	@Override
	public void pourSauce() {
		System.out.println("下锅的酱料是辣椒");
	}
}
// 炒菜心
public class ConcreteClass_CaiXin extends AbstractClass {
	@Override
	public void pourVegetable() {
		System.out.println("下锅的蔬菜是菜心");
	}
	@Override
	public void pourSauce() {
		System.out.println("下锅的酱料是蒜蓉");
	}
}
// 测试类
public class Client {
	public static void main(String[] args) {
		//炒手撕包菜
		ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
		baoCai.cookProcess();
		//炒蒜蓉菜心
		ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();
		caiXin.cookProcess();
	}
}

注意:为防止恶意操作,一般模板方法都加上 final 关键词。

(1)模板方法模式的钩子方法

在模板方法模式的父类,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。

钩子方法的主要目的是用来干预执行流程,使得我们控制行为流程更加灵活,更符合实际业务的需求。钩子方法一般为适合条件分支语句的返回值(如boolean、int等),我们可以根据自己的业务场景来决定是否需要使用钩子方法。

我们还是以炒菜为例,在模板抽象类中加一步:焯水。并不是炒所有的菜都需要焯水的:

// 抽象模板类
public abstract class AbstractClass {
	public final void cookProcess() {
		if(this.needBlanching()) {
			// 按需要判断是否要焯水
			this.blanching();
		}
		//第一步:倒油
		this.pourOil();
		//第二步:热油
		this.heatOil();
		//第三步:倒蔬菜
		this.pourVegetable();
		//第四步:倒调味料
		this.pourSauce();
		//第五步:翻炒
		this.fry();
	}
	public boolean needBlanching() {
		return false;
	}
	public void blanching() {
		System.out.println("焯水");
	}
	// 第一步都是一样的,倒油
	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("炒啊炒啊炒到熟啊");
	}
}
// 炒包菜
public class ConcreteClass_BaoCai extends AbstractClass {
	@Override
	public boolean needBlanching() {
		System.out.println("下锅的蔬菜是包菜,需要焯水");
		return true;
	}
	@Override
	public void pourVegetable() {
		System.out.println("下锅的蔬菜是包菜");
	}
	@Override
	public void pourSauce() {
		System.out.println("下锅的酱料是辣椒");
	}
}
// 炒菜心
public class ConcreteClass_CaiXin extends AbstractClass {
	@Override
	public void pourVegetable() {
		System.out.println("下锅的蔬菜是菜心");
	}
	@Override
	public void pourSauce() {
		System.out.println("下锅的酱料是蒜蓉");
	}
}
// 测试类
public class Client {
	public static void main(String[] args) {
		//炒手撕包菜
		ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
		baoCai.cookProcess();
		//炒蒜蓉菜心
		ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();
		caiXin.cookProcess();
	}
}

2、重构JDBC案例

创建一个模板类JdbcTemplate,封装所有的JDBC操作。

public abstract class JdbcTemplate {
    private DataSource dataSource;

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

    public final List<?> executeQuery(String sql, 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);
            //5、关闭结果集
            rs.close();
            //6、关闭语句集
            pstm.close();
            //7、关闭连接
            conn.close();
            return result;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    private List<?> parseResultSet(ResultSet rs) throws Exception {
        List<Object> result = new ArrayList<Object>();
        int rowNum = 0;
        while (rs.next()){
            result.add(this.mapRow(rs,rowNum++));
        }
        return result;
    }
	// 抽象方法
    public abstract Object mapRow(ResultSet rs,int rowNum) throws Exception;


    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();
    }
}

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

    public Object 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;
    }

    public List<?> selectAll(){
        String sql = "select * from t_member";
        return super.executeQuery(sql ,null);
    }
}
public class Test {
    public static void main(String[] args) {
        MemberDao memberDao = new MemberDao(new DataSource());
        List<?> result = memberDao.selectAll();
    }
}

我们使用模板方法封装jdbc,将数据映射方法在子类重写,极大地提高了代码复用率。

但是,这种方案真的就是最优方案吗?

三、模板方法模式与Callback回调模式

模板模式常用在框架开发中,通过提供功能扩展点,让框架用户在不修改框架源码的情况下,基于扩展点定制化框架的功能。除此之外,模板模式还可以起到代码复用的作用。

复用和扩展是模板模式的两大作用,实际上,还有另外一个技术概念,也能起到跟模板模式相同的作用,那就是回调(Callback)

1、回调基本原理

相对于普通的函数调用来说,回调是一种双向调用关系。A 类事先注册某个函数 F 到 B 类,A 类在调用 B 类的 P 函数的时候,B 类反过来调用 A 类注册给它的 F 函数。这里的 F 函数就是“回调函数”。A 调用 B,B 反过来又调用 A,这种调用机制就叫作“回调”。

// A 类将回调函数传递给 B 类
public interface ICallback {
  void methodToCallback();
}
public class BClass {
  public void process(ICallback callback) {
    //...
    callback.methodToCallback();
    //...
  }
}
public class AClass {
  public static void main(String[] args) {
    BClass b = new BClass();
    b.process(new ICallback() { //回调对象
      @Override
      public void methodToCallback() {
        System.out.println("Call back me.");
      }
    });
  }
}

回调不仅可以应用在代码设计上,在更高层次的架构设计上也比较常用。比如,通过三方支付系统来实现支付功能,用户在发起支付请求之后,一般不会一直阻塞到支付结果返回,而是注册回调接口(类似回调函数,一般是一个回调用的 URL)给三方支付系统,等三方支付系统执行完成之后,将结果通过回调接口返回给用户。

回调可以分为同步回调和异步回调(或者延迟回调)。同步回调指在函数返回之前执行回调函数;异步回调指的是在函数返回之后执行回调函数。上面的代码实际上是同步回调的实现方式,在 process() 函数返回之前,执行完回调函数 methodToCallback()。而上面支付的例子是异步回调的实现方式,发起支付之后不需要等待回调接口被调用就直接返回。从应用场景上来看,同步回调看起来更像模板模式,异步回调看起来更像观察者模式。

2、案例一:回调方式重构JDBC

上面我们使用模板方法模式使用JDBC,我们在此基础上进一步使用组合、回调方式进行重构。

定义JdbcTemplate 工具类:

public 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();
    }
}

public class MemberDao {
    private JdbcTemplate jdbcTemplate = new JdbcTemplate(null);

    public List<?> selectAll(){
        String sql = "select * from t_member";
        return jdbcTemplate.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);
    }
}

我们发现,取消了原来的继承机制,使用组合+方法回调机制,似乎代码更灵活。

3、案例二:注册监听事件

在客户端开发中,我们经常给控件注册事件监听器,比如下面这段代码,就是在 Android 应用开发中,给 Button 控件的点击事件注册监听器。

Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    System.out.println("I am clicked.");
  }
});

从代码结构上来看,事件监听器很像回调,即传递一个包含回调函数(onClick())的对象给另一个函数。从应用场景上来看,它又很像观察者模式,即事先注册观察者(OnClickListener),当用户点击按钮的时候,发送点击事件给观察者,并且执行相应的 onClick() 函数。

4、模板方法模式 VS 回调

从应用场景上来看,同步回调跟模板模式几乎一致。它们都是在一个大的算法骨架中,自由替换其中的某个步骤,起到代码复用和扩展的目的。而异步回调跟模板模式有较大差别,更像是观察者模式。

从代码实现上来看,回调和模板模式完全不同。回调基于组合关系来实现,把一个对象传递给另一个对象,是一种对象之间的关系;模板模式基于继承关系来实现,子类重写父类的抽象方法,是一种类之间的关系。

组合优于继承。实际上,这里也不例外。在代码实现上,回调相对于模板模式会更加灵活,主要体现在下面几点:

  • 像 Java 这种只支持单继承的语言,基于模板模式编写的子类,已经继承了一个父类,不再具有继承的能力。
  • 回调可以使用匿名类来创建回调对象,可以不用事先定义类;而模板模式针对不同的实现都要定义不同的子类。
  • 如果某个类中定义了多个模板方法,每个方法都有对应的抽象方法,那即便我们只用到其中的一个模板方法,子类也必须实现所有的抽象方法。而回调就更加灵活,我们只需要往用到的模板方法中注入回调对象即可。

四、源码中的目标方法模式

1、InputStream类

InputStream类就使用了模板方法模式。在InputStream类中定义了多个 read() 方法,如下:

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;
	}
}

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

在InputStream父类中已经定义好了读取一个字节数组数据的方法是每次读取一个字节,并将其存储到数组的第一个索引位置,读取len个字节数据。具体如何读取一个字节数据,由子类实现。

2、AbstractList

我们看一下AbstractList的部分源码:

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

get方法是一个抽象方法,它的逻辑交由子类来进行实现,ArrayList就是AbstractList的子类。

同理,有AbstractList就有AbstractSet和AbstractMap,源码也用到了模板方法模式。

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

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

相关文章

Camtasia Studio2023最新版喀秋莎电脑录制屏幕编辑器

不管是在我们平日的工作当中&#xff0c;还是生活当中&#xff0c;camtasia studio可以方便地进行屏幕操作的录制和配音、视频的剪辑和过场动画、添加说明字幕和水印、制作视频封面和菜单、视频压缩和播放。 你都会因为一些事情&#xff0c;从而需要进行录屏的需求。而Camtasi…

超详细,unity如何制作人物行走的遥杆?

介绍 在游戏中&#xff0c;移动遥杆是一种常见的用户界面元素&#xff0c;它允许玩家通过触摸或鼠标输入来控制游戏对象的移动。移动遥杆通常由一个圆形或方形的背景和一个可以拖动的小球&#xff08;称为拇指杆&#xff09;组成。玩家可以通过拖动拇指杆来控制游戏对象的移动…

某IC交易网 js逆向解析学习【2023/05/16】

文章目录 文章目录 文章目录前言网址目标参数确认加密点cookie解密第一步hex1算法解析rind和rnns完结撒花前言 可以关注我哟,一起学习,主页有更多练习例子 如果哪个练习我没有写清楚,可以留言我会补充 如果有加密的网站可以留言发给我,一起学习共享学习路程 如侵权,联系我…

Vue.js表单输入绑定

对于Vue来说&#xff0c;使用v-bind并不能解决表单域对象双向绑定的需求。所谓双向绑定&#xff0c;就是无论是通过input还是通过Vue对象&#xff0c;都能修改绑定的数据对象的值。Vue提供了v-model进行双向绑定。本章将重点讲解表单域对象的双向绑定方法和技巧。 10.1 实现双…

单片机的介绍

目录 一、介绍 1.单片机简介 2.单片机型号 3.体系 二、硬件基础 1.引言 2.电路基础 电的类比 电流 电压 电路 3.电子元器件 电阻 电容 二极管 三极管 4.常见电气接口 传统音频 视频 电源 RJ45网口 DB9串口 5.开发板/最小系统板 三、STM32介绍 1.简介…

JAVA电商 B2B2C商城系统 多用户商城系统 直播带货 新零售商城 o2o商城 电子商务 拼团商城 分销商城

JAVA电商 B2B2C商城系统 多用户商城系统 直播带货 新零售商城 o2o商城 电子商务 拼团商城 分销商城 1. 鸿鹄Cloud架构清单 2. Commonservice&#xff08;通用服务&#xff09; 通用服务&#xff1a;对spring Cloud组件的使用&封装&#xff0c;是一套完整的针对于分布式微…

Android Studio中的布局讲解

文章目录 1.LinearLayout&#xff08;线性布局&#xff09;2.RelativeLayout&#xff08;相对布局&#xff09;相对于兄弟元素&#xff1a;相对于父元素对齐方式间隔 3.GridLayout&#xff08;网格布局&#xff09;设置最大列数设置最大行数指定控件的位置 4.FrameLayout&#…

包管理工具:pnpm | 京东云技术团队

作者&#xff1a;京东零售 杨秀竹 pnpm 是什么 pnpm&#xff08; performant npm &#xff09;指的是高性能的 npm&#xff0c;与 npm 和 yarn 一样是一款包管理工具&#xff0c;其根据自身独特的包管理方法解决了 npm、yarn 内部潜在的安全及性能问题&#xff0c;在多数情况…

耗子叔-我的互联网引路人

早上一早看到各大程序员群提到左耳朵耗子-陈皓&#xff0c;因为心梗辞世的信息&#xff0c;真的让人难以置信&#xff0c;因为据我所知他还不到50。 虽然我从来没见过他&#xff0c;交谈也很少&#xff0c;但是我知道他的情况&#xff0c;知道他的公司&#xff0c;知道他的好恶…

不要再问我加密的问题了,使用crypto-js中的AES加密方法,连续多次加密/解密,注意事项

每日鸡汤&#xff0c;每个你想要学习的念头&#xff0c;都是未来的你向自己求救 需求&#xff1a;有一段字符串text&#xff0c;有3个key&#xff0c;后端用这三个key一次加密&#xff1b;然后把加密后的字符串返回给前端&#xff0c;前端用这3个key依次解密&#xff0c;得到原…

剖析:在线帮助中心对企业能够起到什么作用?

随着互联网技术的不断发展和普及&#xff0c;越来越多的企业开始将自己的业务转移到了线上。这种转移不仅能够大幅度提高企业的效率&#xff0c;还能够让企业的服务更加贴近用户的需求。然而&#xff0c;在线服务也存在着一些问题&#xff0c;比如用户可能会遇到一些困难&#…

大人,时代变了!缺少成本票可不能买发票啊,是有办法的!

业务是流程&#xff0c;财税是结果&#xff0c;税收问题千千万&#xff0c;关注《税算盘》来帮你找答案。 企业所得税和增值税一样&#xff0c;都是我国重要的税收之一。企业所得税征收对象为企业的利润部分&#xff0c;再度细分就与企业的成本票有关。 企业所得税高是如今众…

电商系统分类树查询功能优化方案总结

前言 分类树查询功能&#xff0c;在各个业务系统中可以说随处可见&#xff0c;特别是在电商系统中。 但就是这样一个简单的分类树查询功能&#xff0c;我们却优化了5次。 到底是怎么回事呢&#xff1f; 背景 我们的网站使用了SpringBoot推荐的模板引擎&#xff1a;Thymelea…

案例5:Java大学生创新创业项目管理设计与实现任务书

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

RK3568|3588|3566处理器属于什么档次?

随着科技的迅猛发展&#xff0c;处理器作为计算机和电子设备的核心组件&#xff0c;其性能的提升对于设备的功能和用户体验起着至关重要的作用。在处理器市场中&#xff0c;不同的处理器被划分为不同的档次&#xff0c;以便用户能够更好地选择适合自己需求的产品。那么&#xf…

解决git clone与git push出现的若干问题:Failed to connect to github.com port 443: Timed out

1 连接github失败问题汇总&#xff1a;Failed to connect to github.com port 443: Timed out 1.1 解决ping不通github.com的问题 1.1.1 查询github的IP的地址 在以下链接找到网页显示github的ip地址http://github.global.ssl.fastly.net.ipaddress.com/&#xff0c;如图所示…

FSW26现金回收RS FSW43 信号和频谱分析仪

Rohde & Schwarz FSW26信号和频谱分析仪&#xff0c;2 Hz - 26.5 GHz 高性能 Rohde & Schwarz (R&S) FSW26 信号和频谱分析仪专为方便、准确和快速而设计。其独特的触摸屏、直观的多视图结果显示和优化的用户指南使 R&S FSW26 分析仪的操作高效方便。凭借其无…

玩转ChatGPT:AskYourPDF插件尝鲜

一、写在前面 首先&#xff0c;吐槽一下&#xff0c;感觉被CloseAI耍了&#xff1a; 上周发文说这一周对PLUS开放联网和插件功能&#xff0c;搞得网络一片狂欢。但是今天通过身边统计学发现&#xff0c;开通了PLUS后&#xff0c;拥有联网和插件功能的只是少数&#xff08;而且…

TCP连接不释放,应用产生大量CLOSE_WAIT状态TCP

一、起源 23年元旦期间&#xff0c;大家都沉浸在一片祥和的过节气氛当中。 “滴滴滴”&#xff0c;这头同事的电话响起&#xff0c;具体说些什么我也没太在意&#xff0c;但见同事接完电话之后展现出了一副懊恼夹杂着些许不耐烦的表情。 我不解问道&#xff1a;“怎么了&…

扇区(sector),块(block),簇(cluster)

1.硬盘(可以认为硬盘就是磁盘) # fdisk -l Disk /dev/cciss/c0d0: 146.7 GB, 146778685440 bytes 255 heads, 63 sectors/track, 17844 cylinders Units cylinders of 16065 * 512 8225280 bytes 可以看到几个名词&#xff1a;heads/sectors/cylinders&#xff0c;分别就是磁…