设计模式の单例工厂原型模式

文章目录

  • 前言
  • 一、单例模式
    • 1.1、饿汉式静态常量单例
    • 1.2、饿汉式静态代码块单例
    • 1.3、懒汉式单例(线程不安全)
    • 1.4、懒汉式单例(线程安全,同步代码块)
    • 1.5、懒汉式单例(线程不安全,同步代码块)
    • 1.6、懒汉式单例(线程安全,双检锁模式)
    • 1.7、静态内部类单例
    • 1.8、枚举单例
  • 二、工厂模式
    • 2.1、简单工厂模式
    • 2.2、工厂方法模式
    • 2.3、抽象工厂模式
    • 小结
  • 三、原型模式
    • 3.1、如何在原型模式中实现浅拷贝和深拷贝?
    • 小结


前言

  本篇是关于设计模式中单例模式(8种,包含线程安全,非安全的实现)、工厂模式(3种)、以及原型模式(深拷贝、浅拷贝)的笔记。


一、单例模式

  单例模式的核心目的是确保某个类只有一个实例,并且提供一个全局访问点来获取该实例。这种模式通常用于需要全局共享资源或者全局配置的场景,通常可用在日志管理器(只有一个日志实例用于输出日志)、线程池(避免重复创建)等全局资源只需要初始化一次的场景。
  如果需要实现单例模式,通常需要满足以下三大要素:

  1. 私有化构造函数:防止外部直接创建实例。
  2. 静态变量:保存唯一的实例。
  3. 公共的静态方法:提供对外的访问方式,返回该唯一实例。

1.1、饿汉式静态常量单例

  最常见的一种饿汉式单例,Singleton1的实例是在外部调用Singleton1getInstance方法时创建的,并且Java 的类加载懒加载的,也就是说只有当类第一次被引用时,才会加载这个类。在加载过程中,JVM 会保证静态变量的初始化是线程安全的。

JVM是如何保证静态变量初始化的线程安全?
类加载过程是串行的,每个类在被加载时会有一个单独的类加载过程,加载过程中的所有操作是线程安全的。JVM 会确保对类的初始化只有一个线程可以执行。其他线程会被阻塞,直到类初始化完成。
对于静态变量,JVM 在类的初始化过程中会执行双检模式,当线程第一次访问类时,如果该类尚未初始化,JVM 会进行初始化,并且只有一个线程会执行这个初始化过程。其他线程在初始化过程中会被阻塞,直到第一个线程完成初始化,类初始化过程保证只会被执行一次,避免了多个线程同时初始化实例的问题。
在案例中,是在同一个线程中获取了两次INSTANCE实例,为何都是同一个?
因为静态变量,是属于类而不是属于某个方法的,静态变量是类的所有实例共享的,当 Singleton1 类被加载并初始化时,JVM 会创建 INSTANCE 变量并赋值。这一过程只会发生一次。

public class HungryMan1 {public static void main(String[] args) {Singleton1 s1 = Singleton1.getInstance();Singleton1 s2 = Singleton1.getInstance();System.out.println(s1 == s2);}
}/*** 类加载时就创建单例对象* 由JVM保证线程安全性*/
class Singleton1{private Singleton1(){}private final static Singleton1 INSTANCE = new Singleton1();public static Singleton1 getInstance(){return INSTANCE;}}

1.2、饿汉式静态代码块单例

  相比较于第一种实现,区别在于本实现是在静态代码块中完成单例对象初始化的。当调用Singleton2.getInstance(),这触发 Singleton2 类的加载,类加载过程中,JVM 会初始化静态成员变量和静态代码块。

public class HungryMan2 {public static void main(String[] args) {Singleton2 s1 = Singleton2.getInstance();Singleton2 s2 = Singleton2.getInstance();System.out.println(s1 == s2);}
}class Singleton2{private Singleton2(){}private final static Singleton2 INSTANCE;/*** 在静态代码块中完成初始化*/static {INSTANCE = new Singleton2();}public static Singleton2 getInstance(){return INSTANCE;}
}

1.3、懒汉式单例(线程不安全)

  该种单例的设计思想是,在调用getInstance()时才会主动去创建单例实例。但是下面的实现是存在线程安全问题的,如果两个线程同时到达了if块,都判断为空,就会创建两个不同的实例。

