23种设计模式漫画版系列—原型模式

简介: 23种设计模式漫画版系列—原型模式

01意图

原型模式亦称: 克隆、Clone、Prototype

原型模式是一种创建型设计模式 使你能够复制已有对象 而又无需使代码依赖它们所属的类


02问题

如果你有一个对象 并希望生成与其完全相同的一个复制品 你该如何实现呢 首先 你必须新建一个属于相同类的对象 然后 你必须遍历原始对象的所有成员变量 并将成员变量值复制到新对象中

不错 但有个小问题 并非所有对象都能通过这种方式进行复制 因为有些对象可能拥有私有成员变量 它们在对象本身以外是不可见的

从外部 复制对象并非总是可行


直接复制还有另外一个问题因为你必须知道对象所属的类才能创建复制品所以代码必须依赖该类即使你可以接受额外的依赖性那还有另外一个问题有时你只知道对象所实现的接口而不知道其所属的具体类比如可向方法的某个参数传入实现了某个接口的任何对象


03解决方案

原型模式将克隆过程委派给被克隆的实际对象模式为所有支持克隆的对象声明了一个通用接口该接口让你能够克隆对象同时又无需将代码和对象所属类耦合通常情况下这样的接口中仅包含一个克隆方法


所有的类对克隆方法的实现都非常相似该方法会创建一个当前类的对象然后将原始对象所有的成员变量值复制到新建的类中你甚至可以复制私有成员变量因为绝大部分编程语言都允许对象访问其同类对象的私有成员变量


支持克隆的对象即为原型当你的对象有几十个成员变量和几百种类型时对其进行克隆甚至可以代替子类的构造

预生成原型可以代替子类的构造

其运作方式如下创建一系列不同类型的对象并不同的方式对其进行配置如果所需对象与预先配置的对象相同那么你只需克隆原型即可无需新建一个对象

04真实世界类比

现实生活中 产品在得到大规模生产前会使用原型进行各种测试 但在这种情况下 原型只是一种被动的工具 不参与任何真正的生产活动


一个细胞的分裂

由于工业原型并不是真正意义上的自我复制 因此细胞有丝分裂 还记得生物学知识吗 或许是更恰当的类比 有丝分裂会产生一对完全相同的细胞 原始细胞就是一个原型 它在复制体的生成过程中起到了推动作用

05原型模式结构

基本实现



原型注册表实现

06伪代码

在本例中原型模式能让你生成完全相同的几何对象副本 同时无需代码与对象所属类耦合

克隆一系列位于同一类层次结构中的对象

所有形状类都遵循同一个提供克隆方法的接口 在复制自身成员变量值到结果对象前 子类可调用其父类的克隆方法

// 基础原型。
abstract class Shape is
    field X: int
    field Y: int
    field color: string
    // 常规构造函数。
    constructor Shape() is
        // ...
    // 原型构造函数。使用已有对象的数值来初始化一个新对象。
    constructor Shape(source: Shape) is
        this()
        this.X = source.X
        this.Y = source.Y
        this.color = source.color
    // clone(克隆)操作会返回一个形状子类。
    abstract method clone():Shape
// 具体原型。克隆方法会创建一个新对象并将其传递给构造函数。直到构造函数运
// 行完成前,它都拥有指向新克隆对象的引用。因此,任何人都无法访问未完全生
// 成的克隆对象。这可以保持克隆结果的一致。
class Rectangle extends Shape is
    field width: int
    field height: int
    constructor Rectangle(source: Rectangle) is
        // 需要调用父构造函数来复制父类中定义的私有成员变量。
        super(source)
        this.width = source.width
        this.height = source.height
    method clone():Shape is
        return new Rectangle(this)
class Circle extends Shape is
    field radius: int
    constructor Circle(source: Circle) is
        super(source)
        this.radius = source.radius
    method clone():Shape is
        return new Circle(this)
// 客户端代码中的某个位置。
class Application is
    field shapes: array of Shape
    constructor Application() is
        Circle circle = new Circle()
        circle.X = 10
        circle.Y = 10
        circle.radius = 20
        shapes.add(circle)
        Circle anotherCircle = circle.clone()
        shapes.add(anotherCircle)
        // 变量 `anotherCircle(另一个圆)`与 `circle(圆)`对象的内
        // 容完全一样。
        Rectangle rectangle = new Rectangle()
        rectangle.width = 10
        rectangle.height = 20
        shapes.add(rectangle)
    method businessLogic() is
        // 原型是很强大的东西,因为它能在不知晓对象类型的情况下生成一个与
        // 其完全相同的复制品。
        Array shapesCopy = new Array of Shapes.
        // 例如,我们不知晓形状数组中元素的具体类型,只知道它们都是形状。
        // 但在多态机制的帮助下,当我们在某个形状上调用 `clone(克隆)`
        // 方法时,程序会检查其所属的类并调用其中所定义的克隆方法。这样,
        // 我们将获得一个正确的复制品,而不是一组简单的形状对象。
        foreach (s in shapes) do
            shapesCopy.add(s.clone())
        // `shapesCopy(形状副本)`数组中包含 `shape(形状)`数组所有
        // 子元素的复制品。


