简介

是一种创建型设计模式,它用于创建对象的新实例,同时尽量减少了与创建对象的复杂性。原型模式是通过复制(克隆)现有对象来创建新对象的一种方式,这可以在不依赖具体类的情况下创建新对象,同时减少了构造过程的开销。

原型模式的核心思想是基于一个现有对象创建新对象,而这个现有对象被称为原型(Prototype)。原型模式通过克隆原型对象来创建新对象,而不是使用构造函数。

主要组成部分:

原型接口(Prototype Interface):定义了一个克隆方法,用于复制原型对象以创建新对象。

具体原型类(Concrete Prototype Class):实现了原型接口,它可以克隆自身,通常包括一个克隆方法来实现对象的复制。

客户端(Client):使用原型对象和克隆方法来创建新对象。

<注:>:
但对于Java而言,那个原型抽象类Prototype
是用不着的,因为克隆实在是太常用了,所以Java提供了Cloneable接口,其
中就是唯一的一个方法clone(),这样就只需要实现这个接口就可以完成
原型模式了

工作流程

- 客户端通过原型接口访问原型对象,并请求复制原型对象。

- 原型对象复制自身,生成一个新的对象实例。

- 客户端得到新的对象实例,不需要关心创建的细节。

例子

结构

代码

具体原型类

/**
 * @author: lpy
 * @Date: 2023/10/18
 */
@Data
public class Resume implements Cloneable{
    public String name;
    public Integer age;
    public String address;
    public String company;
    public String birthDay;

    /** 项目经历,深复制测试*/
    private ProjectExperience pe;

    public void setPe(String projectTitle, String circle){
        this.pe.setCircle(circle);
        this.pe.setProjectTitle(projectTitle);
    }

    public Resume(String name,int age){
        this.name =name;
        this.age = age;
        this.pe = new ProjectExperience();
    }


    public void display() {
        System.out.println("Resume{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                ", company='" + company + '\'' +
                ", birthDay='" + birthDay + '\'' +
                ", projectTitle='"+this.pe.getProjectTitle()+'\''+
                ", circle='"+this.pe.getCircle()+'\''+
                '}');
    }

    public void setPersonalInfo(String address, String company, String birthDay){
        this.address=address;
        this.company=company;
        this.birthDay=birthDay;
    }

    @Override
    public Resume clone(){
        Resume re = null;
        try {
            re = (Resume)super.clone();
        }catch (CloneNotSupportedException ex){
            System.out.println("clone失败"+ex.getMessage());
        }
        return  re;
    }
}

/**
 * @author: lpy
 * @Date: 2023/10/19
 */
@AllArgsConstructor
@Data
@NoArgsConstructor
public class ProjectExperience implements Cloneable{
    private String projectTitle;
    private String circle;
}

测试类

而String是一种拥有值类型特点的特殊引用类型,super.clone()方法是这样,如果字段是值类型的,则对该字段执行逐位复制,如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其副本引用同一对象。

/**
 * @author: lpy
 * @Date: 2023/10/19
 */
public class PrototypeTest {
    public static void main(String[] args) {
        /**
         * String是一种拥有值类型特点的特殊引用类
         * 型,super.clone()方法是这样,如果字段是值类型的,则对该字段执行
         * 逐位复制,如果字段是引用类型,则复制引用但不复制引用的对象;因此,
         * 原始对象及其副本引用同一对象。
         */
//        Resume xs = new Resume("小菜", 24);
//        xs.setPersonalInfo("洛杉矶","google","2000-9");
//
//        Resume xs1 = xs.clone();
//        xs1.setName("xiaoc ");
//        xs.display();
//        xs1.display();

        Resume xs = new Resume("小菜", 24);
        xs.setPersonalInfo("洛杉矶","google","2000-9");
        xs.setPe("接口平台","11");

        Resume xs1 = xs.clone();
        xs1.setName("xiaoc ");
        xs1.setPe("pm","13");
        /**
         * 由于浅复制的问题导致后面的数据覆盖了前面的数据
         * Resume{name='小菜', age=24, address='洛杉矶', company='google', birthDay='2000-9', projectTitle='pm', circle='13'}
         * Resume{name='xiaoc ', age=24, address='洛杉矶', company='google', birthDay='2000-9', projectTitle='pm', circle='13'}
         */
        xs.display();
        xs1.display();
    }
}

问题解决