public class LazyMan1 {public static void main(String[] args) throws InterruptedException {Singletion3 s1 = Singletion3.getInstance();Singletion3 s2 = Singletion3.getInstance();System.out.println(s1 == s2);}
}class Singletion3{private Singletion3(){}private static Singletion3 instance;/*** 多线程下存在并发问题* @return*/public static Singletion3 getInstance(){if (instance == null){instance = new Singletion3();}return instance;}
}

1.4、懒汉式单例(线程安全,同步代码块)

public class LazyMan2 {public static void main(String[] args) {Singletion4 s1 = Singletion4.getInstance();Singletion4 s2 = Singletion4.getInstance();System.out.println(s1 == s2);}
}class Singletion4{private Singletion4(){}private static Singletion4 instance;/*** 解决线程安全问题,但是synchronized是重量级锁,效率低* @return*/public static synchronized Singletion4 getInstance(){if (instance == null){instance = new Singletion4();}return instance;}
}

1.5、懒汉式单例(线程不安全,同步代码块)

  1.4的案例,使用重量级锁保证线程安全,弊端在于锁的粒度过大。如果缩小锁的范围?本案例的写法依旧会存在线程安全问题:

public class LazyMan3 {public static void main(String[] args) {Singletion5 s1 = Singletion5.getInstance();Singletion5 s2 = Singletion5.getInstance();System.out.println(s1 == s2);}
}class Singletion5 {private Singletion5() {}private static Singletion5 instance;/*** 降低锁的粒度,依旧会存在线程安全问题* 比如AB两个线程在IF处判断,都为空,都进入了IF块* 虽然只有一个线程能争抢到锁,但是在释放锁之后,另一个线程也能再次进入同步代码块创建一个新的对象* @return*/public static Singletion5 getInstance() {if (instance == null) {synchronized (Singletion5.class) {instance = new Singletion5();}}return instance;}
}

1.6、懒汉式单例(线程安全,双检锁模式)

JUC并发编程,java内存模型,volatile关键字,双检锁单例

public class LazyMan4 {public static void main(String[] args) {}
}class Singletion6 {private Singletion6() {}/*** 这里的volatile 一定要加 第一是避免指令重排序问题,第二是将对于instance的更改立刻同步到主存,防止缓存问题=-**/private static volatile Singletion6 instance;/*** 降低锁的粒度,并且进行双重检查* @return*/public static Singletion6 getInstance() {if (instance == null) {synchronized (Singletion6.class) {if (instance == null) {instance = new Singletion6();}}}return instance;}
}

1.7、静态内部类单例

  保证线程安全的方式,和饿汉式的类似。

public class StaticInner {public static void main(String[] args) {Singleton7 s1 = Singleton7.getInstance();Singleton7 s2 = Singleton7.getInstance();System.out.println(s1 == s2);}
}class Singleton7{private Singleton7(){}/*** 使用静态内部类的方式,静态内部类会在其中方法/变量被调用时初始化*/public static class inner{private static final Singleton7 INSTANCE = new Singleton7();}/*** 外部调用Singleton7的getInstance静态方法时,初始化inner内部类* JVM在加载类时是线程安全的,通过静态内部类只加载一次,保证只创建一次外部类的实例* @return*/public static Singleton7 getInstance(){return inner.INSTANCE;}}

1.8、枚举单例

  最后一种是枚举单例,也是推荐使用的一种方式。上面所有的单例,即使是线程安全的,也会有可能因为使用反射而被破坏。

枚举单例为什么能保证线程安全?
在类加载时,JVM 会创建枚举实例并将其保存在内存中。在整个应用生命周期内,枚举的实例是唯一的,JVM 会在类加载时保证它的线程安全和单例性。
当 Singleton.INSTANCE 被访问时,枚举实例已经由 JVM 在类加载时创建好并且只会创建一次。

public class EnumSingleton {public static void main(String[] args) {Singleton8 s1 = Singleton8.SINGLETON;Singleton8 s2 = Singleton8.SINGLETON;System.out.println(s1 == s2);}
}/*** 枚举单例 没有线程安全问题,也不会导致通过暴力反射破坏单例* 前面的方式,虽然将构造私有化,但是都是可以通过反射破解的*/
enum Singleton8{SINGLETON;}

