(四)趣学设计模式 之 原型模式!

news/2025/2/24 18:33:52

在这里插入图片描述

目录


🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支持一下,感谢🤗!
🌟了解抽象工厂模式请看: (三)趣学设计模式 之 抽象工厂模式!

这篇文章带你详细认识一下设计模式中的原型模式

一、 啥是原型模式

原型模式,说白了,就是“山寨”! 🤣 你有一个宝贝,不想自己辛辛苦苦再做一个,就找个复印机(克隆方法),咔嚓一下,复制一个一模一样的出来!

  • 对象的创建成本很高: 就像造火箭 🚀,太费劲了,不如复制一个!
  • 需要创建大量相似对象: 就像克隆羊 🐑,要一大堆一样的羊,一个个生太慢了!
  • 希望隐藏对象的创建细节: 就像做菜 🍳,你只想吃,不想知道怎么做的!

二、 为什么要用原型模式

原型模式,好处多多:

  • 省钱省力: 不用重复造轮子 🚗,直接复制,省时省力!
  • 灵活多变: 想复制啥就复制啥,不用提前定好,很灵活!
  • 简单易懂: 不用管对象怎么创建的,直接复制就行,代码更简单!
  • 避免麻烦: 不用创建一堆乱七八糟的子类,省心!

三、 原型模式怎么实现?

原型模式主要有两种“山寨”方法:

  • 浅拷贝(Shallow Copy)
  • 深拷贝(Deep Copy)

实现原型模式的关键点

  • Cloneable 接口: 就像一个“允许复制”的标签 🏷️,贴上这个标签,才能被复印!
  • Object 类的 clone() 方法: 就像复印机的基本功能,只能复印表面信息(浅拷贝)!
  • 重写 clone() 方法: 就像升级复印机,让它可以复印所有信息(深拷贝)!

1.浅拷贝(Shallow Copy):只复制表面! 🤏

浅拷贝就像复印身份证 🪪,你复印了一张身份证,上面的名字、地址都一样,但是里面的芯片还是原来的那个。如果原来的身份证信息变了,复印件也会跟着变!

// 1. 定义原型接口 (Cloneable 是 Java 内置的接口)
interface Prototype extends Cloneable {
    Prototype clone(); // 克隆方法
}

// 2. 定义具体原型类
class Sheep implements Prototype {
    private String name;
    private int age;
    private Address address; // 引用类型属性

    public Sheep(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(String city) {
        this.address.setCity(city);
    }

    @Override
    public Sheep clone() {
        try {
            // 浅拷贝:直接调用 Object 类的 clone() 方法
            return (Sheep) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println("克隆失败! 😭");
            return null;
        }
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address=" + address +
                '}';
    }
}

// 地址类 (引用类型)
class Address {
    private String city;

    public Address(String city) {
        this.city = city;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Address{" +
                "city='" + city + '\'' +
                '}';
    }
}

// 3. 客户端使用
public class Client {
    public static void main(String[] args) {
        // 创建原型对象
        Address address = new Address("北京");
        Sheep originalSheep = new Sheep("多莉", 3, address);

        // 克隆原型对象
        Sheep clonedSheep = originalSheep.clone();

        // 打印原始对象和克隆对象
        System.out.println("原始羊: " + originalSheep);
        System.out.println("克隆羊: " + clonedSheep);

        // 修改原始对象的引用类型属性
        originalSheep.setAddress("上海");

        // 再次打印原始对象和克隆对象
        System.out.println("修改后的原始羊: " + originalSheep);
        System.out.println("修改后的克隆羊: " + clonedSheep);
    }
}

代码解释:

  • Prototype:原型接口,定义了克隆方法。
  • Sheep:具体原型类,实现了 Prototype 接口,表示羊。
  • clone():克隆方法,用于创建对象的副本。
  • Address:地址类,作为 Sheep 类的引用类型属性。

输出结果:

原始羊: Sheep{name='多莉', age=3, address=Address{city='北京'}}
克隆羊: Sheep{name='多莉', age=3, address=Address{city='北京'}}
修改后的原始羊: Sheep{name='多莉', age=3, address=Address{city='上海'}}
修改后的克隆羊: Sheep{name='多莉', age=3, address=Address{city='上海'}}

分析:

可以看到,修改原始羊的地址后,克隆羊的地址也跟着改变了!这是因为浅拷贝只复制了地址的“指针”,原始羊和克隆羊指向的是同一个地址,所以一个变了,另一个也跟着变!

2. 深拷贝(Deep Copy):彻底复制! 💯

深拷贝就像克隆一只完整的羊 🐑,你克隆了一只羊,它有自己的名字、年龄和地址,和原来的羊完全没有关系。即使原来的羊死了,克隆羊还是活蹦乱跳的!

import java.io.*;

class Sheep implements Prototype, Serializable { // 注意实现 Serializable 接口
    private String name;
    private int age;
    private Address address;

    public Sheep(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public Sheep clone() {
        try {
            // 使用序列化和反序列化实现深拷贝
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (Sheep) ois.readObject();

        } catch (Exception e) {
            System.out.println("克隆失败! 😭");
            return null;
        }
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address=" + address +
                '}';
    }
}

class Address implements Serializable { // 注意实现 Serializable 接口
    private String city;

    public Address(String city) {
        this.city = city;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Address{" +
                "city='" + city + '\'' +
                '}';
    }
}

public class Client {
    public static void main(String[] args) {
        // 创建原型对象
        Address address = new Address("北京");
        Sheep originalSheep = new Sheep("多莉", 3, address);

        // 克隆原型对象
        Sheep clonedSheep = originalSheep.clone();

        // 打印原始对象和克隆对象
        System.out.println("原始羊: " + originalSheep);
        System.out.println("克隆羊: " + clonedSheep);

        // 修改原始对象的引用类型属性
        address.setCity("上海");

        // 再次打印原始对象和克隆对象
        System.out.println("修改后的原始羊: " + originalSheep);
        System.out.println("修改后的克隆羊: " + clonedSheep);
    }
}

注意: 使用序列化和反序列化实现深拷贝,需要确保对象及其所有属性都实现了 Serializable 接口。

输出结果:

原始羊: Sheep{name='多莉', age=3, address=Address{city='北京'}}
克隆羊: Sheep{name='多莉', age=3, address=Address{city='北京'}}
修改后的原始羊: Sheep{name='多莉', age=3, address=Address{city='上海'}}
修改后的克隆羊: Sheep{name='多莉', age=3, address=Address{city='北京'}}

分析:

可以看到,修改原始羊的地址后,克隆羊的地址没有改变!这是因为深拷贝复制了地址的所有信息,原始羊和克隆羊拥有不同的地址,所以一个变了,另一个不会受到影响!

四、 原型模式的应用场景

  • 游戏开发: 游戏里的怪物 👾,不用一个个设计,复制几个改改属性就行!
  • 文档编辑器: 文档的模板 📄,复制一份改改,就能生成新的文档!
  • 图形编辑器: 图形对象 🖼️,复制一份改改颜色、大小,就能得到新的图形!
  • 配置管理: 软件的配置 ⚙️,复制一份改改,就能适应不同的环境!
  • 报表生成: 报表的模板 📊,复制一份改改数据,就能生成不同的报表!

五、 原型模式的优点和缺点

优点:

  • 省钱省力: 不用重新创建对象,直接复制,就像复制粘贴一样简单,大大提高了效率!
  • 隐藏秘密: 客户端不用知道对象是怎么创建的,只需要知道怎么复制就行,保护了对象的内部结构!
  • 灵活应变: 可以在运行时动态地复制对象,想复制啥就复制啥,非常灵活!
  • 减少麻烦: 不用创建一堆子类,避免了代码的膨胀,让代码更简洁!

缺点:

  • 复制很麻烦: 特别是深拷贝,要复制对象的所有信息,就像搬家一样,很麻烦!
  • 破坏隐私: 为了复制对象,可能需要访问对象的私有属性,就像偷看别人的日记一样,破坏了对象的封装性!
  • 需要贴标签: 在 Java 中,需要实现 Cloneable 接口才能被复制,就像需要贴上“允许复制”的标签,有点麻烦!
  • 可能出错: 有时候复制操作可能不会执行构造函数,就像克隆羊没有灵魂一样,可能会导致对象的状态不正确!

六、 总结

  • 原型模式就是“山寨”大法,通过复制现有对象来创建新对象!
  • 适用于需要大量相似对象、对象创建成本高、需要隐藏对象创建细节的场景!
  • 有两种“山寨”方法:浅拷贝和深拷贝!
  • 浅拷贝只复制表面信息,深拷贝复制所有信息!
  • 要根据实际情况选择合适的“山寨”方法!
  • 使用原型模式需要注意一些问题,比如破坏封装性、可能出错等等!

希望这篇文章能让你彻底理解原型模式! 👍
看完请看:(五)趣学设计模式 之 建造者模式!


http://www.niftyadmin.cn/n/5864703.html

相关文章

【单片机毕业设计14-基于stm32c8t6的智能宠物养护舱系统设计】

【单片机毕业设计14-基于stm32c8t6的智能宠物养护舱系统设计】 前言一、功能介绍二、硬件部分三、软件部分总结 前言 🔥这里是小殷学长,单片机毕业设计篇14-基于stm32c8t6的智能宠物养护舱系统设计 🧿创作不易,拒绝白嫖可私 一、功…

vue怎么设置允许局域网手机访问

打开vite.config.ts 添加 server: {host: 0.0.0.0}, host: 0.0.0.0:设置为0.0.0.0,允许从所有IP访问。port: 5173:指定端口号,可以根据需要进行修改。不指定默认 5173disableHostCheck: true:禁用主机检查&#xff0c…

C#贪心算法

贪心算法:生活与代码中的 “最优选择大师” 在生活里,我们常常面临各种选择,都希望能做出最有利的决策。比如在超市大促销时,面对琳琅满目的商品,你总想用有限的预算买到价值最高的东西。贪心算法,就像是一…

Android KMP初探

Android KMP初探 前言: 最近线上听了Kotlin官网举行的KMP会议,感觉听神奇的,于是就把官方demo下载下来尝试了一下,下载插件和所需要的依赖都用了很久,但是发现里面的代码很少,于是尝试自己手写了一下&…

设计模式-adapter模式(适配器)

解释 适配器模式(Adapter Pattern)用于将一个类的接口转换成客户端所期望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。该模式属于结构型设计模式。 应用场景 场景1:旧系统与新系统的整合 当你有…

从人机环境系统智能角度看传统IP的全球化二次创作法则

从人机环境系统智能的视角看,传统IP的全球化二次创作法则需结合技术、文化、伦理与环境的复杂协同。这一过程不仅是内容的本土化改编,更是人、机器与环境在动态交互中实现价值共创的体现。 一、人机环境系统智能的底层逻辑与IP二次创作的融合 1、感知层&…

Go 语言中的协程

概念 Go语言中的协程(Goroutine)是一种由Go运行时管理的轻量级线程。它是Go语言并发模型的核心,旨在通过简单、易用的方式支持高并发的程序设计。 创建协程 协程的创建非常简单,只需要使用go关键字,后面跟着一个函数…

深度学习-4.优化与正则化

Deep Learning - Lecture 4 Optimization and Regularization 优化(Optimization)随机梯度下降(Stochastic gradient descent)带动量的随机梯度下降法(Stochastic gradient descent (with momentum))自适应梯度方法(Ad…