【设计模式】- 创建者模式

news2025/5/16 3:14:40

单例模型

饿汉式

静态方法创建对象

public class Singleton {
    // 私有构造方法
    private Singleton(){}
    private static Singleton instance = new Singleton();
    // 提供一个外界获取的方法
    public static Singleton getInstance(){
        return instance;
    }
}

静态代码块创建对象

public class Singleton {
    private Singleton(){}
    private static Singleton instance;
    static {
        instance = new Singleton();
    }
    public static Singleton getInstance(){
        return instance;
    }
}

懒汉式

synchronized关键字

public class Singleton {
    private Singleton(){}
    private static Singleton instance;
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

问题】上边的代码是存在线程不安全的情况的,当线程1进来,判读instance==null成立,准备创建对象;但是线程1还没创建对象完毕时,线程2来了,线程2也判断成立,也去创建对象,此时就会创建两个不同的对象。
解决】:给getInstance方法上添加synchronized关键字

public class Singleton {
    private Singleton(){}
    private static Singleton instance;
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton(); // 下边简称:“写操作”
        }
        return instance; // 下边简称:“读操作”
    }
}

双重检查锁

问题】:对于getInstance()方法,其实大部分的操作都是读操作,读操作是线程安全的,如果直接给getInstance方法上加锁,其实会造成大量的线程等待。
解决】:调整加锁的时机,双重检查锁

