【22-54】创建者模式(详解五大模式)

news2024/11/25 12:29:42

目录

一.创建者模式介绍

二.单例设计模式

2.1 单例模式的结构

2.2 单例模式的实现

2.2.1.1 饿汉式-方式1(静态变量方式)

2.2.1.2 饿汉式-方式2(静态代码块方式)

2.2.2.1 懒汉式-方式1(线程不安全)

2.2.2.2 懒汉式-方式2(线程安全)

2.2.2.3 懒汉式-方式3(双重检察锁)

2.2.2.4 懒汉式-方式4(静态内部类方式)

2.2.3 枚举方式

2.3 存在的问题

2.3.1 问题演示

2.3.2 问题的解决

三.工厂模式

3.1 概述

3.1 简单工厂模式

3.1.1 结构

3.1.2 实现

3.1.3  优缺点

3.1.4 扩展

3.2 工厂方法模式

3.2.1 概念

3.2.2 结构

3.2.3 实现

3.2.4 优缺点

3.3 抽象工厂模式

3.3.1 概述

3.3.2 结构

3.3.3 实现

3.3.4 优缺点

3.3.5 使用场景

3.4 JDK源码解析-Collection.iterator方法

四.原型模式

4.1 概述

4.2 结构

4.3 实现

4.4 案例

4.6 扩展(深克隆)

五.建造者模式

5.1 概述

5.2 结构

5.3 实例

5.4 优缺点

5.5 使用场景

5.6 模式扩展

六.创建者模式对比

6.1 工厂方法模式VS建造者模式

6.2 抽象工厂模式VS建造者模式


一.创建者模式介绍

创建者模式的主要关注点时“怎样创建对象?”,它的主要特点时“将对象的创建与使用分离”。

这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。

创建者模式可以分为:

  • 单例模式
  • 工厂方法模式
  • 抽象工程模式
  • 原型模式
  • 建造者模式

二.单例设计模式

单例模式时Java中最简单的设计模式之一。这中类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

2.1 单例模式的结构

单例模式的主要有以下角色:

  • 单例类:只能创建一个实例的类
  • 访问类:使用单例类

2.2 单例模式的实现

单例设计模式分类两种:

  • 饿汉式:类加载就会导致该实例对象被创建。
  • 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

2.2.1.1 饿汉式-方式1(静态变量方式)

Singleton类

package part3.singleton.demo1;

// 饿汉式,静态成员变量
public class Singleton {

    //1.私有构造方法,外界就无法方位,就无法创建对象
    private Singleton(){}

    //2.在本类中创建本类对象
    private static Singleton instance = new Singleton();

    //3.t提供一个公有的访问方式,让外界获取该对象.  static:外界无法创建Singleton对象,所以应该无法调用静态方法。
    public static Singleton getInstance(){
        return instance;
    }
}

Client类

package part3.singleton.demo1;

public class Client {

    public static void main(String[] args) {

        //创建Singleton类的对象
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();

        //判断获取到的两个是否是同个对象
        System.out.println(instance1 == instance2);

    }

}

说明:该方式在成员变量声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

2.2.1.2 饿汉式-方式2(静态代码块方式)

Singleton类

package part3.singleton.demo2;

// 饿汉式-方式2(静态代码块)
public class Singleton {

    //1.私有构造方法
    private Singleton(){}

    //2.声明Singleton类型的变量
    private static Singleton instance; // 初始值为null

    //3.在静态代码块中进行赋值
    static {
        instance = new Singleton();
    }

    //4.对外界提供获取该类对象的方法
    public static Singleton getInstance(){
        return instance;
    }
}

Client类

package part3.singleton.demo2;

public class Client {

    public static void main(String[] args) {
        //获取Singleton类的对象
        Singleton instance1=Singleton.getInstance();
        Singleton instance2=Singleton.getInstance();

        //判断两次获取的类对象是否是同一个
        System.out.println(instance1==instance2);
    }
}

说明:该方式在成员变量声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是随着类的加载而创建。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

2.2.2.1 懒汉式-方式1(线程不安全)

Singleton类

package part3.singleton.demo3;

//懒汉式 方式1 线程不安全
public class Singleton {

    //私有构造方法
    private Singleton(){}

    //声明Singleton类型变量instance
    private static Singleton instance; //只是声明一个变量,并没有创建对象

    //对外界提供访问方式
    public static Singleton getInstance(){ // 首次使用无非就是在这个访问函数
        //判断instance是否为null,如果为null,说明还没有创建Singleton对象
        //如果没有,创建一个并返回,如果有,直接返回
        if(instance == null){
            //线程1等待,线程2获取到cpu的执行全,也会进入到该判断里面

            instance = new Singleton();
        }
        return instance;
    }
}

Client类

package part3.singleton.demo3;

import part3.singleton.demo2.Singleton;

public class Client {
    public static void main(String[] args) {
        //获取Singleton类对象
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();

        //判断获取的两个对象是否相同
        System.out.println(instance1 == instance2);
    }
}

说明:从上面代码我们可以看出该方式在成员变量位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候 赋值的呢?当调用getinstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果但是,如果是多线程环境,会出现线程安全问题。

2.2.2.2 懒汉式-方式2(线程安全)

Singleton类

package part3.singleton.demo4;

//懒汉式 方式1 线程不安全
public class Singleton {

    //私有构造方法
    private Singleton(){}

    //声明Singleton类型变量instance
    private static Singleton instance; //只是声明一个变量,并没有创建对象