二、工厂模式

  工厂模式的核心思想在于,将对象的实例化过程封装起来,使得代码不需要直接调用构造方法来创建对象,而是通过一个工厂方法来获取实例客户端代码不需要关心如何创建对象,只需要关心如何使用对象。(七大原则中的迪米特原则,依赖倒置原则)即,将对象的创建过程和使用过程分离,客户端可以获取到对象,而不需要知道对象的具体创建细节。

2.1、简单工厂模式

  假设现在要模拟一个制作披萨的过程,披萨的种类有CheessGreek两种,制作的过程有prepare准备bake烘烤cut切割box打包。由于不同的披萨准备原材料的方式是不一样的,可以这样设计:

public abstract class Pizza {private String name;public void setName(String name) {this.name = name;}/*** 每种披萨的准备过程是不一样的,留给子类去实现*/public void prepare() {}public void bake() {System.out.println("烘烤 " + name);}public void cut() {System.out.println("切割 " + name);}public void box() {System.out.println("打包 " + name);}}
public class GreekPizza extends Pizza {@Overridepublic void prepare() {System.out.println("制作希腊披萨 准备材料");}
}
public class CheesePizza extends Pizza {@Overridepublic void prepare() {System.out.println("制作奶酪披萨 准备材料");}
}

  再用一个类模拟订购披萨的过程:

public class OrderPizza {public OrderPizza() {Pizza pizza = null;Scanner sc = new Scanner(System.in);while (true){System.out.println("***********请输入需要制作的pizza***********");String next = sc.next();//调用工厂的方法,创建具体的实例PizzaFactory pizzaFactory = new PizzaFactory();pizza = pizzaFactory.createPizza(next);if (pizza == null){break;}pizza.bake();pizza.cut();pizza.box();}sc.close();}
}

  将制作具体披萨的代码放置到了工厂类中,这样有什么好处?如果有多个订购披萨的类,并且我现在要加一个披萨的种类,如果制作具体披萨的代码还是放置在每个订购披萨的类中,那么所有的类都需要进行修改,扩展性很差。

/*** 创建Pizza的工厂类*/
public class PizzaFactory {public Pizza createPizza(String type) {Pizza pizza;if (type.equals("Cheese")){pizza = new CheesePizza();pizza.setName("奶酪披萨");}else if (type.equals("Greek")){pizza = new GreekPizza();pizza.setName("希腊披萨");}else {return null;}return pizza;}
}
/*** Pizza店*/
public class PizzaStore {public static void main(String[] args) {new OrderPizza();}
}

2.2、工厂方法模式

  工厂方法模式是对简单工厂模式的一种改进。假设需求发生变更,除了披萨有不同的种类,还有不同地区供应披萨,比如北京的Cheess披萨,伦敦的Greek披萨…等,简单工厂模式不适合这种复杂的需求,我们可以用工厂方法模式将一个大的工厂,拆分成多个子工厂实现:
在这里插入图片描述

public abstract class Pizza {private String name;public void setName(String name) {this.name = name;}/*** 每种披萨的准备过程是不一样的,留给子类去实现*/public void prepare() {}public void bake() {System.out.println("烘烤 " + name);}public void cut() {System.out.println("切割 " + name);}public void box() {System.out.println("打包 " + name);}}class LDGreekPizza extends Pizza {@Overridepublic void prepare() {System.out.println("制作伦敦希腊披萨 准备材料");}
}class LDCheesePizza extends Pizza{@Overridepublic void prepare() {System.out.println("制作伦敦奶酪披萨 准备材料");}
}class BJGreekPizza extends Pizza {@Overridepublic void prepare() {System.out.println("制作北京希腊披萨 准备材料");}
}class BJCheesePizza extends Pizza {@Overridepublic void prepare() {System.out.println("制作北京奶酪披萨 准备材料");}
}