public class Singleton {
    private Singleton(){}
    private static volatile Singleton instance; // volatile:保证指令的有序性和可见性
    public static Singleton getInstance(){
        // 第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象
        if(instance == null){
            synchronized (Singleton.class){
                // 第二次判断
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

静态内部类

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

JVM加载外部类的过程是不会加载静态内部类的,只有内部类的属性、方法被调用时才会加载并初始化静态属性。静态属性被static修饰,所以只会被实例化一次。

枚举类

枚举类也是线程安全的,只会被加载一次,也是所有单例实现中唯一一种不会被破坏的单例模式

public enum Singleton {
    INSTANCE
}

枚举方式是属于饿汉式的方式,在不考虑浪费内存空间的情况下,首选枚举方式

存在的问题

问题】:破坏单例模式(让单例模式可以创建多个对象,枚举方式除外)

通过序列化和反序列化破坏单例模式

public class Client {
    public static void main(String[] args) throws Exception {
        writeObject2File();
        readObjectFromFile(); // Singleton@27bc2616
        readObjectFromFile(); // Singleton@3941a79c
    }

    // 从文件中读取对象
    private static void readObjectFromFile() throws Exception {
        // 1. 创建输入流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Desktop\\a.txt"));
        // 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("D:\\Desktop\\a.txt"));
        oos.writeObject(instance);
        // 3. 释放资源
        oos.close();
    }
}

上面的代码生成的两个对象不是同一个对象,破坏了单例模式

通过反射破坏单例模式

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 s1 = (Singleton) cons.newInstance();
        Singleton s2 = (Singleton) cons.newInstance();
        System.out.println(s1 == s2); // false - 破坏单例模式
    }
}

上边代码返回的是false,说明s1和s2不是同一个对象,破坏了单例模式

问题解决

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

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

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

反射方式破坏单例模式解决方法

其实反射破坏的原理是:通过反射获取Singleton的私有构造方法,然后通过这个私有的构造方法去创建对象。
因此我们只需要在构造方法里添加一个判断即可

public class Singleton implements Serializable {
    private static boolean flag = false;
    private Singleton(){
        synchronized (Singleton.class){
            if(flag) {
                throw new RuntimeException("不能创建多个对象");
            }
            flag = true;
        }
    }
    // 定义一个静态内部类
    private static class SingletonHolder{
        // 在内部类中声明并初始化外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

JDK源码解析 - Runtime类

饿汉式:
在这里插入图片描述

工厂模式

引例:点咖啡

现在有美式咖啡、拿铁咖啡,顾客可以选择咖啡的种类,咖啡都需要进行加糖加奶。

原本的写法:

咖啡类:

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

美式咖啡:

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

拿铁咖啡:

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

咖啡店:

public class CoffeeStore {
    public Coffee orderCoffee(String coffeeType) {
        // 声明Coffee类型的变量,根据不同的类型创建不同的子类对象
        Coffee coffee = null;
        if("american".equals(coffeeType)) {
            coffee = new AmericanCoffee();
        }else if("latte".equals(coffeeType)) {
            coffee = new LatteCoffee();
        }else {
            throw new RuntimeException("对不起的,您所点的咖啡没有");
        }
        // 加配料
        coffee.addmilk();
        coffee.addsugar();
        return coffee;
    }
}

测试方法:

public class Client {
    public static void main(String[] args) {
        // 创建咖啡店类
        CoffeeStore store = new CoffeeStore();
        // 点咖啡
        Coffee coffee = store.orderCoffee("latte");
        System.out.println(coffee.getName());
    }
}

存在问题】:如果需要更换对象,那么所有new对象的地方都要修改一遍,这就违背了软件设计的开闭原则。
解决】:工厂模式

简单工厂模式

角色:

  • 抽象产品:定义了产品的规范(咖啡类)
  • 具体产品:是现货集成抽象产品的子类(美式咖啡、拿铁咖啡)
  • 具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品

简单咖啡工厂类,用来生产咖啡:

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

咖啡店:

public class CoffeeStore {
    public Coffee orderCoffee(String coffeeType) {
        SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
        // 调用生产咖啡的方法
        Coffee coffee = factory.createCoffee(coffeeType);

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

解除了CoffeeStore和具体的咖啡的耦合
优势】:工厂类的客户端可能有很多,这样只需要去修改SimpleCoffeeFactory的代码,可以省去其他的修改操作。
劣势】:如果要再加新的品种的咖啡,就必须要修改SimpleCoffeeFactory的代码,这违反了开闭原则。

工厂方法模式

角色:

  • 抽象产品:定义了产品的规范(咖啡类)
  • 具体产品:是现货集成抽象产品的子类(美式咖啡、拿铁咖啡)
  • 抽象工厂:提供创建产品的接口,调用者通过访问它具体工厂的工厂方法来创建产品
  • 具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品

抽象工厂:

public interface CoffeeFactory {
    // 创建咖啡对象的方法
    Coffee createCoffee();
}

具体工厂:

  • 拿铁咖啡工厂对象 - 用来生产拿铁咖啡
public class LatteCoffeeFactory implements CoffeeFactory {

    @Override
    public Coffee createCoffee() {
        return new LatteCoffee();
    }
}
  • 美式咖啡工厂对象 - 用来生产美式咖啡
public class AmericanCoffeeFactory implements CoffeeFactory {
    @Override
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }
}

咖啡店:

public class CoffeeStore {
    private CoffeeFactory factory;
    public void setFactory(CoffeeFactory factory) {
        this.factory = factory;
    }
    // 点咖啡
    public Coffee orderCoffee() {
        // 创建咖啡
        Coffee coffee = factory.createCoffee();
        // 加配料
        coffee.addmilk();
        coffee.addmilk();
        return coffee;
    }
}

测试方法:

public class Client {
    public static void main(String[] args) {
        // 创建咖啡店类
        CoffeeStore store = new CoffeeStore();
        store.setFactory(new AmericanCoffeeFactory()); // 生产美式咖啡
        // 点咖啡
        Coffee coffee = store.orderCoffee();
        System.out.println(coffee.getName());
    }
}

优势】:用户只要知道具体工程的类名就可以得到产品;系统增加新的产品只需要新增具体产品类和对应的具体工厂类即可。
劣势】:每增加一个产品就需要增加一个具体产品类和具体工厂类, 增加了系统的复杂度

抽象工厂模式

抽象工厂模式和工厂方法模式的区别:

  • 工厂方法模式:只生产一个等级的产品
  • 抽象工厂模式:可以创建多个不同等级的产品

需求变更】:现在咖啡店不仅需要生产咖啡,还需要生产甜品

甜品抽象类:

public abstract class Dessert {
    abstract void show();
}

提拉米苏类:

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

抹茶慕斯类:

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

甜品抽象工厂:

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

意大利风味甜品工厂(生产拿铁咖啡和提拉米苏甜品):

public class ItaltyDessertFactory implements DessertFactory{ // 意大利风味甜品工厂(生产拿铁咖啡和提拉米苏甜品)
    @Override
    public Coffee createCoffee() {
        return new LatteCoffee(); // 拿铁咖啡
    }

    @Override
    public Dessert createDessert() {
        return new Trimisu(); // 提拉米苏
    }
}

美式咖啡的甜品工厂(生产美式咖啡和抹茶慕斯):

public class AmericanDessertFactory implements DessertFactory{ // 美式咖啡的甜品工厂 - 生产美式咖啡和抹茶慕斯
    @Override
    public Coffee createCoffee() {
        return new AmericanCoffee(); // 美式咖啡
    }

    @Override
    public Dessert createDessert() {
        return new MatchaMousse(); // 抹茶慕斯
    }
}

测试类:

public class Client {
    public static void main(String[] args) {
        // 创建意大利风味的工厂
        ItaltyDessertFactory it = new ItaltyDessertFactory();
        Coffee coffee = it.createCoffee(); // 拿铁咖啡
        Dessert dessert = it.createDessert(); // 提拉米苏
        System.out.println(coffee.getName());
        dessert.show();
    }
}

如果要加一个产品族,只需要再加一个对应的工厂类,不需要修改其他类
优点】:客户端只能使用同一个产品族中的对象
缺点】:产品族种需要新增一个新的产品,所有的工厂类都需要进行修改。

适用场景:

  • 需要创建的对象是一系列相互关联或相互依赖的产品族(电器工厂中的电视机、洗衣机、空调)
  • 系统种有多个产品族每次只用其中一种产品(有人只喜欢穿一个品牌的衣服和裤子)
  • 系统提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构
  • 如:搜狗输入法换皮肤(一套一起换)

模式扩展(Spring框架底层)

bean.properties文件:

american = com.itheima.pattern02factory.factory_04_config.AmericanCoffee
latte = com.itheima.pattern02factory.factory_04_config.LatteCoffee

工厂类:

public class CoffeeFactory {
    // 1. 定义容器对象存储咖啡对象
    private static HashMap<String, Coffee> map = new HashMap<>();
    // 2. 加载配置文件,并创建该配置文件里类的对象并进行存储(只需要加载一次)
    static {
        // 2.1 创建Properties对象
        Properties p = new Properties();
        // 2.2 调用p对象中的load方法进行配置文件的加载
        InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
            p.load(is);
            // 2.3 从p集合中获取全类名并创建对象
            Set<Object> keys = p.keySet();
            for (Object key : keys) {
                String className = p.getProperty((String) key);
                // 2.4 通过反射技术创建对象
                Class clazz = Class.forName(className);
                Coffee coffee = (Coffee) clazz.newInstance();
                // 2.5 将名称和对象存储到容器中
                map.put((String) key, coffee);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    // 根据名称获取对象
    public static Coffee createCoffee(String name) {
        return map.get(name);
    }
}

JDK源码解析 - Collection.iterator()

在这里插入图片描述
在这里插入图片描述

Collection是抽象工厂;ArrayList是具体工厂
【补】:DateForamt类中的getInstance()、Calendar类中的getInstance()也是工厂模式

原型模式

用一个已经创建的对象作为原型,复制这个原型对象来创建一个和原型对象相同的新对象。

包含的角色:

  • 抽象原型类:规定了具体原型对象必须实现的clone()方法
  • 具体原型类:实现抽象原型类中的clone()方法,他是可以被复制的对象

浅克隆:创建一个新对象,新对象的属性和原来对象完全相同。对于非基本类型属性,克隆对象和源对象指向的是同一块内存空间
深克隆:创建一个新对象,属性中的引用的其他对象也会被克隆,不会指向原有对象地址。

引例1:克隆对象

具体原型对象

public class Relizetype implements Cloneable { // 必须实现Cloneable接口:否则调用clone()会抛出CloneNotSupportedException
    public Relizetype() {
        System.out.println("具体的原型类创建成功");
    }
    @Override
    public Relizetype clone() throws CloneNotSupportedException { // clone()方法是在Object类里的
        System.out.println("具体原型复制成功");
        return (Relizetype) super.clone();
    }
}

测试类

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建原型对象
        Relizetype relizetype = new Relizetype();
        // 调用原型类中的clone()方法进行对象的克隆
        Relizetype clone = relizetype.clone();
        System.out.println(relizetype == clone); // false
    }
}

必须实现Cloneable接口:否则调用clone()会抛出CloneNotSupportedException。
重写clone()方法:通常需将其改为public访问权限,并返回具体类型。
clone() 方法创建了一个新的对象,而不是返回原对象的引用,因为java的Object.clone()方法是一个native方法,不会调用构造方法,而是直接分配内存并复制数据

引例2.1:三好学生奖状分发(浅克隆)

奖状类:

@Data
public class Citation implements Cloneable {
    // 三好学生上的姓名
    private String name;

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

    public void show() {
        System.out.println(name + "同学被评为三好学生");
    }
}

测试类:

public class CitationTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 1. 创建原型对象
        Citation citation = new Citation();
        // 2. 克隆奖状对象
        Citation citation1 = citation.clone();
        citation.setName("张三");
        citation1.setName("李四");
        citation.show(); // 张三同学被评为三好学生
        citation1.show(); // 李四同学被评为三好学生
    }
}

使用场景

  1. 对象的创建非常复杂,可以使用原型模式快捷创建对象(原型对象的所属类必须实现clone()方法)
  2. 性能和安全的要求比较高

引例2.2:三好学生奖状分发(深克隆)

学生类:

@Data
@Accessors(chain = true)
public class Student {
    // 学生姓名
    private String name;
}

奖状类:

@Data
public class Citation implements Cloneable {
    private Student student;

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

    public void show() {
        System.out.println(student.getName() + "同学被评为三好学生");
    }
}

测试类:

public class CitationTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 1. 创建原型对象
        Citation citation = new Citation();
        Student stu = new Student().setName("张三");
        citation.setStudent(stu);
        // 2. 克隆奖状对象
        Citation citation1 = citation.clone();
        Student stu1 = citation1.getStudent();
        stu1.setName("李四");
        citation.show(); // 李四同学被评为三好学生
        citation1.show(); // 李四同学被评为三好学生
    }
}

问题】由上边测试结果可知,修改了citation1成员变量的值的同时,也修改了citation对象的值
产生原因】这是因为stu和stu1此时是一个对象(这就是浅克隆),此时将stu1的属性改成“李四”,导致stu的属性也变成“李四”

修改】:把浅克隆变成深克隆(使用序列化和反序列化实现)

public class CitationTest1 {
    public static void main(String[] args) throws Exception {
        // 1. 创建原型对象
        Citation citation = new Citation();
        Student stu = new Student().setName("张三");
        citation.setStudent(stu);

        // 把对象写入文件中
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt")); // 对象输出流对象
        oos.writeObject(citation); // 写对象
        oos.close(); // 释放资源

        // 从文件中读取对象 (2. 克隆奖状对象)
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
        Citation citation1 = (Citation) ois.readObject();
        citation1.getStudent().setName("李四");
        ois.close();

        citation.show(); // 张三同学被评为三好学生
        citation1.show(); // 李四同学被评为三好学生
    }
}

Citation类和Student类都必须实现Serializable接口,否则会抛NotSerializableException异常

修改】:把浅克隆变成深克隆(在重写clone()方法的时候调用属性的clone()方法)

奖状类:

@Data
public class Citation implements Cloneable, Serializable {
    private Student student;

