mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-17 08:46:40 +08:00
782 lines
23 KiB
HTML
782 lines
23 KiB
HTML
<!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:
|
||
|
||
"人"
|
||
def __init__(self, name, age):
|
||
|
||
self.__name = name
|
||
|
||
self.__age = age
|
||
def showMyself(self):
|
||
|
||
print("我是" + self.__name + ",年龄" + str(self.__age) + ".")
|
||
def coding(self):
|
||
|
||
print("我是码农,我在Coding改变世界...")
|
||
def reading(self):
|
||
|
||
print("阅读使我快乐!知识使我成长!如饥似渴地阅读是生活的一部分...")
|
||
def fallInLove(self):
|
||
|
||
print("春风吹,月亮明,花前月下好相约...")
|
||
def clone(self):
|
||
|
||
return copy(self)
|
||
</code></pre>
|
||
|
||
<p>测试代码:</p>
|
||
|
||
<pre><code class="language-python">def testProtoType():
|
||
|
||
tony = Person("Tony", 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:
|
||
|
||
"人"
|
||
def __init__(self, name, age):
|
||
|
||
self.__name = name
|
||
|
||
self.__age = age
|
||
|
||
self.__petList = []
|
||
def showMyself(self):
|
||
|
||
print("我是" + self.__name + ",年龄" + str(self.__age) + ". 我养了这些宠物:")
|
||
|
||
for pet in self.__petList:
|
||
|
||
print(pet + "\t", end="")
|
||
|
||
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("Tony", 26)
|
||
|
||
tony.addPet("小狗Coco")
|
||
|
||
print("父本tony:", end="")
|
||
|
||
tony.showMyself()
|
||
tony1 = tony.deepClone()
|
||
|
||
tony1.addPet("小猫Amy")
|
||
|
||
print("副本tony1:", end="")
|
||
|
||
tony1.showMyself()
|
||
|
||
print("父本tony:", end="")
|
||
|
||
tony.showMyself()
|
||
tony2 = tony.clone()
|
||
|
||
tony2.addPet("小兔Ricky")
|
||
|
||
print("副本tony2:", end="")
|
||
|
||
tony2.showMyself()
|
||
|
||
print("父本tony:", end="")
|
||
|
||
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("list's id:", id(list))
|
||
|
||
print("list1's id:", id(list1))
|
||
|
||
print("修改之前:")
|
||
|
||
print("list:", list)
|
||
|
||
print("list1:", list1)
|
||
|
||
list1.append(4);
|
||
|
||
print("修改之后:")
|
||
|
||
print("list:", list)
|
||
|
||
print("list1:", 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:
|
||
|
||
"克隆的基类"
|
||
def clone(self):
|
||
|
||
"浅拷贝的方式克隆对象"
|
||
|
||
return copy(self)
|
||
def deepClone(self):
|
||
|
||
"深拷贝的方式克隆对象"
|
||
|
||
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):
|
||
|
||
"人"
|
||
def __init__(self, name, age):
|
||
|
||
self.__name = name
|
||
|
||
self.__age = age
|
||
|
||
self.__petList = []
|
||
def showMyself(self):
|
||
|
||
print("我是" + self.__name + ",年龄" + str(self.__age) + ". 我养了这些宠物:")
|
||
|
||
for pet in self.__petList:
|
||
|
||
print(pet + "\t", end="")
|
||
|
||
print()
|
||
def addPet(self, pet):
|
||
|
||
self.__petList.append(pet)
|
||
def coding(self):
|
||
|
||
print("我是码农,我在Coding改变世界...")
|
||
def reading(self):
|
||
|
||
print("阅读使我快乐!知识使我成长!如饥似渴地阅读是生活的一部分...")
|
||
def fallInLove(self):
|
||
|
||
print("春风吹,月亮明,花前月下好相约...")
|
||
</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>
|
||
|