  对工厂进行拆分:

/*** 订购pizza*/
public abstract class OrderPizza {/*** 该方法留给具体的 伦敦 北京 披萨的类去实现,做自己类型的pizza* @param type* @return*/public abstract Pizza createPizza(String type);public OrderPizza() {Pizza pizza = null;Scanner sc = new Scanner(System.in);while (true){System.out.println("***********请输入需要制作的pizza***********");String next = sc.next();//制作pizzapizza = createPizza(next);if (pizza == null){break;}pizza.bake();pizza.cut();pizza.box();}sc.close();}
}class LDOrderPizza extends OrderPizza{public LDOrderPizza() {super();}@Overridepublic Pizza createPizza(String type) {Pizza pizza;if (type.equals("Cheese")){pizza = new CheesePizza();pizza.setName("伦敦奶酪披萨");}else if (type.equals("Greek")){pizza = new GreekPizza();pizza.setName("伦敦希腊披萨");}else {return null;}return pizza;}
}class BJOrderPizza extends OrderPizza {public BJOrderPizza() {super();}@Overridepublic Pizza createPizza(String type) {Pizza pizza;if (type.equals("Cheese")){pizza = new CheesePizza();pizza.setName("北京奶酪披萨");}else if (type.equals("Greek")){pizza = new GreekPizza();pizza.setName("北京希腊披萨");}else {return null;}return pizza;}
}

  在模拟订购披萨时,只需要创建具体实现类的对象即可:

/*** Pizza店*/
public class PizzaStore {public static void main(String[] args) {//创建具体的实现类new BJOrderPizza();}
}

2.3、抽象工厂模式

  抽象工厂模式工厂方法模式的进一步扩展,将工厂抽象成两层,接口层:抽象工厂,实现类:具体负责生产各自产品的工厂:
在这里插入图片描述

/*** 抽象工厂模式* 侧重于将工厂分为了多层*/
public interface AbsFactory {Pizza createPizza(String type);}class BJPizzaFactory implements AbsFactory {@Overridepublic Pizza createPizza(String type) {Pizza pizza;if (type.equals("Cheese")){pizza = new CheesePizza();pizza.setName("北京奶酪披萨");}else if (type.equals("Greek")){pizza = new GreekPizza();pizza.setName("北京希腊披萨");}else {return null;}return pizza;}
}class LDPizzaFactory implements AbsFactory {@Overridepublic Pizza createPizza(String type) {Pizza pizza;if (type.equals("Cheese")){pizza = new CheesePizza();pizza.setName("伦敦奶酪披萨");}else if (type.equals("Greek")){pizza = new GreekPizza();pizza.setName("伦敦希腊披萨");}else {return null;}return pizza;}
}