    @Override
    public Citation clone() throws CloneNotSupportedException {
        Citation clone = (Citation) super.clone();
        clone.setStudent(student.clone()); // 深拷贝
        return clone;
    }

    public void show() {
        System.out.println(student.getName() + "同学被评为三好学生");
    }
}

学生类:

@Data
@Accessors(chain = true)
public class Student implements Serializable, Cloneable {
    // 学生姓名
    private String name;

    @Override
    public Student clone() throws CloneNotSupportedException {
        return (Student) super.clone();
    }
}

测试类:

public class CitationTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 1. 创建原型对象
        Citation citation = new Citation();
        Student stu = new Student().setName("张三");
        citation.setStudent(stu);
        // 2. 克隆奖状对象
        Citation citation1 = citation.clone();
        citation1.getStudent().setName("李四");
        citation.show(); // 张三同学被评为三好学生
        citation1.show(); // 李四同学被评为三好学生
    }
}

建造者模式

将复杂对象的构建和表示分离,使同样的构建过程可以创建不同的表示。

建造者建造的产品一般需要有较多的共同点,组成部分需要相似(如果产品之间差异比较大,不适合使用建造者模式)

  • 产品类(Product):要创建的复杂对象
  • 抽象建造者类(Builder):这个接口规定要实现复杂对象那部分的创建,不涉及具体的对象创建
  • 具体建造者类(ConcreteBuilder):实现Builder接口,完成复杂产品的各个部件的具体创建方法(强调装配的过程)
  • 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,不涉及具体产品信息,只保证对象各个部分完整创建或按照某种顺序创建
    在这里插入图片描述