07原型模式适合应用场景

如果你需要复制一些对象 同时又希望代码独立于这些对象所属的具体类 可以使用原型模式

这一点考量通常出现在代码需要处理第三方代码通过接口传递过来的对象时 即使不考虑代码耦合的情况 你的代码也不能依赖这些对象所属的具体类 因为你不知道它们的具体信息

原型模式为客户端代码提供一个通用接口 客户端代码可通过这一接口与所有实现了克隆的对象进行交互 它也使得客户端代码与其所克隆的对象具体类独立开来

如果子类的区别仅在于其对象的初始化方式 那么你可以使用该模式来减少子类的数量 别人创建这些子类的目的可能是为了创建特定类型的对象

在原型模式中 你可以使用一系列预生成的 各种类型的对象作为原型

客户端不必根据需求对子类进行实例化 只需找到合适的原型并对其进行克隆即可

08实现方式

  1. 创建原型接口 并在其中声明 克隆方法 如果你已有类层次结构 则只需在其所有类中添加该方法即可

  2. 原型类必须另行定义一个以该类对象为参数的构造函数 构造函数必须复制参数对象中的所有成员变量值到新建实体中 如果你需要修改子类 则必须调用父类构造函数 让父类复制其私有成员变量值

如果编程语言不支持方法重载 那么你可能需要定义一个特殊方法来复制对象数据 在构造函数中进行此类处理比较方便 因为它在调用 new运算符后会马上返回结果对象

  3. 克隆方法通常只有一行代码 使用 new运算符调用原型版本的构造函数 注意 每个类都必须显式重写克隆方法并使用自身类名调用 new运算符 否则 克隆方法可能会生成父类的对象

  4. 你还可以创建一个中心化原型注册表 用于存储常用原型

你可以新建一个工厂类来实现注册表 或者在原型基类中添加一个获取原型的静态方法 该方法必须能够根据客户端代码设定的条件进行搜索 搜索条件可以是简单的字符串 或者是一组复杂的搜索参数 找到合适的原型后 注册表应对原型进行克隆 并将复制生成的对象返回给客户端

最后还要将对子类构造函数的直接调用替换为对原型注册表工厂方法的调用

9原型模式优缺点

  • 你可以克隆对象 而无需与它们所属的具体类相耦合
  • 你可以克隆预生成原型 避免反复运行初始化代码
  • 你可以更方便地生成复杂对象
  • 你可以用继承以外的方式来处理复杂对象的不同配置
  • 克隆包含循环引用的复杂对象可能会非常麻烦

10Python、Go、Java 代码示例

详见次条文章


11推荐UML实用工具

亿图图示

关注公众号:全栈芬达,回复亿图图示,获取激活版。

初学者秒会的专业级UML图绘制软件。无需掌握复杂操作,可以零基础轻松绘制280+种绘图类型


Visio


12 UML  类图关系

UML类图非常简单,可以用下面的图表示一个类:


该图表示一个叫做Person的类,该类有name、age、sex三个private属性,每个属性的类型紧跟在冒号的后面。该类有walk和speak两个方法,其中walk方法是public的,而speak方法是protected的,两个方法的返回值类型紧跟在冒号的后面。

+:公有属性,其它类可以访问该属性

-:私有属性,不能被其它类访问(默认为私有)

\#:保护属性,只能被本类及其派生类访问

~:包内可见,可以被本包中的其它类访问

如果要表示一个接口,则用下面的图表示:

下面介绍类与类之间的关系。如果按照关系的紧密程度从弱到强划分,类与类之间的关系包括:

  • 依赖
  • 关联
  • 聚合
  • 组合
  • 实现
  • 继承


依赖关系

依赖关系是所有类间关系中最弱的一种,它用下面的图表示:


图中的箭头方向表示依赖的方向,上图表示类A依赖类B。

依赖,顾名思义表示一个实体的存在必须依赖另一个实体的存在。可以这样认为,如果类A依赖类B,那么类A只有在类B存在的情况下,才能编译通过。

下面代码是依赖的一个例子:

public class UserController {

private UserService userService;


public User query(Strint userId) {

User user = userService.queryUser(userId);


return user;


}


}

在这段代码,UserController类同时依赖于UserService和User两个类,可以用下面的类图表示它们的依赖关系:


可见依赖关系大量的存在于我们的代码中,但千万不要在项目设计时将全部的依赖关系都画出来,这不仅很累,而且也没有必要。当梳理依赖关系时,先要搞清楚你关注什么,想表达什么,只画出真正需要画的就可以。