    //对外界提供访问方式
    public static synchronized Singleton getInstance(){ // 首次使用无非就是在这个访问函数
        //判断instance是否为null,如果为null,说明还没有创建Singleton对象
        //如果没有,创建一个并返回,如果有,直接返回
        if(instance == null){
            //线程1等待,线程2获取到cpu的执行全,也会进入到该判断里面

            instance = new Singleton();
        }
        return instance;
    }
}

【加锁】在getInstance函数前面加了关键字synchronized(同步) => 这样就不允许同一时间多线程使用这个函数,就不会出现前面的线程不安全问题。

2.2.2.3 懒汉式-方式3(双重检察锁)

再来讨论以下懒汉模式中加锁的问题,对于getInstance方法来说,绝大部分的操作都是读操作,读操作时线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生一种新的实现模式:双重检查锁模式。

package part3.singleton.demo5;

//双重检查锁方式
public class Singleton {

    //私有构造方法
    private Singleton(){}

    //声明Singleton类型变量
    private static Singleton instance;

    //对外界提供公共的访问方式
    public static Singleton getInstance(){
        //第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象
        if(instance == null){
            synchronized (Singleton.class){
                //第二次判断
                if(instance==null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

双重检查锁是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检查锁模式看上去完美无缺,其实是存在问题的,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指针重排序操作。

要解决双重检查锁模式带来空指针异常问题,只需要使用volatile关键字,volatile关键字可以保证可见性和有序性。

package part3.singleton.demo5;

//双重检查锁方式
public class Singleton {

    //私有构造方法
    private Singleton(){}

    //声明Singleton类型变量
    private static volatile Singleton instance;

    //对外界提供公共的访问方式
    public static Singleton getInstance(){
        //第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象
        if(instance == null){
            synchronized (Singleton.class){
                //第二次判断
                if(instance==null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

添加volatile关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。

2.2.2.4 懒汉式-方式4(静态内部类方式)

静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序。

//静态内部类方式
public class Singleton {

    //私有构造方法
    private Singleton(){};

    //定义一个静态内部类
    private static class SingletonHolder{
        //在内部类中声明并初始化外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }

    //提供一个公共的访问方式
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }

}
public class Client {

    public static void main(String[] args) {

        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();

        System.out.println(instance1 == instance2);
    }
}

说明:第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅确保线程安全,也能保证Singleton类的唯一性。

小结:静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有任何锁的情况下,保证了多线程下的安全,并且灭有任何性能影响和空间浪费。

2.2.3 枚举方式

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会被装载一次,设计则会充分利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

// 枚举实现方式
public enum Singleton {
    INSTANCE;
}
public class Client {
    public static void main(String[] args) {

        Singleton instance1 = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;

        System.out.println(instance1 == instance2);
    }
}

如果不考虑内存的问题,枚举就是一种很棒的单例实现方式。 

2.3 存在的问题

2.3.1 问题演示

破坏单例模式:
使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射。

  • 序列化反序列化

Singleton类

public class Singleton implements Serializable {

    //私有构造方法
    private Singleton(){};

    //定义一个静态内部类
    private static class SingletonHolder{
        //在内部类中声明并初始化外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }

    //提供一个公共的访问方式
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }

}

Client测试类

//测试使用反射破坏单例模式
public class Client {

    public static void main(String[] args) throws Exception {
        //writeObject2File();
        readObjectFromFile();
        readObjectFromFile();
    }
    //从文件读取数据(对象)
    public static void readObjectFromFile() throws Exception {
        //1.创建对象输入流对象
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("E:\\新建文件夹"));
        //2.读取对象
        Singleton instance=(Singleton)ois.readObject();
        System.out.println(instance);
        //3.释放方法
        ois.close();
    }

    //向文件中写数据(对象)
    public static void writeObject2File() throws Exception{
        //1.获取Singleton对象
        Singleton instance=Singleton.getInstance();
        //2.创建对象输出流对象
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("E:\\新建文件夹"));
        //3.写对象
        oos.writeObject(instance);
        //4.释放资源
        oos.close();
    }

}

上面代码运行结果是false,表型序列化和反序列化已经破坏了单例设计模式。

  • 反射

Singleton类

public class Singleton {

    //私有构造方法
    private Singleton(){}

    //定义一个·静态内部类
    private static class SingletonHolder{
        //在内部类中声明并初始化外部类的对象
        private static  final Singleton INSTANCE=new Singleton();
    }

    //提供公开的访问方式
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

Client测试类

//反射破坏单例模式
public class Client {
    public static void main(String[] args) throws Exception{
        //1.获取Singleton的字节码对象
        Class clazz=Singleton.class;
        //2.获取无参构造对象
        Constructor cons = clazz.getDeclaredConstructor();
        //3.取消访问检查
        cons.setAccessible(true);
        //4.创建Singleton对象
        Singleton s1 =(Singleton) cons.newInstance();
        Singleton s2 =(Singleton) cons.newInstance();

        System.out.println(s1==s2);

    }
}

2.3.2 问题的解决

  • 序列化、反序列化破坏单例模式的解决方法

在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。

Singleton类

public class Singleton implements Serializable {

    //私有构造方法
    private Singleton(){};

    //定义一个静态内部类
    private static class SingletonHolder{
        //在内部类中声明并初始化外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }

    //提供一个公共的访问方式
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }

    //当进行反序列化时,会自动调用该方法,将方法的返回值直接返回
    public Object readResolve(){
        return SingletonHolder.INSTANCE;
    }
}

Client类

//测试使用反射破坏单例模式
public class Client {

    public static void main(String[] args) throws Exception {
        //writeObject2File();
        readObjectFromFile();
        readObjectFromFile();
    }
    //从文件读取数据(对象)
    public static void readObjectFromFile() throws Exception {
        //1.创建对象输入流对象
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("E:\\新建文件夹"));
        //2.读取对象
        Singleton instance=(Singleton)ois.readObject();
        System.out.println(instance);
        //3.释放方法
        ois.close();
    }

    //向文件中写数据(对象)
    public static void writeObject2File() throws Exception{
        //1.获取Singleton对象
        Singleton instance=Singleton.getInstance();
        //2.创建对象输出流对象
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("E:\\新建文件夹"));
        //3.写对象
        oos.writeObject(instance);
        //4.释放资源
        oos.close();
    }

}
  • 反射方式破解单例的解决方法

SIngleton类

public class Singleton {

    private static boolean flag=false;

    //私有构造方法
    private Singleton(){
        synchronized (Singleton.class){
            //判断flag的信息是否时true,如果时true。说明非第一次访问,直接抛出异常,否则正常构造就好
            if(flag){
                throw new RuntimeException("不能创建多个对象");
            }
            //将flag设置为true
            flag=true;
        }
    }

    //定义一个·静态内部类
    private static class SingletonHolder{
        //在内部类中声明并初始化外部类的对象
        private static  final Singleton INSTANCE=new Singleton();
    }

    //提供公开的访问方式
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

Client类

//反射破坏单例模式
public class Client {
    public static void main(String[] args) throws Exception{
        //1.获取Singleton的字节码对象
        Class clazz=Singleton.class;
        //2.获取无参构造对象
        Constructor cons = clazz.getDeclaredConstructor();
        //3.取消访问检查
        cons.setAccessible(true);
        //4.创建Singleton对象
        Singleton s1 =(Singleton) cons.newInstance();
        Singleton s2 =(Singleton) cons.newInstance();

        System.out.println(s1==s2);
    }
}

三.工厂模式

3.1 概述

需求:设计一个咖啡点点餐系统

设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能。

具体类的设计如下:

Coffee类

public abstract class Coffee {

    public abstract String getName();

    //加糖
    public void addsugar(){
        System.out.println("加糖");
    }

    //加奶
    public void addMilk(){
        System.out.println("加奶");
    }
}

AmericanCoffee类

//美式咖啡
public class AmericanCoffee extends Coffee {
    public String getName(){
        return "美食咖啡";
    }
}

LatteCoffee类

//拿铁咖啡
public class LatteCoffee extends Coffee{
    public String getName(){
        return "拿铁咖啡";
    }
}

CoffeeStore类

public class CoffeeStore {

    public Coffee orderCoffee(String type) {
        //声明Coffe类型的变
        Coffee coffee = null;
        if("american".equals(type)){
            coffee=new AmericanCoffee();
        }
        else if("latte".equals(type)){
            coffee=new LatteCoffee();
        }
        else{
            throw new RuntimeException("对不起,你所点的咖啡没有");
        }
        //加配料
        coffee.addMilk();
        coffee.addsugar();
        return coffee;
    }
}

Client测试类

public class Client {
    public static void main(String[] args) {

        //1.创建咖啡店类
        CoffeeStore store=new CoffeeStore();
        //2.点咖啡
        Coffee coffee = store.orderCoffee("latte");

        System.out.println(coffee.getName());
    }
}

在JAVA中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦。

在本教程中会介绍三种工厂的使用

  • 简单工厂模式(不属于GOF的23中经典设计模式)
  • 工厂方法模式
  • 抽象工厂模式

3.1 简单工厂模式

简单工厂不是一种设计模式,反而比较像是一种编程习惯。

3.1.1 结构

简单工厂包含如下角色:

  • 抽象产品:定义了产品的规范,描述了产品的主要特性的功能。
  • 具体产品:实现或者继承抽象产品的子类。
  • 具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品。

3.1.2 实现

现在使用简单工厂对上面案例进行改进,类图如下:

Coffee类

//咖啡类
public abstract class Coffee {

    public abstract String getName();

    //加糖
    public void addsugar(){
        System.out.println("加糖");
    }

    //加奶
    public void addMilk(){
        System.out.println("加奶");
    }
}

AmericanCoffee类

//美式咖啡
public class AmericanCoffee extends Coffee {
    public String getName(){
        return "美食咖啡";
    }
}

LatteCoffee类

//拿铁咖啡
public class LatteCoffee extends Coffee {
    public String getName(){
        return "拿铁咖啡";
    }
}

SimpleCoffeeFactory类

public class SimpleCoffeeFactory {

    public Coffee createCoffee(String type) {
        //声明Coffee类型的变量,根据不同累心创建不同的Coffee子类对象
        Coffee coffee=null;
        if("american".equals(type)){
            coffee=new AmericanCoffee();
        }
        else if("latte".equals(type)){
            coffee=new LatteCoffee();
        }
        else{
            throw new RuntimeException("对不起,您所点的咖啡没有");
        }
        return coffee;
    }

}

CoffeeStore类

public class CoffeeStore {

    public Coffee orderCoffee(String type) {

        SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
        //调用生产咖啡的方法
        Coffee coffee=factory.createCoffee(type);

        //加配料
        coffee.addMilk();
        coffee.addsugar();
        return coffee;
    }
}

Client测试类 

public class Client {
    public static void main(String[] args) {
        //创建咖啡店类对象
        CoffeeStore store = new CoffeeStore();
        Coffee coffee = store.orderCoffee("latte");

        System.out.println(coffee.getName());

    }
}

工厂处理创建对象的细节,一旦有了SimpleCoffeeFactory,CoffeeStore类中的orderCoffee()就变成此对象的客户,后期如果需要Coffee对象直接从工厂中获取即可。这样也就解除了和Coffee实现类的耦合,同时又产生了新的耦合,CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合,工厂对象和商品对象的耦合。

后期如果再加新品种的咖啡,我们势必要需求修改SiimpleCoffeeFactory的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只能需要修改工厂类的代码,省去其他的修改操作。

3.1.3  优缺点

优点

封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户的代码,如果要实现新产品直接修改工厂类,而不是需要再原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展

缺点:

增加新产品是还是需要修改工厂类的代码,违背了“开闭原则”。

3.1.4 扩展

静态工厂

在开发中也有一部分人将工厂类中创建对象的功能定义为静态的;这个就是静态工厂模式,它也不是23中设计模式中的。代码如下:

public class SimpleCoffeeFactory {
    public static Coffee createCoffee(String type) {
        //声明Coffee类型的变量,根据不同累心创建不同的Coffee子类对象
        Coffee coffee=null;
        if("american".equals(type)){
            coffee=new AmericanCoffee();
        }
        else if("latte".equals(type)){
            coffee=new LatteCoffee();
        }
        else{
            throw new RuntimeException("对不起,您所点的咖啡没有");
        }
        return coffee;
    }
}

3.2 工厂方法模式

针对上例中的缺点,使用工厂方法模式就可以完美的解决,完全遵循开闭原则。

3.2.1 概念

【延迟】定义一个用于创建对象的接口,让子类决定实例化哪个产品类的对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。

3.2.2 结构

工厂方法模式的主要角色:

  • 抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
  • 具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品:实现了抽象角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。

3.2.3 实现

使用工厂方法模式对上例进行改进,类图如下:

CoffeeFactory接口

//抽象工厂
public interface CoffeeFactory {

    //创建Coffee对象的方法
    Coffee createCoffee();

}

AmericanCoffeeFactory类

//美式咖啡类,专门用于生产美式咖啡
public class AmericanCoffeeFactory implements  CoffeeFactory {

    public Coffee createCoffee() {
        return new AmericanCoffee();
    }
}

LatteCoffeeFactory类

//拿铁咖啡工厂,专门生产拿铁咖啡
public class LatteCoffeeFactory implements CoffeeFactory {
    public Coffee createCoffee() {
        return new LatteCoffee();
    }
}

Client测试类

public class Client {
    public static void main(String[] args) {
        //创建咖啡店对象
        CoffeeStore coffeeStore = new CoffeeStore();
        //创建对象
        CoffeeFactory coffeeFactory = new AmericanCoffeeFactory();
        coffeeStore.setFactory(coffeeFactory);

        //点咖啡
        Coffee coffee = coffeeStore.orderCoffee();
        System.out.println(coffee.getName());
    }
}

从以上的编写代码可以看到,要增加产品类式也要相应地增加工厂类,不需要修改工厂类的代码了,这样就解决了简单工厂模式的缺点。

工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。

3.2.4 优缺点

优点:

  • 用户只需要知道具体工厂的名称就可以得到所要的产品,无须知道产品的具体创建过程。
  • 在系统增加新的产品是只需要添加产品类和对象的具体工厂类,无须对原工厂进行任何修改,满足开闭原则。

缺点:

  • 每增加一个产品就要增加一个具体品类和一个对应的具体工厂类,这增加了系统的复杂度。

3.3 抽象工厂模式

3.3.1 概述

前面介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机、传智播客只培养计算机软件专业的学生等。

这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,既生产多等级(种类)的产品,如电器厂既生产电视机又生产洗衣机或空调,大学生既有软件专业又有生物专业等。

本节要介绍的抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,下图所示横轴是产品等级,也就是同一类产品;纵轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。

概念:抽象工厂模式是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。

3.3.2 结构

抽象工厂模式的主要角色如下:

  • 抽象工厂:提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
  • 具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品:实现了抽象产品角色所定义的接口,有具体工厂来创建,它同具体工厂之间是多对一的关系。

3.3.3 实现

现在咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等,要是按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。其中拿铁咖啡、美式咖啡是一个产品等级,都是咖啡;提拉米苏、抹茶慕斯也是一个产品等级;拿铁咖啡和提拉米苏是同一个产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是同一个产品族(也就是属于美式风味)。所以这个案例可以使用抽象工厂模式实现。类图如下:

Dessert抽象产品

//甜品抽象类
public abstract class Dessert {
    public abstract void show();
}

Trimisu具体产品

//提拉米苏类
public class Trimisu extends Dessert{
    public void show(){
        System.out.println("提拉米苏");
    }
}

MatchMousse具体产品

//抹茶慕斯类
public class MatchaMousse extends Dessert{
    public void show(){
        System.out.println("抹茶慕斯");
    }
}

Coffee抽象产品

//咖啡类
public abstract class Coffee {
    public abstract String getName();
    //加糖
    public void addsugar(){
        System.out.println("加糖");
    }
    //加奶
    public void addMilk(){
        System.out.println("加奶");
    }
}

AmericanCoffee具体产品

//美式咖啡
public class AmericanCoffee extends Coffee {
    public String getName(){
        return "美式咖啡";
    }
}

LatteCoffee具体产品

//拿铁咖啡
public class LatteCoffee extends Coffee {
    public String getName(){
        return "拿铁咖啡";
    }
}

DessertFactory抽象工厂

public interface DessertFactory {
    //生产咖啡的功能
    Coffee createCoffee();
    //生产甜品的功能
    Dessert createDessert();
}

AmericanDessertFactory具体工厂

//生产美式咖啡和抹茶慕斯
public class AmericanDessertFactory implements DessertFactory {
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }
    public  Dessert createDessert() {
        return new MatchaMousse();
    }
}

ItalyDessertFactory具体工厂

//生产拿铁和提拉米苏产品
public class ItalyDessertFactory implements DessertFactory {
    public Coffee createCoffee(){
        return new LatteCoffee();
    }
    public Dessert createDessert(){
        return new Trimisu();
    }
}

Client测试类

public class Client {
    public static void main(String[] args) {
        //创建的是意大利风味甜品工厂对象
        ItalyDessertFactory factory=new ItalyDessertFactory();
        AmericanDessertFactory factory1=new AmericanDessertFactory();
        //获取拿铁咖啡和提拉米苏甜品
        Coffee coffee = factory.createCoffee();
        Dessert dessert = factory.createDessert();
        Coffee coffee1 = factory1.createCoffee();
        Dessert dessert1 = factory1.createDessert();

        System.out.println(coffee.getName());
        dessert.show();
        System.out.println(coffee1.getName());
        dessert1.show();
    }
}

3.3.4 优缺点

优点:

当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只能使用同一个产品族中的对象。

缺点:

当产品族中需要新增加一个新的产品时,所有的工厂类都需要进行修改。

3.3.5 使用场景

  • 当需要创建的对象时一系列相互关联或相互依赖的产品族时,如电器工厂的电视机、洗衣机、空调等。
  • 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某个品牌的衣服和鞋子。
  • 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。

如:输入法换皮肤,一整套一起换。生成不同操作系统程序。

3.4 JDK源码解析-Collection.iterator方法

public class Demo {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        //获取迭代器对象
        Iterator<String> iterator=list.iterator();
        //使用迭代器遍历
        while(iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

对上面的代码,使用迭代器遍历集合,获取集合中的元素。而单列集合获取迭代器的方法就使用到工厂方法模式。我们通过类图看看结构:

Collection接口时抽象工厂,ArrayList时具体工厂,Iterator接口是抽象商品类,ArrayList类中的Iter内部类时具体的商品类。在具体的工厂类中iterator()方法创建具体的商品类的对象。

四.原型模式

4.1 概述

同一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

4.2 结构

原型模式包含如下角色:

  • 抽象原型类:规定了具体原型对象必须实现的clone()方法
  • 具体原型类:实现抽象原型类的clone()方法,它是可以被复制的对象
  • 访问类:使用具体原型类中的clone()方法来复制新的对象

接口类图如下:

4.3 实现

原型模式的克隆分为浅克隆和深克隆。

浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。

深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

JAVA中Object类中提供了clone()方法来实现浅克隆。Cloneable接口时上面类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。代码如下:
Realizetype实现类

public class Realizetype implements Cloneable{

    public Realizetype() {
        System.out.println("Realizetype Constructor");
    }

    @Override
    public Realizetype clone() throws CloneNotSupportedException {
        System.out.println("Realizetype clone");
        return (Realizetype) super.clone();
    }
}

Client测试类

public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {
        //创建一个原型类对象
        Realizetype realizetype=new Realizetype();
        Realizetype realizetype1=realizetype.clone();

        System.out.println("原型对象和克隆出来的是否时同一个同一个对象?"+(realizetype==realizetype1));
    }
}

4.4 案例

用原型模式生成“三好学生”奖状

同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个“三好学生”奖状出来,然后再修改奖状上的名字即可。

奖状如下:

Citation实现类

public class Citation implements Cloneable{

    //三好学生上的姓名
    private String name;

    public String getName(){
        return name;
    }

    public void setName(String name){
        this.name = name;
    }

    public Citation clone()throws CloneNotSupportedException{
        return (Citation)super.clone();
    }

    public void show(){
        System.out.println(name+"同学,在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
    }
}

CitationTest类

public class CitationTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        //1.创建原型对象
        Citation c1 = new Citation();
        //2.克隆奖状对象
        Citation c2=c1.clone();

        c1.setName("张三");
        c2.setName("李四");

        //3。调用show方法展示
        c1.show();
        c2.show();
    }
}

4.5 使用场景

  • 对象的创建非常复杂,可以使用原型模式快捷的创建对象。
  • 性能和安全要求比较高。【如果new,用户可以根据自己想法创建对象】

4.6 扩展(深克隆)

将上面的“三好学生”奖状的案例中Citation类的name属性修改为Student类型的属性。代码如下:

Student类 

public class Student {
    //学生的姓名
    private String name;

    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public String toString(){
        return "Student{"+
               "name='"+name+'\''+
               '}';
    }
}

 Citation类

public class Citation implements Cloneable{

    //三好学生上的姓名
//    private String name;
//
//    public String getName(){
//        return name;
//    }
//
//    public void setName(String name){
//        this.name = name;
//    }

    private Student stu;

    public Student getStu(){
        return stu;
    }

    public void setStu(Student stu){
        this.stu = stu;
    }

    public Citation clone()throws CloneNotSupportedException{
        return (Citation)super.clone();
    }

    public void show(){
        System.out.println(stu.getName()+"同学,在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
    }
}

CitationTest类

public class CitationTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        //1.创建原型对象
        Citation c1 = new Citation();
        //创建张三学生对象
        Student stu1=new Student();
        stu1.setName("张三");
        c1.setStu(stu1);

        //2.克隆奖状对象
        Citation c2=c1.clone();
        c2.getStu().setName("李四");

//        c1.setName("张三");
//        c2.setName("李四");

        //3。调用show方法展示
        c1.show();
        c2.show();
    }
}

说明:

stu1对象和stu2对象是同一个对象,就会产生stu2对象中name属性值改为“李四”,两个Citation(奖状)对象显示的都是李四。这就是浅克隆的效果,对具体原型类中的引用类型的属性进行引用的复制。这种情况需要使用深克隆,而进行深克隆需要使用对象流。代码如下:

Student类

public class Student implements Serializable {
    //学生的姓名
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String toString(){
        return "Student{"+
               "name='"+name+'\''+
               '}';
    }
}

Citation类

public class Citation implements Cloneable, Serializable {

    //三好学生上的姓名
//    private String name;
//
//    public String getName(){
//        return name;
//    }
//
//    public void setName(String name){
//        this.name = name;
//    }

    private Student stu;

    public Student getStu(){
        return stu;
    }

    public void setStu(Student stu){
        this.stu = stu;
    }

    public Citation clone()throws CloneNotSupportedException{
        return (Citation)super.clone();
    }

    public void show(){
        System.out.println(stu.getName()+"同学,在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
    }
}

CitationTest测试类

public class CitationTest {
    public static void main(String[] args) throws Exception {
        //1.创建原型对象
        Citation c1 = new Citation();
        //创建张三学生对象
        Student stu1=new Student();
        stu1.setName("张三");
        c1.setStu(stu1);

        //创建对象输出流对象
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("E:/a.txt"));
        //写对象,序列化
        oos.writeObject(c1);
        //释放资源
        oos.close();

        //创建对象输入流对象
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("E:/a.txt"));
        //读取对象
        Citation c2=(Citation)ois.readObject();
        //释放资源
        ois.close();
        c2.getStu().setName("李四");

        c1.show();
        c2.show();

    }
}

运行结果:

注意:Citation类和Student类必须实现Serializable接口,否则会抛NotSerializableException异常。

五.建造者模式

5.1 概述

将一个复杂对象的构建与表示分离,使得同样的构建过程剋有创建不同的表示。

  • 分离了部件的构造(由Builder来负责)和装配(由Direator负责)。从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
  • 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
  • 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

5.2 结构

建造者模式包含如下角色:

  • 抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的对象部件的创建。
  • 具体建造者类(ConcreteBuilder):实现Builder接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
  • 产品类(Product):要创建的复杂对象。
  • 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按照某种顺序创建。

类图如下:

5.3 实例

创建共享单车

生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维、铝合金等材质的,车座有橡胶、真皮等材质。对于自行车的生产可以使用建造者模式。这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和OfoBuilder是具体建造者;Director是指挥者。类图如下:

Bike类

//产品对象
public class Bike {
    private String frame; //车架
    private String seat; //车座
    public String getFrame(){
        return frame;
    }
    public String getSeat(){
        return seat;
    }
    public void setFrame(String frame){
        this.frame = frame;
    }
    public void setSeat(String seat){
        this.seat = seat;
    }
}

Builfer抽象类

//抽象构建者
public abstract class Builder {

    //声明Bike类型的变量并进行赋值,提高代码的复用性
    protected Bike bike=new Bike();  //一个对象未组装

    public abstract void buildFrame();

    public abstract void buildSeat();

    public  abstract Bike createBike();

}

MobileBuilder实现类

public class MobileBuilder extends Builder{

    public void buildFrame(){
        bike.setFrame("碳纤维车架");
    }
    public void buildSeat(){
        bike.setSeat("真皮车座");
    }
    public Bike createBike(){
        return bike;
    }

}

OfoBuilder实现类

public class OfoBuilder extends Builder {
    public void buildFrame(){
        bike.setFrame("铝合金车架");
    }

    public void buildSeat(){
        bike.setSeat("橡胶车座");
    }

    @Override
    public Bike createBike() {
        return bike;
    }
}

Director指挥类

public class Director {
    //声明Builder类型的变量
    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    //组装自行车的功能
    public Bike construct(){
        builder.buildFrame();
        builder.buildSeat();
        return builder.createBike();
    }
}

Client测试类

public class Client {
    public static void main(String[] args) {
        //创建指挥者对象
        Director director=new Director(new MobileBuilder());
        //让指挥者只会组装自行车
        Bike bike = director.construct();

        System.out.println(bike.getFrame());
        System.out.println(bike.getSeat());
    }
}

注意:

上面示例是Builder模式的常规方法,指挥者类Director在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用的先后次序,并向调用者但会完整的产品类,但是有些情况下需要简化系统结构,可以把只会者类和抽象建造者类进行结合。

public abstract class Builder {

    //声明Bike类型的变量并进行赋值,提高代码的复用性
    protected Bike bike=new Bike();  //一个对象未组装

    public abstract void buildFrame();
    public abstract void buildSeat();
    public  abstract Bike createBike();

    public Bike construct(){
        this.buildFrame();
        this.buildSeat();
        return this.createBike();
    }

}

说明:

这样做确实简化了系统结构,但同时也加重了抽象建造者的职责,也不是太符合单一职责原则,如果construct()过于复杂,建议还是封装到Director中。

5.4 优缺点

优点:

  • 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
  • 在建造者模式中,客户端不必知道产品内部的组成细节,将产品本身和产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
  • 可以更加精细控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
  • 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也不会对原有的功能引入风险。符合开闭原则。

缺点:

  • 建造者模式所创建的产品一般具有较多的共同点,组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定限制。

5.5 使用场景

建造者模式创建的是复杂对象,其产品的各个部分经常面临剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用:

  • 创建的对象较复杂,由多个部件构成,各部件面临复杂的变化,但构件间的建造顺序是稳定的。
  • 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。

5.6 模式扩展

建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数的时候,如果创建这个类的实例,代码的可读性就会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。

Phone类 

public class Phone {

    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;

    public Phone(String cpu, String screen, String memory, String mainboard) {
        this.cpu = cpu;
        this.screen = screen;
        this.memory = memory;
        this.mainboard = mainboard;
    }

    public String getCpu() {
        return cpu;
    }

    public void setCpu(String cpu) {
        this.cpu = cpu;
    }

    public String getScreen() {
        return screen;
    }

    public void setScreen(String screen) {
        this.screen = screen;
    }

    public String getMemory() {
        return memory;
    }

    public void setMemory(String memory) {
        this.memory = memory;
    }

    public String getMainboard() {
        return mainboard;
    }

    public void setMainboard(String mainboard) {
        this.mainboard = mainboard;
    }

    public String toString() {
        return "Phone{"+
                "cpu='"+cpu+'\''+
                ". screen='"+screen+'\''+
                ". memory='"+memory+'\''+
                ". mainboard='"+mainboard+'\''+
                '}';
    }

}

Client测试类

public class Client {
    public static void main(String[] args) {
        //创建Phone对象
        Phone phone =new Phone("intel","三星屏幕","金士顿","华硕");
        System.out.println(phone);
    }
}

上面在客户端代码中构建Phone对象,传递了四个参数,如果参数更多呢?代码的可读性及使用的成本就是比较高。

重构后代码:

Phone类

public class Phone {

    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;

    public String toString() {
        return "Phone{"+
                "cpu='"+cpu+'\''+
                ". screen='"+screen+'\''+
                ". memory='"+memory+'\''+
                ". mainboard='"+mainboard+'\''+
                '}';
    }

    //私有构造方法
    private Phone(Builder builder) {
        this.cpu = builder.cpu;
        this.screen = builder.screen;
        this.memory = builder.memory;
        this.mainboard = builder.mainboard;
    }

    public static final class Builder {
        private String cpu;
        private String screen;
        private String memory;
        private String mainboard;

        public Builder cpu(String cpu){
            this.cpu = cpu;
            return this;
        }

        public Builder screen(String screen){
            this.screen = screen;
            return this;
        }

        public Builder memory(String memory){
            this.memory = memory;
            return this;
        }

        public Builder mainboard(String mainboard){
            this.mainboard = mainboard;
            return this;
        }

        public Phone build() {
            return new Phone(this);
        }
    }

}

Client测试类

public class Client {
    public static void main(String[] args) {
        //创建Phone对象
        Phone phone = new Phone.Builder()
                .cpu("intel")
                .screen("三星屏幕")
                .memory("金士顿")
                .mainboard("华硕")
                .build();
        System.out.println(phone);
    }
}

重构后的代码在使用起来更方便,某种程度上也可以提高开发效率。从软件设计上,对程序员的要求比较高。

六.创建者模式对比

6.1 工厂方法模式VS建造者模式

工厂方法模式主要的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。

我们举个简单例子来说明两者的差异,如果制造一个超人,如果使用工厂方法模式,直接产出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。

6.2 抽象工厂模式VS建造者模式

抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。

建造者模式则是按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。

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

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

相关文章

用手机写一本电子书

第1步、进入Andi.cn网站 第2步、点击登录&#xff0c;注册用户 第3步、点击去创作&#xff0c;进入创作页面 第4步、点击右下角的小笔&#xff0c;写一篇文章 第5步、下翻&#xff0c;点击提交按钮 第6步、再写一篇文章 第7步、点击栏目设计 第8步、进入栏目设计&#xff0c;点…

excel卓越之道笔记

excel快捷键 1.Alt+=一键求和 2.Tab补全函数名称 3.CONCAT可以连选,CONCATENATE只能一个单元格一个单元格点选 4.excel365用不了phonetic函数,但是可以用concat代替 5.textjoin连接标识码,在Arcgis中筛选出所需要素,也是很好用的 6.法1:alt+; 定位可见单元格,复制后只…

Linux入门——01常用命令

0.命令行解释器shell 用户无法直接给操作系统指令&#xff0c;需要经过shell,才能让操作系统明白。如果用户对操作系统非法操作&#xff0c;会有shell保护。shell本身也是一个进程&#xff0c;当然&#xff0c;用户给shell的指令&#xff0c;shell会派生出子进程进行执行&#…

Unity Protobuf3.21.12 GC 问题(反序列化)

背景&#xff1a;Unity接入的是 Google Protobuf 3.21.12 版本&#xff0c;排查下来反序列化过程中的一些GC点&#xff0c;处理了几个严重的&#xff0c;网上也有一些分析&#xff0c;这里就不一一展开&#xff0c;默认读者已经略知一二了。 如果下面有任何问题请评论区留言提…

【Kubernetes中如何对etcd进行备份和还原】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

不同路径

不同路径 思路&#xff1a; 法一&#xff1a;动态规划 const int N 110; class Solution { int dp[N][N];//dp[i][j]&#xff1a;从起点走到 i j的路径个数。 public:int uniquePaths(int m, int n) {for(int i1;i<n;i){dp[1][i]1;} for(int i1;i<m;i) dp[i][1]1;f…

day36.动态规划+重载操作符

动态规划好难啊(ಥ﹏ಥ) 终于搞懂0-1背包问题的二维数组转一维数组优化的问题了。如图所示: 将二维数组转换成一位数组的核心就是&#xff0c;dp[i][j]选取时&#xff0c;他的值只与dp[i-1][j]&#xff0c;也就是上一行有关&#xff0c;所以可以引出使用一维数组代替二维数组…

python 使用宝塔面板在云服务器上搭建 flask

打开宝塔面板到【网站】&#xff0c;选择【python项目】&#xff0c;点【添加python项目】 填上相关信息&#xff1a; 注意&#xff1a;项目端口是你打算在外网用来访问flask的端口号 勾选【放行端口】&#xff0c;并提交 到阿里云里&#xff0c;选择安全组 手动添加放行端口…

datawind可视化查询-其他函数

飞书文档学习链接:https://www.volcengine.com/docs/4726/47275 1. 用户名函数 用户名函数并非 ClickHouse 官方函数,而是与项目用户信息相结合,用于返回当前使用用户的指定信息的函数。 USERNAME()可返回当前用户的用户名,如下所示。该函数也可与其他函数组合使用 2. J…

51 无显式主键时 mysql 增加的 DB_ROW_ID

前言 这里主要是 探讨, 在我们创建了一个 无主键的数据表, 然后 mysql 会为我们增加的这一个 DB_ROW_ID 的相关 新建一个无主键字段的数据表如下 CREATE TABLE implicit_id_table (username varchar(16) DEFAULT NULL,age int(11) DEFAULT NULL ) ENGINEInnoDB DEFAULT CH…

MySQL范围分区分区表

什么是范围分区分区表&#xff1f; 范围分区是一种根据某个列的范围值来分割表数据的分区方式。在范围分区中&#xff0c;每个分区都有自己的范围条件&#xff0c;当插入数据时&#xff0c;MySQL会根据指定的范围条件将数据分配到相应的分区中。这种分区方式可以使得表的数据按…

2024前端面试题-css篇

1.p和div区别 p自带有一定margin-top和margin-bottom属性值&#xff0c;而div两个属性值为0&#xff0c;也便是两个p之间有不一定间距&#xff0c;而div没有。 2.对css盒模型的理解 标准盒模型&#xff1a;content不包括padding、border、margin ie盒模型&#xff1a;conten…

关于我的生信笔记开通《知识星球》

关于知识星球 1. 为什么到现在才开通《知识星球》 从很早关注我的同学应该了解小杜的知识分享历程&#xff0c;小杜是从2021年11月底开始进入此“坑”&#xff0c;一直坚持到现在&#xff0c;马上3年了&#xff08;24年11月底到期&#xff09;。自己也从一个小青年&#xff0…

【图文并茂】ant design pro 如何统一封装好 ProFormSelect 的查询请求

你仔细看上面的图片吧 经常有这样的需求吧。 这些列表都是查询出来的。 后端 你的后端必须要有 api 。 const getUsers handleAsync(async (req: Request, res: Response) > {const { email, name, live, current 1, pageSize 10 } req.query;const query: any {};…

的卢易表:批量处理Excel数据的自动化工具

的卢易表&#xff1a;批量处理Excel数据的自动化工具 简介 的卢易表是一个可以批量批量处理Excel数据的自动化工具。 自动化是其最大的特点&#xff0c;因为它可以根据配置好的选项自动处理excel数据。 批量是它另一个特点&#xff0c;因为可以做到自动化&#xff0c;所以你可…

JavaScript语法基础之DOM基础

目录 1. DOM 基础 1.1. DOM 是什么&#xff1f; 1.1.1. DOM 对象 1.1.2. DOM 结构 1.2. 节点类型 1.3. 获取元素 1.3.1. getElementById() 1.3.2. getElementsByTagName() 1.3.3. getElementsByClassName() 1.3.4. getElementsByName() 1.4.如何去操作对象 修改属性…

代驾系统源码开发中的用户体验优化:从设计到实现的全方位解析

在当今数字化时代&#xff0c;代驾服务已经成为城市生活中不可或缺的一部分。为了帮助开发者和企业快速搭建代驾服务平台&#xff0c;许多开源的代驾系统源码应运而生。这些源码不仅节省了开发时间&#xff0c;还为进一步的定制化开发提供了坚实的基础。本文将以“开源代驾系统…

Git使用——将GitHub设置成Token

GitHub提供了一种授权方式&#xff0c;使用Token来代替用户名和密码进行身份验证&#xff1b; 下面是将GitHub设置成Token的方法和操作流程&#xff1b; 一、登录GitHub账户 1. GitHub官网&#xff1a;https://github.com 2. 点击右上角的“Sign in”按钮&#xff0c;输入Gi…

遗传算法与深度学习实战(7)——使用遗传算法解决N皇后问题

遗传算法与深度学习实战&#xff08;7&#xff09;——使用遗传算法解决N皇后问题 0. 前言1. N 皇后问题2. 解的表示3. 遗传算法解决 N 皇后问题小结系列链接 0. 前言 进化算法 (Evolutionary Algorithm, EA) 和遗传算法 (Genetic Algorithms, GA) 已成功解决了许多复杂的设计…

Leetcode JAVA刷刷站(74)搜索二维矩阵

一、题目概述 二、思路方向 要在一个满足上述条件的矩阵中查找一个整数 target&#xff0c;我们可以利用矩阵的排序和递增特性来优化搜索过程。由于矩阵的每一行都是非严格递增的&#xff0c;且后一行的第一个元素大于前一行的最后一个元素&#xff0c;我们可以将矩阵视为一个…