引例:创建共享单车

需求】:自行车包含了车架、车座等组件的生产;车架又有碳纤维,铝合金等材质;车座有橡胶、真皮的材质
在这里插入图片描述

Bike类:产品类(车架、车座组件)
Builder:抽象建造者(MobikeBuilder、OfoBuilder是具体的建造者)
Director:指挥者

产品类:

@Data
public class Bike {
    /*车架*/
    private String frame;
    /*车座*/
    private String seat;
}

抽象构建者:

public abstract class Builder {
    /*Bike对象*/
    protected Bike bike = new Bike(); // 目前还没有组装组件(指挥者做)
    /*构建车架*/
    public abstract void buildFrame();
    /*构建车座*/
    public abstract void buildSeat();
    /*构建自行车*/
    public abstract Bike createBike();
}

具体构建者1(摩拜单车):

public class MobileBuilder extends Builder{
    @Override
    public void buildFrame() {
        bike.setFrame("碳纤维车架");
    }

    @Override
    public void buildSeat() {
        bike.setSeat("真皮车座");
    }

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

具体构建者2(ofo单车):

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

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

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

指挥者类:

public class Director {
    private Builder builder;
    public Director(Builder builder) {
        this.builder = builder;
    }
    /*组装自行车*/
    public Bike construct() {
        builder.buildFrame();
        builder.buildSeat();
        return builder.createBike();
    }
}

测试类:

public class Client {
    public static void main(String[] args) {
        // 1. 创建指挥者对象
        Director director = new Director(new MobileBuilder());
        // 2. 让指挥者进行自行车的组装
        Bike bike = director.construct();
        System.out.println(bike.getFrame());
        System.out.println(bike.getSeat());
    }
}

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中。

模式扩展

当一个类的构造方法需要传入很多参数,如果创建这个类的实例,代码的可读性就会很差,就可以使用建造者模式进行重构。
手机类:

@Data
public class Phone {
    private String cpu;
    private String screen;
    private String memory;
    private String 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;
        }
        /*使用构建者创建Phone对象*/
        public Phone build() {
            return new Phone(this);
        }
    }
}