上面的代码是存在问题的。

        /**
         * 由于浅复制的问题导致后面的数据覆盖了前面的数据
         * Resume{name='小菜', age=24, address='洛杉矶', company='google', birthDay='2000-9', projectTitle='pm', circle='13'}
         * Resume{name='xiaoc ', age=24, address='洛杉矶', company='google', birthDay='2000-9', projectTitle='pm', circle='13'}
         */

@AllArgsConstructor
@Data
@NoArgsConstructor
public class ProjectExperience implements Cloneable{
    private String projectTitle;
    private String circle;

    @Override
    public ProjectExperience clone(){
        ProjectExperience pe = null;
        try {
            pe = (ProjectExperience)super.clone();
        }catch (CloneNotSupportedException ex){
            System.out.println("clone失败"+ex.getMessage());
        }
        return  pe;
    }
}

@Data
public class Resume implements Cloneable{
    // ......省略部分代码
    @Override
    public Resume clone(){
        Resume re = null;
        try {
            re = (Resume)super.clone();
            // 解决浅拷贝问题,这里只复制了一层,如果涉及到多层的话需要再考虑,可能会有循环引用的问题。
            re.pe = this.pe.clone();
        }catch (CloneNotSupportedException ex){
            System.out.println("clone失败"+ex.getMessage());
        }
        return  re;
    }
}

应用

- ① 节省资源 ( 内存 CPU 硬件等 ) , ② 构造函数复杂 ( 计算繁琐 耗时 ) , ③ 创建大量对象 ;

- 动态配置对象:原型模式可用于根据运行时需求配置对象的不同属性,而不需要手动设置每个属性。

- 保护对象状态:在某些情况下,您可能希望确保对象的状态不会被修改,通过克隆对象,您可以保护原始对象的状态。

原型模式优缺点

  • 优点:
    • 使用原型模式创建对象比直接new一个对象更有效,因为他是直接对内存进行拷贝,数据对象的内容就全部都有了,不需要重新创建内存区域重新实例化赋值这些操作了,这样的话速度能够快很多。

    • 隐藏制造新实例的复杂性

    • 重复地创建相似对象时可以考虑使用原型模式

  • 缺点:

    • 每一个类必须配备一个克隆方法

    • 深层复制比较复杂

    • 深复制要深入到多少层,需要事先就考虑好,而且要当心出现循环引用的问题,需要小心处理

7 大设计原则

不能全部遵守 , 也不能不遵守 , 注意平衡 功能 和 系统复杂度 , 找到最合适的一个点 ;

- 单一职责原则(Single Responsibility Principle - SRP):
这个原则规定一个类应该只有一个改变的理由,也就是说,一个类应该只有一个单一的职责。这有助于保持类的简单性和可维护性,因为每个类只需关注一个特定的功能。

- 开放-封闭原则(Open-Closed Principle - OCP):
开放-封闭原则要求系统中的软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着当需要添加新功能时,应该通过扩展现有代码来实现,而不是修改原有代码。

- 里氏替换原则(Liskov Substitution Principle - LSP):
里氏替换原则规定,子类应该能够替换其父类,而不会影响程序的正确性。也就是说,子类应该继承父类的行为,但可以扩展或修改该行为,但不应该改变父类的行为。

- 接口隔离原则(Interface Segregation Principle - ISP):
接口隔离原则要求一个类不应该强迫其客户端依赖于它们不需要的接口。应该根据客户端的需求定义小而精确的接口,而不是大而笨重的接口。

- 依赖倒置原则(Dependency Inversion Principle - DIP):
依赖倒置原则要求高级模块不应该依赖于低级模块,它们都应该依赖于抽象。具体来说,这意味着应该通过抽象接口或抽象类来定义模块之间的依赖关系,而不是直接依赖于具体实现。

- 迪米特法则(Law of Demeter - LoD):
迪米特法则也被称为最少知识原则,它要求一个对象应该只与其密切的朋友交互,而不应该暴露太多的信息给外部对象。这有助于减少对象之间的耦合度,提高系统的灵活性。

- 合成复用原则(Composite Reuse Principle - CRP):
合成复用原则鼓励通过组合(合成)已有的类来实现新功能,而不是通过继承现有的类。这样可以降低系统的耦合度,并且更加灵活。

源码位置

全部源码github.com/xs-alpha/designPattern

如有内容侵权,麻烦联系博主删除

参考文献

浙公网安备33011302000604

辽ICP备20003309号