learn.lianglianglee.com/专栏/白话设计模式 28 讲(完)/13 克隆模式:给你一个分身术.md.html
2022-05-11 19:04:14 +08:00

782 lines
23 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>13 克隆模式:给你一个分身术.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/白话设计模式 28 讲(完)/00 生活中的设计模式:启程之前,请不要错过我.md.html">00 生活中的设计模式:启程之前,请不要错过我.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/01 监听模式:坑爹的热水器.md.html">01 监听模式:坑爹的热水器.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/02 适配模式:身高不够鞋来凑.md.html">02 适配模式:身高不够鞋来凑.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/03 状态模式:人与水的三态.md.html">03 状态模式:人与水的三态.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/04 单例模式:你是我生命的唯一.md.html">04 单例模式:你是我生命的唯一.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/05 职责模式:我的假条去哪了.md.html">05 职责模式:我的假条去哪了.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/06 中介模式:找房子问中介.md.html">06 中介模式:找房子问中介.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/07 代理模式:帮我拿一下快递.md.html">07 代理模式:帮我拿一下快递.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/08 装饰模式:你想怎么穿就怎么穿.md.html">08 装饰模式:你想怎么穿就怎么穿.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/09 工厂模式:你要拿铁还是摩卡.md.html">09 工厂模式:你要拿铁还是摩卡.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/10 迭代模式:下一个就是你了.md.html">10 迭代模式:下一个就是你了.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/11 组合模式:自己组装电脑.md.html">11 组合模式:自己组装电脑.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/12 构建模式:想要车还是庄园.md.html">12 构建模式:想要车还是庄园.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/白话设计模式 28 讲(完)/13 克隆模式:给你一个分身术.md.html">13 克隆模式:给你一个分身术.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/14 策略模式:怎么来不重要,人到就行.md.html">14 策略模式:怎么来不重要,人到就行.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/15 命令模式:大闸蟹,走起!.md.html">15 命令模式:大闸蟹,走起!.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/16 备忘模式:好记性不如烂笔头.md.html">16 备忘模式:好记性不如烂笔头.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/17 享元模式:颜料很贵必须充分利用.md.html">17 享元模式:颜料很贵必须充分利用.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/18 外观模式:学妹别慌,学长帮你.md.html">18 外观模式:学妹别慌,学长帮你.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/19 访问模式:一千个读者一千个哈姆雷特.md.html">19 访问模式:一千个读者一千个哈姆雷特.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/20 生活中的设计模式:与经典设计模式的不解渊源.md.html">20 生活中的设计模式:与经典设计模式的不解渊源.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/21 生活中的设计模式:那些未完待续的设计模式.md.html">21 生活中的设计模式:那些未完待续的设计模式.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/22 深入解读过滤器模式:制作一杯鲜纯细腻的豆浆.md.html">22 深入解读过滤器模式:制作一杯鲜纯细腻的豆浆.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/23 深入解读对象池技术:共享让生活更便捷.md.html">23 深入解读对象池技术:共享让生活更便捷.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/24 深入解读回调机制:把你技能亮出来.md.html">24 深入解读回调机制:把你技能亮出来.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/25 谈谈我对设计模式的理解.md.html">25 谈谈我对设计模式的理解.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/26 谈谈我对设计原则的思考.md.html">26 谈谈我对设计原则的思考.md.html</a>
</li>
<li>
<a href="/专栏/白话设计模式 28 讲(完)/27 谈谈我对项目重构的看法.md.html">27 谈谈我对项目重构的看法.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>13 克隆模式:给你一个分身术</h1>
<p>【故事剧情】</p>
<blockquote>
<p>Tony 最近在看一部电视剧《闪电侠》,里面一个人物叫 Danton Black 的超级人类,拥有复制自身的超能力,能够变身出六个自己,男主角第一次与他交锋时还晕了过去。</p>
<p>Tony 也想要有这种超能力,这样就可以同时处理多件事啦:可以一边敲代码、一边看书、还能一边约妹,哈哈!</p>
<p>当然这是不可能的,虽然现在的克隆技术已经能够克隆羊、克隆狗、克隆猫,但还不能克隆人!就算可以,也不能使克隆出来的自己立刻就变成二十几岁的你,当他长到二十几岁时你已经四十几岁了,他还能理解你的想法吗?</p>
</blockquote>
<p><img src="assets/cdf6fa20-781d-11e8-81a3-e1036ee16107.jpg" alt="img" /></p>
<h3>用程序来模拟生活</h3>
<p>人的克隆是困难的,但程序的克隆是简单的,因为它天生就具有方便复制的特点。在程序设计中,也有一种思想是来源于克隆这一概念,它就是克隆模式。在谈这一模式之前,我们先用程序来模拟一下 Tony 这一 YY 的想法。</p>
<p>源码示例:</p>
<pre><code class="language-python">from copy import copy, deepcopy
class Person:
&quot;&quot;
def __init__(self, name, age):
self.__name = name
self.__age = age
def showMyself(self):
print(&quot;我是&quot; + self.__name + &quot;,年龄&quot; + str(self.__age) + &quot;.&quot;)
def coding(self):
print(&quot;我是码农我在Coding改变世界...&quot;)
def reading(self):
print(&quot;阅读使我快乐!知识使我成长!如饥似渴地阅读是生活的一部分...&quot;)
def fallInLove(self):
print(&quot;春风吹,月亮明,花前月下好相约...&quot;)
def clone(self):
return copy(self)
</code></pre>
<p>测试代码:</p>
<pre><code class="language-python">def testProtoType():
tony = Person(&quot;Tony&quot;, 26)
tony.showMyself()
tony.coding()
tony1 = tony.clone()
tony1.showMyself()
tony1.reading()
tony2 = tony.clone()
tony2.showMyself()
tony2.fallInLove()
</code></pre>
<p>输出结果:</p>
<pre><code>我是Tony,年龄:26
我是码农我在Coding改变世界...
我是Tony,年龄:26
阅读使我快乐!知识使我成长!如饥似渴地阅读是生活的一部分...
我是Tony,年龄:26
春风吹,月亮明,花前月下好相约...
</code></pre>
<p>在上面的例子中Tony 克隆出了两个自己 tony1 和 tony2因为是克隆出来的所有姓名和年龄都一样这样 Tony 就可以同时去敲代码、读书和约会了。</p>
<h4>从剧情中思考克隆模式</h4>
<p>像上面的 Demo 一样,通过拷贝自身的属性来创建一个新对象的过程叫做<strong>克隆模式</strong>。在很多书籍和资料中被称为<strong>原型模式</strong>,但我觉得克隆一词更能切中其主旨。</p>
<p>克隆模式的核心就是一个 Clone 方法Clone 方法的功能就是拷贝父本的所有属性,主要包括两个过程:</p>
<ul>
<li>分配一块新的内存空间给新的对象;</li>
<li>拷贝父本对象的所有属性。</li>
</ul>
<h4>浅拷贝与深拷贝</h4>
<p>要讲清楚这个概念,先来看一个例子,还是基于上面的 Demo我们稍做一些修改。</p>
<pre><code class="language-python">class Person:
&quot;&quot;
def __init__(self, name, age):
self.__name = name
self.__age = age
self.__petList = []
def showMyself(self):
print(&quot;我是&quot; + self.__name + &quot;,年龄&quot; + str(self.__age) + &quot;. 我养了这些宠物:&quot;)
for pet in self.__petList:
print(pet + &quot;\t&quot;, end=&quot;&quot;)
print()
def addPet(self, pet):
self.__petList.append(pet)
def clone(self):
return copy(self)
def deepClone(self):
return deepcopy(self)
</code></pre>
<p>测试代码:</p>
<pre><code class="language-python">def testProtoType2():
tony = Person(&quot;Tony&quot;, 26)
tony.addPet(&quot;小狗Coco&quot;)
print(&quot;父本tony&quot;, end=&quot;&quot;)
tony.showMyself()
tony1 = tony.deepClone()
tony1.addPet(&quot;小猫Amy&quot;)
print(&quot;副本tony1&quot;, end=&quot;&quot;)
tony1.showMyself()
print(&quot;父本tony&quot;, end=&quot;&quot;)
tony.showMyself()
tony2 = tony.clone()
tony2.addPet(&quot;小兔Ricky&quot;)
print(&quot;副本tony2&quot;, end=&quot;&quot;)
tony2.showMyself()
print(&quot;父本tony&quot;, end=&quot;&quot;)
tony.showMyself()
</code></pre>
<p>输出结果:</p>
<pre><code>父本tony我是Tony,年龄26. 我养了这些宠物:
小狗Coco
副本tony1我是Tony,年龄26. 我养了这些宠物:
小狗Coco 小猫Amy
父本tony我是Tony,年龄26. 我养了这些宠物:
小狗Coco
副本tony2我是Tony,年龄26. 我养了这些宠物:
小狗Coco 小兔Ricky
父本tony我是Tony,年龄26. 我养了这些宠物:
小狗Coco 小兔Ricky
</code></pre>
<p>在上面这个例子中,我们看到“副本 tony1”是通过深拷贝的方式创建的我们对 tony1 对象增加宠物,不会影响 tony 对象。而副本 tony2 是通过浅拷贝的方式创建的,我们对 tony2 对象增加宠物时tony 对象也更着改变。这是因为 Person 类<code>__petList</code>成员是一个可变的引用类型,<strong>浅拷贝只拷贝引用类型对象的指针(指向),而不拷贝引用类型对象指向的值;深拷贝到同时拷贝引用类型对象及其指向的值。</strong></p>
<p>引用类型对象本身可以修改Python 中的引用类型有列表List、字典Dictionary、类对象。Python 在赋值的时候默认是浅拷贝,如</p>
<pre><code>def testList():
list = [1, 2, 3];
list1 = list;
print(&quot;list's id:&quot;, id(list))
print(&quot;list1's id:&quot;, id(list1))
print(&quot;修改之前:&quot;)
print(&quot;list:&quot;, list)
print(&quot;list1:&quot;, list1)
list1.append(4);
print(&quot;修改之后:&quot;)
print(&quot;list:&quot;, list)
print(&quot;list1:&quot;, list1)
</code></pre>
<p>结果:</p>
<pre><code>list's id: 56424568
list1's id: 56424568
修改之前:
list: [1, 2, 3]
list1: [1, 2, 3]
修改之后:
list: [1, 2, 3, 4]
list1: [1, 2, 3, 4]
</code></pre>
<p>通过 Clone 的方式创建对象时,浅拷贝往往是很危险的,因为一个对象的改变另一个对象也同时改变。深拷贝会对一个对象的发生进行完全拷贝,这样两个对象之间就不会相互影响了,你改你的,我改我的。</p>
<p>在使用克隆模式时,除非一些特殊情况(如需求本身就要求两个对象一起改变),<strong>尽量使用深拷贝的方式</strong>(称其为<strong>安全模式</strong>)。</p>
<h3>克隆模式的模型抽象</h3>
<h4>代码框架</h4>
<p>克隆模式非常简单,我们可以对它进行进一步的重构和优化,抽象出克隆模式的框架模型。</p>
<pre><code class="language-python">from copy import copy, deepcopy
class Clone:
&quot;克隆的基类&quot;
def clone(self):
&quot;浅拷贝的方式克隆对象&quot;
return copy(self)
def deepClone(self):
&quot;深拷贝的方式克隆对象&quot;
return deepcopy(self)
</code></pre>
<h4>类图</h4>
<p>上面的代码框架可用类图表示如下:</p>
<p><img src="assets/ebf0ed60-781d-11e8-81a3-e1036ee16107.jpg" alt="enter image description here" /></p>
<h4>基于框架的实现</h4>
<p>有了上面的代码框架之后,我们要实现示例代码的功能就会更简单了。最开始的示例代码我们假设它为 version 1.0,那么再看看基于框架的 version 2.0 吧。</p>
<pre><code class="language-python">class Person(Clone):
&quot;&quot;
def __init__(self, name, age):
self.__name = name
self.__age = age
self.__petList = []
def showMyself(self):
print(&quot;我是&quot; + self.__name + &quot;,年龄&quot; + str(self.__age) + &quot;. 我养了这些宠物:&quot;)
for pet in self.__petList:
print(pet + &quot;\t&quot;, end=&quot;&quot;)
print()
def addPet(self, pet):
self.__petList.append(pet)
def coding(self):
print(&quot;我是码农我在Coding改变世界...&quot;)
def reading(self):
print(&quot;阅读使我快乐!知识使我成长!如饥似渴地阅读是生活的一部分...&quot;)
def fallInLove(self):
print(&quot;春风吹,月亮明,花前月下好相约...&quot;)
</code></pre>
<p>测试代码不用变(同 testProtoType2()),自己跑一下,会发现输出结果和之前的是一样的。</p>
<h4>模型说明</h4>
<p>克隆模式也叫原型模式应用场景非常之广泛。Java 中与基类 Object 融为一体,可以随手就拿来用,只要 implements Cloneabble 接口就默认拥有了克隆的功能。而在 Python 中,克隆模式更是成为了语言本身的部分,因为 Python 中对象的赋值就是一个浅拷贝的过程。</p>
<h5><strong>克隆模式的优点</strong></h5>
<ul>
<li>克隆模式是通过内存拷贝的方式进行复制,比 new 的方式创建对象性能更好;</li>
<li>通过深拷贝的方式,可以方便地创建一个具有相同属性和行为的另一个对象,特别是对于复杂对象,方便性尤为体现。</li>
</ul>
<h5><strong>克隆模式的缺点</strong></h5>
<p>通过克隆的方式创建对象,<strong>不会执行类的构造函数</strong>,这不一定是缺点,但大家使用的时候需要注意这一点。</p>
<h3>应用场景</h3>
<ul>
<li>如果创建新对象(如复杂对象)成本较大,我们可以利用已有的对象进行复制来获得。</li>
<li>当类的初始化需要消耗非常多的资源时,如需要消耗很多的数据、硬件等资源。</li>
</ul>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/白话设计模式 28 讲(完)/12 构建模式:想要车还是庄园.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/白话设计模式 28 讲(完)/14 策略模式:怎么来不重要,人到就行.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997b977d1d3cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>