测试类:

public class Client {
    public static void main(String[] args) {
        /*创建手机对象 - 通过构建者对象获取手机对象*/
        Phone phone = new Phone.Builder()
                .cpu("intel")
                .screen("三星")
                .memory("金士顿内存条")
                .mainboard("华硕")
                .build();
        System.out.println(phone); // Phone(cpu=intel, screen=三星, memory=金士顿内存条, mainboard=华硕)
    }
}

将构建的顺序交给客户,这个相当于lombok里的@Builder注解

构建者模式对比

工厂方法模式 vs 建造者模式

  1. 工厂方法模式:整体对象的创建方式
  2. 建造者模式:部件构建的过程

抽象工厂模式 vs 建造者模式

  1. 抽象工厂模式:实现对产品家族的创建,不需要关心建造过程,只关心什么产品由什么工厂生产
  2. 建造者模式:按照指定的蓝图建造产品,通过组装零件而产生一个新产品

抽象工厂模式:汽车配件生产工厂(生产一个产品族的产品)
建造者模式:骑车组装工厂(通过对配件的组装可以返回一个完整的骑车)

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

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

相关文章

南审计院考研分享会 经验总结

汪学长 – 中科大 计科专硕 初试准备 数学先做真题&#xff0c;模拟题刷的越多分越高&#xff1b;408真题最重要&#xff0c;模拟题辅助&#xff1b;英语只做真题&#xff1b;政治9月份开始背 代码能力在低年级培养的重要性和路径 考研不选择机构原因 因为机构里面学习的框…