关联关系

关联关系表示两个实体间存在一定的联系,这种联系比依赖关系更紧密,不仅仅只是“两个实体触碰到”这样松散的关系。例如Student和School这两个类,一个学生一定会有一个对应的学校,那么Student和School间就存在关联关系,且它们的关系是一对多的。


用下面的UML图表示:



关联关系也可以用于领域建模,例如要设计一个骰子游戏,游戏者连续投掷两次筛子,如果两次点数的总数是7,则游戏者赢,否则游戏者输。可以用下面UML图对这个问题进行领域建模,各实体间使用的就是关联关系。这也是关联关系的一种特殊用法。

聚合&组合

聚合也是一种关联关系,但是这种关联关系存在整体与部分的语义。例如大雁和大雁群,一只大雁是整个大雁群的一部分。这就是一种聚合关系,具有has-a的语义。下面的UML图用来描述聚合关系。

组合是一种强聚合关系,它表示整体和部分之间具有相同的生命周期,同生共死。例如鸟和翅膀,鸟如果死掉了,那么它的翅膀也会跟着死掉。组合关系具有contains-a的语义。下面的UML图用于表达组合关系。


记忆聚合和组合UML图画法的小技巧:菱形就相当于一个容器,容器指向的实体就是整体,所以上面图中的菱形分别指向大雁群和鸟。此外,由于组合关系的紧密程度比聚合关系更强,所以组合关系用实心菱形,聚合关系用空心菱形。


继承&实现

继承和实现都是Java中的基础,比较容易理解,它们是类与类之间关系最强的。分别用下面的UML图表示。

继承示例:



实现示例:


PS:实现关系应该用空心箭头。

相关文章
|
6月前
|
设计模式 安全 Java
面向对象编程的精髓:Java设计模式 - 原型模式(Prototype)完全参考手册
【4月更文挑战第7天】原型模式是OOP中的创建型设计模式,用于通过复制现有实例创建新实例,尤其适用于创建成本高或依赖其他对象的情况。它包括Prototype接口、ConcretePrototype实现和Client客户端角色。优点是性能优化、避免子类化和动态增加产品族。实现包括定义原型接口、实现具体原型和客户端调用克隆方法。最佳实践涉及确保克隆正确性、选择深拷贝或浅拷贝及考虑线程安全。但需注意克隆方法管理、性能开销和循环引用等问题。在Java中,实现Cloneable接口和覆盖clone方法可实现原型模式。
77 4
|
6月前
|
设计模式 Java 关系型数据库
23种设计模式 —— 原型模式【克隆羊、浅拷贝、深拷贝】
23种设计模式 —— 原型模式【克隆羊、浅拷贝、深拷贝】
|
6月前
|
设计模式 安全 Java
【设计模式】原型模式
【设计模式】原型模式
|
2月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑】设计模式——原型模式
对比原型模式和传统方式的实现思路、代码方案、优缺点,阐述原型模式的使用场景,以及深拷贝、浅拷贝等相关概念,并扩展原型模式在Spring源码中的应用。
【Java笔记+踩坑】设计模式——原型模式
|
2月前
|
设计模式 Java
Java设计模式-原型模式(3)
Java设计模式-原型模式(3)
Java设计模式-原型模式(3)
|
4月前
|
设计模式
iLogtail设计模式问题之iLogtail中的原型模式是什么
iLogtail设计模式问题之iLogtail中的原型模式是什么
iLogtail设计模式问题之iLogtail中的原型模式是什么
|
4月前
|
设计模式 JavaScript
js设计模式【详解】—— 原型模式
js设计模式【详解】—— 原型模式
51 6
|
5月前
|
设计模式 Java
Java设计模式之原型模式详解
Java设计模式之原型模式详解
|
5月前
|
设计模式
原型模式-大话设计模式
原型模式-大话设计模式
|
5月前
|
设计模式 Java Spring
设计模式——原型模式
设计模式——原型模式

热门文章

最新文章

  • 1
    C++一分钟之-设计模式:工厂模式与抽象工厂
    43
  • 2
    《手把手教你》系列基础篇(九十四)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-下篇(详解教程)
    50
  • 3
    C++一分钟之-C++中的设计模式:单例模式
    58
  • 4
    《手把手教你》系列基础篇(九十三)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-上篇(详解教程)
    38
  • 5
    《手把手教你》系列基础篇(九十二)-java+ selenium自动化测试-框架设计基础-POM设计模式简介(详解教程)
    64
  • 6
    Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
    59
  • 7
    Java面试题:设计模式在并发编程中的创新应用,Java内存管理与多线程工具类的综合应用,Java并发工具包与并发框架的创新应用
    42
  • 8
    Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
    50
  • 9
    Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
    112
  • 10
    Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
    78