  只需要实例化对应的工厂类实现,即可获得相应的对象。


/*** Pizza店*/
public class PizzaStore {public static void main(String[] args) {new OrderPizza(new BJPizzaFactory());}
}class OrderPizza {public OrderPizza(AbsFactory factory) {setFactory(factory);}private void setFactory(AbsFactory factory) {Pizza pizza = null;Scanner sc = new Scanner(System.in);while (true){System.out.println("***********请输入需要制作的pizza***********");String type = sc.next();//制作pizza 由传入的AbsFactory子类类型 去路由到不同的子类pizza = factory.createPizza(type);if (pizza == null){break;}pizza.bake();pizza.cut();pizza.box();}sc.close();}
}

小结

  简单工厂模式可以理解成仅仅是把具有共性的创建对象的代码抽取到了一个类中,无法应对复杂的业务。而工厂方法模式是对简单工厂模式的一种改进,将产品的创建过程委托给子类来解决简单工厂模式的缺点。会将子类进行分类。每个子类负责创建一个特定类型的产品,客户端只需要通过工厂方法来获取对象。抽象工厂模式可以看做是简单工厂模式工厂方法模式的结合,既包含了抽象工厂和实现工厂,也包含了抽象产品和实现产品。

  • 简单工厂模式适合产品种类较少的情况。
  • 工厂方法模式适合产品种类较多且需要通过继承进行扩展的情况。
  • 抽象工厂模式适合需要创建一系列相关产品的情况。

三、原型模式

  原型模式的核心思想在于,通过复制现有的对象来创建新对象,而不是通过使用构造函数直接创建。这种模式通过克隆现有对象来生成新实例,从而避免了重复创建对象的复杂性和性能开销,特别适合在需要频繁创建类似对象的场景中。
  原型模式的结构通常包括以下几个角色:

  • Prototype(原型接口):定义了一个抽象的克隆方法,通常是 clone() 方法,供具体类实现。
public interface Prototype {Prototype clone();  // 克隆方法
}
  • ConcretePrototype(具体原型类):实现了原型接口的具体类,具体定义克隆方法,返回一个自己对象的副本。
public class ConcretePrototype implements Prototype {private String name;public ConcretePrototype(String name) {this.name = name;}public String getName() {return name;}@Overridepublic Prototype clone() {// 通过构造函数创建新的对象return new ConcretePrototype(this.name);}
}
  • Client(客户端):使用原型对象并通过克隆方法来创建新的对象。
public class Client {public static void main(String[] args) {// 创建一个原型对象ConcretePrototype prototype = new ConcretePrototype("Prototype1");// 通过克隆方法创建一个新对象ConcretePrototype clonePrototype = (ConcretePrototype) prototype.clone();// 输出原型对象的名称System.out.println("Original: " + prototype.getName());System.out.println("Clone: " + clonePrototype.getName());System.out.println(prototype == clonePrototype);}
}

  同时JDK自带的clone方法也是原型模式的体现,默认是浅拷贝,需要实现Cloneable接口。

什么是浅拷贝和深拷贝?
浅拷贝对于基本数据类型,拷贝的是值。对于引用类型,拷贝的是引用(即地址),源对象和目标对象的引用类型成员指向同一个内存位置。如果源对象或目标对象对引用类型的成员进行修改,另一对象的对应成员也会发生变化。
深拷贝对于基本数据类型,拷贝的是值。对于引用类型,会递归地拷贝引用类型所指向的对象。源对象和目标对象不会共享任何引用类型成员,修改一个对象的引用类型成员不会影响另一个对象。也是不可变类设计的一个要素。
JUC并发编程,不可变类设计

3.1、如何在原型模式中实现浅拷贝和深拷贝?

  浅拷贝通常通过调用 clone() 方法来实现:

public class ConcretePrototype implements Cloneable {private String name;private Address address;public ConcretePrototype(String name, Address address) {this.name = name;this.address = address;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Address getAddress() {return address;}public void setAddress(Address address) {this.address = address;}@Overridepublic ConcretePrototype clone() {try {return (ConcretePrototype) super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return null;}@Overridepublic String toString() {return "ConcretePrototype{" +"name='" + name + '\'' +", address=" + address +'}';}
}class Address {private String street;public Address() {}public Address(String street) {this.street = street;}/*** 获取* @return street*/public String getStreet() {return street;}/*** 设置* @param street*/public void setStreet(String street) {this.street = street;}public String toString() {return "Address{street = " + street + "}";}
}
public class ShallowCopy {public static void main(String[] args) {Address address = new Address();address.setStreet("xx街道");ConcretePrototype original = new ConcretePrototype("测试", address);ConcretePrototype copy = original.clone();System.out.println("原件=" + original.toString());System.out.println("复印件=" + copy.toString());System.out.println("修改复印件的Address中的Street字段==================");copy.getAddress().setStreet("yy街道");System.out.println("原件=" + original.toString());System.out.println("复印件=" + copy.toString());}
}

最终的结果是,复印件引用的Address中的字段值发生改变,原件的同步更新
原件=ConcretePrototype{name=‘测试’, address=Address{street = xx街道}}
复印件=ConcretePrototype{name=‘测试’, address=Address{street = xx街道}}
修改复印件的Address中的Street字段==================
原件=ConcretePrototype{name=‘测试’, address=Address{street = yy街道}}
复印件=ConcretePrototype{name=‘测试’, address=Address{street = yy街道}}

  深拷贝的区别在于,在调用clone()方法进行浅克隆后,还需要对其中的引用类型创建新的实例,赋值给复制品的引用类型字段,这样原件和复印件的引用类型字段的引用,指向的地址就不相同:

public class ConcretePrototype implements Cloneable {//......@Overridepublic ConcretePrototype clone() {try {// 深拷贝:创建新的 Address 对象ConcretePrototype clone = (ConcretePrototype) super.clone();// 手动创建新的引用,使原件和复印件的地址不相同clone.address = new Address(this.address.getStreet());return clone;} catch (CloneNotSupportedException e) {e.printStackTrace();}return null;}@Overridepublic String toString() {return "ConcretePrototype{" +"name='" + name + '\'' +", address=" + address +'}';}
}

原件=ConcretePrototype{name=‘测试’, address=Address{street = xx街道}}
复印件=ConcretePrototype{name=‘测试’, address=Address{street = xx街道}}
修改复印件的Address中的Street字段==================
原件=ConcretePrototype{name=‘测试’, address=Address{street = xx街道}}
复印件=ConcretePrototype{name=‘测试’, address=Address{street = yy街道}}

小结

  原型模式的适用场景:

  • 创建对象的代价较大:当对象的创建成本高(如涉及到数据库操作、网络调用等),而且创建出来的对象大部分都相似时,原型模式可以通过复制现有对象来降低性能开销。(和单例模式的区别在于,因为业务需求,该对象必须要创建多份)。
  • 需要大量相似对象:如果需要创建很多类似的对象,通过克隆现有对象可以避免反复执行相同的构造逻辑。
  • 避免重复代码:当多个对象具有相似的构建过程时,原型模式能够避免重复编写对象创建的代码。

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

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

相关文章

net.sf.jsqlparser.statement.select.SelectItem

今天一启动项目,出现了这个错误,仔细想了想,应该是昨天合并代码,导致的mybatis-plus版本冲突,以及分页PageHelper版本不兼容 可以看见这个我是最下边的Caused by 报错信息,这个地方提示我 net .sf.jsqlpar…

第427场周赛: 转换数组、用点构造面积最大的矩形 Ⅰ、长度可被 K 整除的子数组的最大元素和、用点构造面积最大的矩形 Ⅱ

Q1、转换数组 1、题目描述 给你一个整数数组 nums&#xff0c;它表示一个循环数组。请你遵循以下规则创建一个大小 相同 的新数组 result &#xff1a; 对于每个下标 i&#xff08;其中 0 < i < nums.length&#xff09;&#xff0c;独立执行以下操作&#xff1a; 如…

CV工程师专用键盘开源项目硬件分析

1、前言 作为一个电子发烧友&#xff0c;你是否有遇到过这样的问题呢。当我们去查看函数定义的时候&#xff0c;需要敲击鼠标右键之后选择go to definition。更高级一些&#xff0c;我们使用键盘的快捷键来查看定义&#xff0c;这时候可以想象一下&#xff0c;你左手按下ALT&a…

SpringBoot3配置文件

一、统一配置管理概述: SpringBoot工程下&#xff0c;进行统一的配置管理&#xff0c;你想设置的任何参数(端口号、项目根路径、数据库连接信息等等)都集中到一个固定位置和命名的配置文件(application.properties或application.yml)中 配置文件应该放置在Spring Boot工程的s…

【机器学习】任务十一:Keras 模块的使用

1.Keras简介 1.1 什么是Keras&#xff1f; Keras 是一个开源的深度学习框架&#xff0c;用 Python 编写&#xff0c;构建于 TensorFlow 之上。它以简单、快速和易于使用为主要设计目标&#xff0c;适合初学者和研究者。 Keras 提供了高层次的 API&#xff0c;帮助用户快速构…

【新品发布】ESP32-P4开发板 —— 启明智显匠心之作,为物联网及HMI产品注入强劲动力

核心亮点&#xff1a; ESP32-P4开发板&#xff0c;是启明智显精心打造的一款高性能物联网开发板。它专为物联网项目及HMI&#xff08;人机界面&#xff09;产品而设计&#xff0c;旨在为您提供卓越的性能和稳定可靠的运行体验。 强大硬件配置&#xff1a; 双核400MHz RISC-V处…

在Ubuntu22.04.5上安装Docker-CE

文章目录 1. 查看Ubuntu版本2. 安装Docker-CE2.1 安装必要的系统工具2.2 信任Docker的GPG公钥2.3 写入软件源信息2.4 安装Docker相关组件2.5 安装指定版本Docker-CE2.5.1 查找Docker-CE的版本2.5.2 安装指定版本Docker-CE 3. 启动与使用Docker3.1 启动Docker服务3.2 查看Docker…

SSM01-MyBatis框架(一文学会MyBatis)

Mybatis框架 一、Mybatis框架简介 1.1 传统JDBC的缺陷 &#xff08;1&#xff09;数据库连接创建、释放频繁会造成系统资源浪费 【MyBatis通过在核心配置文件中配置数据路连接池解决此问题】 &#xff08;2&#xff09; SQL语句在代码中硬编码(PreparedStatement向占位符传…

uniapp中导入uview或者uview plus

关于SCSS uview-plus依赖SCSS&#xff0c;您必须要安装此插件&#xff0c;否则无法正常运行。 如果您的项目是由HBuilder X创建的&#xff0c;相信已经安装scss插件&#xff0c;如果没有&#xff0c;请在HX菜单的 工具->插件安装中找到"scss/sass编译"插件进行安…

深度解析 Ansible:核心组件、配置、Playbook 全流程与 YAML 奥秘(上)

文章目录 一、ansible的主要组成部分二、安装三、相关文件四、ansible配置文件五、ansible 系列 一、ansible的主要组成部分 ansible playbook&#xff1a;任务剧本&#xff08;任务集&#xff09;&#xff0c;编排定义ansible任务集的配置文件&#xff0c;由ansible顺序依次执…

【CC2530开发基础篇】光敏和热敏传感器

一、前言 1.1 开发背景 本实验通过CC2530单片机接入光敏传感器和热敏传感器&#xff0c;进行数据采集与检测&#xff0c;并将检测结果通过串口终端输出。光敏传感器和热敏传感器是常见的环境感知设备&#xff0c;分别用于测量光强和温度。在实际应用中&#xff0c;这些传感器…

第6章:布局 --[CSS零基础入门]

CSS 布局是网页设计中至关重要的一个方面&#xff0c;它决定了页面上元素的排列和展示方式。以下是几种常见的 CSS 布局方法和技术&#xff1a; 1. 浮动布局&#xff08;Float Layout&#xff09; 浮动布局&#xff08;Float Layout&#xff09;曾经是网页设计中创建多列布局…

设计模式:20、状态模式(状态对象)

目录 0、定义 1、状态模式的三种角色 2、状态模式的UML类图 3、示例代码 0、定义 允许一个对象在其内部状态改变时改变它的行为&#xff0c;对象看起来似乎修改了它的类。 1、状态模式的三种角色 环境&#xff08;Context&#xff09;&#xff1a;环境是一个类&#xff0…

【论文笔记】VisionZip: Longer is Better but Not Necessary in Vision Language Models

&#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平。 基本信息 标题: VisionZip: Longer is Bet…

flex: 1 display:flex 导致的宽度失效问题

flex: 1 & display:flex 导致的宽度失效问题 问题复现 有这样的一个业务场景&#xff0c;详情项每行三项分别占33%宽度&#xff0c;每项有label字数不固定所以宽度不固定&#xff0c;还有content 占满标签剩余宽度&#xff0c;文字过多显示省略号&#xff0c; 鼠标划入展示…

visual studio2019开发过程中遇到的问题和有帮助的插件

文章目录 1. 注释中有中文导致报错2. 打开一个vs2013或者vs2010等老的项目兼容性3. LNK2019 unresolved external symbol main referenced in function __tmainCRTStartup4. image watch插件/扩展使用 1. 注释中有中文导致报错 C4819 The file contains a character that cann…

存内架构IR-DROP问题详解-电容电导补偿

一、总述 电容、电导补偿作为大规模数字电路的关键设计理念&#xff0c;是 CIM 架构优化的核心技术。在 CIM 中&#xff0c;平衡电容或电导并实现计算的精准映射&#xff0c;对能效提升和计算精度保障具有关键作用。本文基于近期文献探讨电容、电导补偿在 CIM 中的具体补偿策…

HDR视频技术之六:色调映射

图像显示技术的最终目的就是使得显示的图像效果尽量接近人们在自然界中观察到的对应的场景。 HDR 图像与视频有着更高的亮度、更深的位深、更广的色域&#xff0c;因此它无法在常见的普通显示器上显示。 入门级的显示器与播放设备&#xff08;例如普通人家使用的电视&#xff0…

隐式神经网络实现低光照图像增强

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

深入浅出:SOME/IP-SD的工作原理与应用

目录 往期推荐 相关缩略语 SOME/IP 协议概述 协议介绍 SOME/IP TP 模块概述和 BSW 模块依赖性 原始 SOME/IP 消息的Header格式 SOME/IP-SD 模块概述 模块介绍 BSW modules依赖 客户端-服务器通信示例 Message 结构 用于SD服务的BSWM状态处理 往期推荐 ETAS工具…