牛客练习赛138(首篇万字题解???)

赛时成绩如下&#xff1a; 1. 小s的签到题 小s拿到了一个比赛榜单&#xff0c;他要用最快的速度找到签到题&#xff0c;但是小s脑子还是有点晕&#xff0c;请你帮帮小s&#xff0c;助力他找到签到题。 比赛榜单是一个 2 行 n 列的表格&#xff1a; 第一行是 n 个大写字母&#…

用git下载vcpkg时出现Connection was reset时的处理

用git安装vcpkg时出现Connect was rest&#xff08;如上图&#xff09;。多谢这位网友的博文解决了问题&#xff1a; 通过:http.sslVerify false全局来设置&#xff0c;执行以下命令&#xff1a; git config --global http.sslVerify "false" 原文链接&#xff1a…

leetcode - 滑动窗口问题集

目录 前言 题1 长度最小的子数组&#xff1a; 思考&#xff1a; 参考代码1&#xff1a; 参考代码2&#xff1a; 题2 无重复字符的最长子串&#xff1a; 思考&#xff1a; 参考代码1&#xff1a; 参考代码2&#xff1a; 题3 最大连续1的个数 III&#xff1a; 思考&am…

一分钟在Cherry Studio和VSCode集成火山引擎veimagex-mcp

MCP的出现打通了AI模型和外部数据库、网页API等资源&#xff0c;成倍提升工作效率。近期火山引擎团队推出了 MCP Server SDK&#xff1a; veimagex-mcp。本文介绍如何在Cherry Studio 和VSCode平台集成 veimagex-mcp。 什么是MCP MCP&#xff08;Model Context Protocol&…

Tomcat与纯 Java Socket 实现远程通信的区别

Servlet 容器​​&#xff08;如 Tomcat&#xff09; 是一个管理 Servlet 生命周期的运行环境&#xff0c;主要功能包括&#xff1a; ​​协议解析​​&#xff1a;自动处理 HTTP 请求/响应的底层协议&#xff08;如报文头解析、状态码生成&#xff09;&#xff1b; ​​线程…

为什么企业建站或独立站选用WordPress

与大多数组织相比&#xff0c;企业业务更需要保持可扩展和可靠的网络存在&#xff0c;以保持竞争力。为此&#xff0c;许多大型企业的 IT 领导者历来寻求昂贵的网络解决方案&#xff0c;这些方案需要签订专有支持合同来保证质量。不过&#xff0c;还有另一种方法。WordPress问世…

镜头内常见的马达类型(私人笔记)

① 螺杆式马达 驱动来源&#xff1a;机身内马达。镜头尾部有一个接收“螺杆”的接口&#xff0c;通过机械传动带动镜头对焦组。缺点&#xff1a;慢、吵、不能用于无机身马达的相机。✅ 典型镜头&#xff1a;尼康 AF、AF-D 系列&#xff1b;美能达老镜头。尼康传统的AF镜头通过…

从代码学习深度学习 - 语义分割和数据集 PyTorch版

文章目录 前言什么是语义分割?图像分割和实例分割Pascal VOC2012 语义分割数据集Pascal VOC2012 语义分割数据集介绍基本信息语义分割部分特点数据格式评价指标应用价值数据集获取使用提示辅助工具代码 (`utils_for_huitu.py`)读取数据预处理数据自定义语义分割数据集类读取数…

4G物联网模块实现废气处理全流程数据可视化监控配置

一、项目背景 随着工业化进程的加速&#xff0c;工业废气的排放对环境造成了严重影响&#xff0c;废气处理厂应运而生。然而&#xff0c;废气处理厂中的设备众多且分散&#xff0c;传统的人工巡检和数据记录方式效率低下&#xff0c;难以及时发现问题。为了实现对废气处理设备…

电商平台如何做好DDoS 攻防战?

一、新型 DDoS 攻击技术演进分析 1.1 电商平台面临的四类攻击范式 graph LR A[DDoS攻击] --> B{网络层} A --> C{应用层} B --> D[CLDAP反射攻击<br>峰值达3.5Tbps] B --> E[QUIC协议洪水攻击] C --> F[API CC攻击<br>精准打击抢购接口] C -->…

【计算机视觉】OpenCV实战项目:Athlete-Pose-Detection 运动员姿态检测系统:基于OpenCV的实时运动分析技术

运动员姿态检测系统&#xff1a;基于OpenCV的实时运动分析技术 1. 项目概述1.1 技术背景1.2 项目特点 2. 技术架构与算法原理2.1 系统架构2.2 核心算法2.3 模型选择 3. 项目部署与运行指南3.1 环境准备硬件要求软件依赖 3.2 项目配置3.3 运行项目基本运行模式高级参数 4. 常见问…

为什么要选择七彩喜数字康养平台?加盟后有何优势?

一&#xff0e;七彩喜数字康养平台 1.技术领先性 七彩喜依托“端-网-云-脑”四层技术架构&#xff0c;整合毫米波雷达、AI算法引擎、区块链等前沿技术&#xff0c;解决传统养老的隐私泄露、设备孤岛等痛点。 比如非接触式健康监测系统通过毫米波雷达实现跌倒检测准确率&#…

【计算机视觉】OpenCV实战项目:基于OpenCV的车牌识别系统深度解析

基于OpenCV的车牌识别系统深度解析 1. 项目概述2. 技术原理与算法设计2.1 图像预处理1) 自适应光照补偿2) 边缘增强 2.2 车牌定位1) 颜色空间筛选2) 形态学操作3) 轮廓分析 2.3 字符分割1) 投影分析2) 连通域筛选 2.4 字符识别 3. 实战部署指南3.1 环境配置3.2 项目代码解析 4.…

鸿蒙接入flutter环境变量配置windows-命令行或者手动配置-到项目的创建-运行demo项目

鸿蒙接入flutter环境变量配置 参考官网 下载flutter git clone https://gitcode.com/openharmony-sig/flutter_flutter.git git checkout -b dev origin/dev # 国内镜像 export PUB_HOSTED_URLhttps://pub.flutter-io.cn export FLUTTER_STORAGE_BASE_URLhttps://storage.fl…

Flink CDC—实时数据集成框架

Flink CDC 是一个基于流的数据集成工具&#xff0c;旨在为用户提供一套功能更加全面的编程接口&#xff08;API&#xff09;&#xff0c;它基于数据库日志的 CDC&#xff08;变更数据捕获&#xff09;技术实现了统一的增量和全量数据读取。 该工具使得用户能够以 YAML 配置文件…

微调ModernBERT为大型语言模型打造高效“过滤器”

ModernBERT&#xff08;2024 年 12 月&#xff09;是最近发布的小型语言模型&#xff0c;由 Answer.AI、LightOn 和 HuggingFace 共同开发。它利用了现代优化技术&#xff0c;如用于 8,192 token 上下文窗口的 RoPE 和 GeGLU layers&#xff0c;在保持效率的同时提升性能。jina…

各大编程语言基本语法区别

1:语言特点 函数式语言和面向对象语言的区别:函数式用函数直接进行操作,面向对象用object.method()进行操作;如:len() <=> object.length() C 语言:1)C 语言可以像汇编语言一样对位、字节和地址进行操作;2)有函数原型;3)具有大量的数值类型;4)函数是C语言…

云计算中的虚拟化:成本节省、可扩展性与灾难恢复的完美结合

云计算中虚拟化的 4 大优势 1. 成本效益 从本质上讲&#xff0c;虚拟化最大限度地减少了硬件蔓延。团队可以将多个虚拟机整合到单个物理主机上&#xff0c;而不是为每个工作负载部署单独的服务器。这大大减少了前期硬件投资和持续维护。 结果如何&#xff1f;更低的功耗、更低…

【Java ee初阶】网络原理

TCP协议 1.确认应答 实现可靠传输的核心机制 2.超时重传 实现可靠传输的核心机制 3.连接管理 网络部分最高频的面试题 4.滑动窗口 提高传输效率的机制 5.流量控制 依据接收方的处理能力&#xff0c;限制发送方的发送速度。 6.拥塞控制 依据传输链路的处理能力&#xff0c…