操作系统进程描述

这是操作系统进程系列文章第二篇-操作系统进程描述 进程 什么是进程 在给进程下定义前,先考虑以下几个概念: 一个计算机平台包括一组硬件资源:比如处理器、内存、I/O 模块、定时器和磁盘驱动器等。 计算机程序是为执行某些任务而开发的。典型情况下,它们接受外来的输入,做一些处理后,输出结果。 直接根据给定的硬件平台写应用程序效率是低下的 开发操作系统是为了给应用程序提供一个方便、安全和一直的接口。操作系统是计算机硬件和应用程序直接的一层软件,对应用程序和工具提供了支持。 可以把操作系统想象为资源的统一抽象表示,可以被应用程序请求和访问。资源包括内存、网络接口和文件系统等。 有了上述概念,现在就可以讨论操作系统怎样以一个有序的方式管理应用程序的执行,以达到以下目的: 资源对多个应用程序是可用的 物理处理器在多个应用程序间切换以保证所有程序都在执行中 处理器和 I/O 设备能得到充分的利用 现代操作系统采用的方法都是依据对应于一个或多个进程存在的应用程序执行的一种模型。 关于进程有很多定义: 一个正在执行的程序 计算机中正在运行的程序的一个实例 可以分配给处理器并由处理器执行的一个实体 由单一的顺序的执行线程、一个当前状态和一组相关的系统资源所描述的活动单元 进程状态 一个被执行的程序,操作系统会为该程序创建一个进程或任务,并且控制进程的执行。 简单来说,程序只有两种状态:运行态、未运行态。 当操作系统创建一个新进程时,它将该进程以未运行态加入到系统中,操作系统知道进程的存在,并等待执行机会。 当前运行的进程不时中断,操作系统的分派器将选择一个新进程运行。 前一个进程从运行态转换到未运行态,另一个从未运行态转换到运行态。 同时,未运行的进程需保持在某种类型的队列中,并等待它们的执行时机。 上图中的排队图可以描述分派器的行为:被中断的进程转移到等待进程队列中,或者,如果进程以及结束或取消,则被销毁。在任何一种情况下,分派器均从队列中选择一个进程来执行。 通过这个模型,可以看出操作系统需要用某种方式来表示每个进程,使得操作系统能够跟踪它,也就是说需要有一些与进程相关的信息,包括进程在内存中的状态和位置,即进程控制块。 进程控制块 进程在任意时间都可以唯一地被表征为以下元素: 标识符:存储在进程控制块中的数字标识符,包括(次进程的标识符-进程 ID,父进程标识符,用户标识符-用户 ID) 状态:进程状态(如运行态,就绪态,等待态等) 优先级:用于描述进程调度优先级的一个或多个域。 程序计数器:程序中即将被执行的下一条指令的地址 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享内存块的指针 上下文数据:进程执行时处理器的寄存器的数据 I/O 状态信息:包括显示的 I/O 请求、分配给进程的 I/O 设备和被进程使用的文件列表等 记账信息:可能包括处理器时间总和、使用的时钟数总和、时间限制、记账号等。 这些信息被存放在一个叫进程控制块的数据结构中,它由操作系统创建和管理。进程控制块是进程存在的唯一标志,也就是说任何一个进程只要进程创建了它就一定有一个跟它相对应的进程控制块,进程结束了进程控制块就会被操作系统回收,进程在执行的过程对进程的所有操作都是通过进程控制块来实现的。 进程创建和终止 进程除运行和未运行外,在进程的生命周期中,创建和终止都是不可避免的。 进程创建 通常有4个事件会导致创建一个进程: 新的批量作业 交互登录。终端用户登录到系统 操作系统因为提供一项服务而创建。操作系统可以创建一个进程,代表用户程序执行一个功能,使用户无需等待。 由现有进程派生。基于模块化的考虑,或者为了开发并行性,用户程序可以指示创建多个进程。 当一个进程派生另一个进程时,前一个称为父进程,被派生的被称为子进程。 一旦操作系统决定创建一个新进程,它就会按以下步骤进行: 给新进程分配一个唯一的进程标识符。 给进程分配空间。 初始化进程控制块。 设置正确的连接。(例如,如果操作系统把每个调度队列都保存成链表,则新进程必须放置在就绪或就绪/挂起链表中)。 创建或扩充其他数据结构。 进程终止 有很多事件可以导致进程终止,比如: 进程完成 进程超时。进程运行时间超过规定的时限 无可用内存 I/O 失败 算术错误 无效指令 父进程终止 父进程请求 。。。 五状态模型 系统中还存在着一些处于非运行状态但已经就绪等待执行的进程,而且还存在另一些处于阻塞状态等待 I/O 操作结束的进程。 ...

2018-03-20 · 1 min · 173 words

操作系统发展和进程简介

这是操作系统进程系列文章第一篇-操作系统发展和进程简介 操作系统的发展 串行处理 对于早期计算机(20世纪40年代后期到20世纪50年代中期),因为没有操作系统,程序员都是直接与计算机硬件打交道。这些机器都在一个控制台上运行,控制台包括显示灯、触发器、某种类型的输入设备和打印机。用机器代码编写的程序通过输入设备载入计算机。如果程序因错误停止,错误原因由指示灯只是。如果程序运行结束,结果将出现在打印机中。 早期系统主要有两个问题: 调度: 大多数设备使用一个硬拷贝的登记表预定时间。如果用户预定了一个小时,半小时就运行结束,计算机将闲置30分钟,而如果没有在一个小时内运行结束,程序也会被强制停止。 准备时间:一个程序称为一个作业,运行一个程序可能需要往内存中加载编译器和程序语言,保存编译程序,加载目标程序和公用函数变链接在一起。每一步都可能需要安装拆卸硬件,如果这些步骤出现错误,只能重新开始,会占用太多时间。 这种模式称为串行处理,用户必须顺序访问计算机。 简单批处理系统 早期计算机非常贵,调度和准备又非常浪费时间和资源,为了最大限度的利用处理器,当时的研究人员开发了批处理操作系统。 第一个批处理操作系统(也是第一个操作系统)是20世纪50年代中期由 General Motors 开发的,用在 IBM 701上。 简单批处理方案的中心思想是使用一个称作监控程序的软件。通过使用这类操作系统,用户不再直接访问机器,相反,用户把卡片或磁带中的作业提交给计算机管理员,由他把这些作业按顺序组织成一批,并将整个批作业放在输入设备上,供监控程序使用。每个程序完成批处理后返回到监控程序,同事监控程序自动加载下一个程序。 我们可以从两个角度分析这个方案是如何工作的: 监控程序角度: 监控程序为了能一直控制事件的顺序,需要总是处于内存中并且可以执行。监控程序每次从输入设备中读取一个作业,读入后,当前作业被放置在用户程序区域,并把控制权交给这个作业。作业完成后,控制权交还给监控程序,监控程序再读入下一个作业。 处理器角度: 从这个角度看,处理器执行内存中存储的监控程序的指令,这些指令读入下一个作业并存储到内存中的另一个部分。一旦已经读入一个作业,处理器将会遇到监控程序的分支指令,分支指令指导处理器在用户程序开始处继续执行。处理器继而执行用户程序直到执行结束或者遇到错误。无论哪种情况,处理器都将从监控程序读入下一个指令。 控制权交给作业仅仅意味着处理器当前取和执行的都是用户程序中的指令,而控制权交给监控程序的意思是处理器当前从监控程序中取指令并且执行指令。 监控程序或者批处理操作系统,只是一个简单的计算机程序。它依赖于处理器可以从内存的不同部分取指令的能力,以交替的获取或释放控制权。此外,还要考虑其他硬件功能: 内存保护:当用户程序在运行时,不能改变包含监控程序的内存区域 定时器:用户防止一个作业独占系统。作业开始时,设置定时器,时间到,用户程序将被停止 特权指定:某些指令设计成特权指令,只能由监控程序执行。 中断:早期的计算机模型没有中断能力。这个特征使得操作系统在让用户程序放弃控制权或从用户程序获得控制权时具有更大的灵活性。 多道程序设计批处理(多任务处理)系统 虽然简单的批处理系统可以提供自动作业序列,但由于 I/O 设备处理速度相对于处理器速度太慢,处理器仍然经常空闲。这个时候多道程序设计/多任务处理方案就被提了出来。 它的工作原理是:基于内存空间可以保存操作系统和一个用户程序,假设内存空间容得下操作系统和两个用户程序,那么当一个作业需要等待 I/O 时,处理器可以切换到另一个可能并不在等待 I/O 的作业。进一步还可以扩展存储器以保存三个、四个或更多的程序,并在它们之间进行切换。 多道程序操作系统比单个程序或单道程序系统相对要复杂一些。对准备运行的多个作业,它们必须保存在内存中,这就需要内存管理。此外,如果多个作业都准备运行,处理器还必须决定运行哪一个,这需要某种调度算法。 多道程序设计是为了让处理器和 I/O 设备同时保持忙状态,以实现最大效率。其关键机制是:在响应表示 I/O 事务结束的信号时,操作系统对内存中驻留的不同程序进行处理器切换。 分时系统 通过使用多道程序设计,可以使批处理更加有效,但是对许多作业来说,需要提供一个交互模式,以使用户可以和计算机交互。 因为当时的计算机特别昂贵且巨大,普通用户也买不起,分时操作系统应运而生。 和多道程序设计允许处理器同时处理多个批作业一样,它还可以用于处理多个交互作业。 多个用户分享处理器的时间,因而该技术成为分时。 分时系统中,多个用户可以通过终端同时访问系统,由操作系统控制每个用户程序以很短的时间为单位交替执行。 如果有 n 个用户同时请求服务,若不计算操作系统的开销,每个用户平均只能得到1/n 计算机的有效速度,但由于人的反应时间相对计算机比较慢,所以一个设计良好的操作系统,其响应时间可以接近于计算机的时间。 批处理多道程序设计和分时的比较 项目 批处理多道程序设计 分时 主要目标 充分使用处理器 减小响应时间 操作系统指令源 作业提供的作业控制语言命令 从终端键入的命令 第一个分时操作系统是由麻省理工学院开发的兼容分时系统(CTSS)。系统运行在一台内存为32000个36位字的机器上,常驻程序占用了5000个。当控制权被分配给一个交互用户时,改用户的程序和数据被载入到内存剩余的27000个字的空间中。程序通常在第5000个字单元处开始被载入,系统时钟以大约没0.2秒一个的速度产生中断,在每个中断处,操作系统恢复控制权,并将处理器分配给下一个用户。因此,在固定的时间间隔内,当前用户被剥夺,另一个用户被载入。这项技术称为时间片技术。 操作系统是最复杂的软件之一,操作系统开发中有5个重要的理论进展:进程、内存管理、信息保护和安全、调度和资源管理、系统结构。 进程 进程的概念是操作系统结构的基础,这个属于最早在20世纪60年代被提出。 关于进程有很多定义: ...

2018-03-07 · 1 min · 123 words

Golang 学习笔记-1:变量&函数

变量&函数 最近在学习golang,写下学习笔记提升记忆。 为了看起来不是那么枯燥,本学习笔记采用分析代码的形式。 首先搬出我们最经典的第一段代码: hello world package main // 0 import "fmt" // 1实现格式化的 I/O /* Print something */ // 2 func main() { // 3 fmt.Println("Hello, world; or καλημε ́ρα κóσμε; orこんにちは 世界") // 4 } 首先我们要认识到 每个Go 程序都是由包组成,程序的运行入口是包main 首行这个是必须的。所有的 Go 文件以 package 开头,对于独立运行的执行文件必须是 package main; 这是说需要将fmt加入到main。不是main 的包被称为库 末尾以 // 开头的内容是单行注释 Package fmt包含有格式化I/O函数,类似于C语言的printf和scanf 这也是注释,表示多行注释。 package main 必须首先出现,紧跟着是 import。在 Go 中,package 总是首先出现, 然后是 import,然后是其他所有内容。当 Go 程序在执行的时候,首先调用的函数 是 main.main(),这是从 C 中继承而来。这里定义了这个函数 调用了来自于 fmt 包的函数打印字符串到屏幕。字符串由 " 包裹,并且可以包含非 ASCII 的字符。这里使用了希腊文和日文、中文" 编译和运行代码 构建 Go 程序的最佳途径是使用 go 工具。 构建 helloworld 只需要: ...

2018-02-22 · 5 min · 968 words

python设计模式-命令模式

命令模式 题目: 现在要做一个智能家居控制遥控器,功能如下图所示。 下图是家电厂商提供的类,接口各有差异,并且以后这种类可能会越来越多。 观察厂商提供的类,你会发现,好多类提供了 on()、off() 方法,除此之外,还有一些方法像 dim()、setTemperature()、setVolumn()、setDirection()。由此我们可以想象,之后还会有更多的厂商类,每个类还会有各式各样的方法。 如果我们把这些类都用到遥控器代码中,代码就会多一大堆的 if 语句,例如 if slot1 == Light: light.on() elif slot1 == Hottub: hottob.jetsOn() 并且更严重的是,每次有新的厂商类加进来,遥控器的代码都要做相应的改动。 这个时候我们就要把动作的请求者(遥控器)从动作的执行者(厂商类)对象中解耦。 如何实现解耦呢? 我们可以使用命令对象。利用命令对象,把请求(比如打开电灯)封装成一个特定对象。所以,如果对每个按钮都存储一个命令对象,那么当按钮按下的时候,就可以请求命令对象做相关的工作。此时,遥控器并不需要知道工作的内容是什么,只要有个命令对象能和正确的对象沟通,把事情做好就可以了。 下面我们拿餐厅点餐的操作来介绍下命令模式。 餐厅通常是这样工作的: 顾客点餐,把订单交给服务员 服务员拿了订单,把订单交给厨师。 厨师拿到订单后根据订单准备餐点。 这里我们把订单想象成一个用来请求准备餐点的对象, 和一般对象一样,订单对象可以被传递:从服务员传递到订单柜台,订单的接口只包含一个方法 orderUp()。这个方法封装了准备餐点所需的动作。 服务员的工作就是接受订单,然后调用订单的 orderUp() 方法,她不需要知道订单内容是什么。 厨师是一个对象,他知道如何准备准备餐点,是任务真正的执行者。 如果我们把餐厅想象成OO 设计模式的一种模型,这个模型允许将”发出请求的对象“和”接受与执行这些请求的对象“分隔开来。比如对于遥控器 API,我们要分隔开”发出请求的按钮代码“和”执行请求的厂商特定对象”。 回到命令模式我们把餐厅的工作流程图转换为命令模式的流程图:这里 client 对应上一张图的顾客,command 对应订单,Invoker 对应服务员,Receiver 对应的是厨师。 命令模式 先来看下命令模式的定义: 命令模式将”请求“封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。 通过上边的定义我们知道,一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和接收者包进对象中。这个对象只暴露一个 execute() 方法,当此方法被调用时,接收者就会进行这些动作。 命令模式类图如下: 回到遥控器的设计:我们打算将遥控器的每个插槽,对应到一个命令,这样就让遥控器变成了调用者。当按下按钮,相应命令对象的 execute() 方法就会被调用,其结果就是接收者(例如:电灯、风扇、音响)的动作被调用。 命令模式还支持撤销,该命令提供和 execute() 方法相反的 undo() 方法。不管 execute() 做了什么,undo() 都会倒转过来。 ...

2018-01-09 · 3 min · 477 words

Python 字典

这一篇是《流畅的 python》读书笔记。主要介绍: 常见的字典方法 如何处理查不到的键 标准库中 dict 类型的变种 散列表的工作原理 泛映射类型 collections.abc 模块中有 Mapping 和 MutableMapping 这两个抽象基类,它们的作用是为 dict 和其他类似的类型定义形式接口。 标准库里所有映射类型都是利用 dict 来实现的,它们有个共同的限制,即只有可散列的数据类型才能用做这些映射里的键。 问题: 什么是可散列的数据类型? 在 python 词汇表(https://docs.python.org/3/glossary.html#term-hashable)中,关于可散列类型的定义是这样的: 如果一个对象是可散列的,那么在这个对象的生命周期中,它的散列值是不变的,而且这个对象需要实现 __hash__() 方法。另外可散列对象还要有 __eq__() 方法,这样才能跟其他键做比较。如果两个可散列对象是相等的,那么它们的散列只一定是一样的 根据这个定义,原子不可变类型(str,bytes和数值类型)都是可散列类型,frozenset 也是可散列的(因为根据其定义,frozenset 里只能容纳可散列类型),如果元组内都是可散列类型的话,元组也是可散列的(元组虽然是不可变类型,但如果它里面的元素是可变类型,这种元组也不能被认为是不可变的)。 一般来讲,用户自定义的类型的对象都是可散列的,散列值就是它们的 id() 函数的返回值,所以这些对象在比较的时候都是不相等的。(如果一个对象实现了 eq 方法,并且在方法中用到了这个对象的内部状态的话,那么只有当所有这些内部状态都是不可变的情况下,这个对象才是可散列的。) 根据这些定义,字典提供了很多种构造方法,https://docs.python.org/3/library/stdtypes.html#mapping-types-dict 这个页面有个例子来说明创建字典的不同方式。 >>> a = dict(one=1, two=2, three=3) >>> b = {'one': 1, 'two': 2, 'three': 3} >>> c = dict(zip(['one', 'two', 'three'], [1, 2, 3])) >>> d = dict([('two', 2), ('one', 1), ('three', 3)]) >>> e = dict({'three': 3, 'one': 1, 'two': 2}) >>> a == b == c == d == e True 除了这些方法以外,还可以用字典推导的方式来建造新 dict。 ...

2017-12-03 · 3 min · 568 words

python设计模式-单例模式

问题:现代化的巧克力工厂具备计算机控制的巧克力锅炉。锅炉做的事情就是把巧克力和牛奶融在一起,然后送到下一个阶段,以制成巧克力棒。下边是一个巧克力公司锅炉控制器的代码,仔细观察一下,这段代码有什么问题? class ChocolateBoiler(object): def __init__(self): self.empty = True self.boiled = False def fill(self): # 向锅炉填充巧克力和牛奶混合物 # 在锅炉内填充原料时,锅炉必须是空的。 # 一旦填入原料,就要把empty 和 boiled 标志设置好 if self.empty: self.empty = False self.boiled = False def drain(self): # 排出煮沸的巧克力和牛奶 # 锅炉排出时,必须是满的且煮沸的。 # 排出完毕empty 设置为 true if not self.empty and self.boiled: self.empty = True def boil(self): # 将颅内物煮沸 # 煮混合物时,锅炉内必须是满的且没有煮沸过 # 一旦煮沸,就把 boiled 设置为 true if not self.empty and not self.boiled: self.boiled = True 从代码可以看出,他们加入了多种判断,以防止不好的事情发生。如果同时存在两个ChocolateBoiler实例,那这么多判断岂不是失去作用了。那我们改如何实现这个需求呢?这个问题的核心是,我们要先判断实例是不是已经存在,如果存在就不再创建。 _chocolate_boiler_instance = None # 声明实例 def chocolate_boiler(): global _chocolate_boiler_instance # 使用全局变量 if _chocolate_boiler_instance is not None: # 判断是否存在,如果存在,直接返回 return _chocolate_boiler_instance else: # 如果不存在,创建一个新的 _chocolate_boiler_instance = ChocolateBoiler() return _chocolate_boiler_instance 现在我们需要获取 ChocolateBoiler 实例的时候只需要调用 chocolate_boiler 方法获取实例即可保证同时只有一个 ChocolateBoiler实例。 ...

2017-11-26 · 2 min · 418 words

python设计模式-建造者模式

问题:在上一篇python设计模式:抽象工厂模式中,我们尝试用抽象工厂模式规范化了 Pizza 原材料的供应以及 Pizza 的创建。但是我们忽略了一个问题,那就是每种 Pizza 的烘焙时间依赖于生面团的厚度和使用的配料,它们所需的时间是不一样的。那这时我们改如何处理呢? Pizza 的制作流程包括:准备(擀面皮、加佐料),然后烘烤、切片、装盒。这些有特定的顺序,不能错乱。 为了保证 生产 Pizza 的步骤不会出错,我们打算指派一个创建者,创建者用于控制 Pizza 的制作流程。 创建 Pizza 创建者 首先我们定义一个 Pizza class Pizza: def __init__(self, name): self.name = name self.dough = None self.sauce = None self.toppings = [] def prepare_dough(self, dough): self.dough = dough print(self.dough) print('preparing the {} dough of your {}...'.format(self.dough, self)) time.sleep(STEP_DELAY) print('Done with the {} dough'.format(self.dough)) def __str__(self): return self.name 然后我们抽象出一个创建者: class PizzaBuilder(object): name = None def __init__(self): self.progress = PIZZA_PROGRESS self.baking_time = 5 def prepare_dough(self): raise NotImplementedError() def add_sauce(self): raise NotImplementedError() def add_topping(self): raise NotImplementedError() def bake(self): raise NotImplementedError() def cut(self): raise NotImplementedError() def box(self): raise NotImplementedError() @property def pizza(self): return Pizza(self.name) 创建具体建造者 class NYStyleCheeseBuilder(PizzaBuilder): name = 'NY Style Sauce and Cheese Pizza' def prepare_dough(self): self.progress = PIZZA_PROGRESS[0] self.pizza.prepare_dough('thin') def add_sauce(self): print('adding the tomato sauce to your pizza..') self.pizza.sauce = 'tomato' time.sleep(STEP_DELAY) print('done with the tomato sauce') def add_topping(self): print('adding the topping (grated reggiano cheese) to your pizza') self.pizza.toppings.append(["Grated", "Reggiano", "Cheese"]) time.sleep(STEP_DELAY) print('done with the topping (grated reggiano cheese)') def bake(self): self.progress = PIZZA_PROGRESS[1] print('baking your pizza for {} seconds'.format(self.baking_time)) time.sleep(self.baking_time) def cut(self): self.progress = PIZZA_PROGRESS[2] print("Cutting the pizza into diagonal slices") def box(self): self.progress = PIZZA_PROGRESS[3] print("Place pizza in official PizzaStore box") 创建指挥者 class Waiter: # 指挥者 def __init__(self): self.builder = None def construct_pizza(self, builder): self.builder = builder # 一旦我们有了一个 pizza,需要做一些准备(擀面皮、加佐料),然后烘烤、切片、装盒 [step() for step in (builder.prepare_dough, builder.add_sauce, builder.add_topping, builder.bake, builder.cut, builder.box)] @property def pizza(self): return self.builder.pizza 完整代码参考:python-design-patter-builder ...

2017-11-12 · 2 min · 266 words

python设计模式-抽象工厂模式

问题:在上一篇 python设计模式:工厂方法模式我们尝试使用工厂方法创建了披萨店,现在为了保证披萨加盟店也能有良好的声誉,我们需要统一原材料,这个该如何做呢? 为了确保每家加盟店都是用高质量的原材料,我们打算建造一加原材料工厂,并将原材料运送到各个加盟店。每个加盟店会对原材料有不同的需求,这里我们就可以用上上一篇介绍的工厂方法模式了。 首先,建造原料工厂 然后建造区域的原料工厂(继承自原料工厂) 在区域的原料工厂中实现原料的创建方法。 将原料工厂组合起来,加入到 PizzaStore(上一篇中由工厂方法实现)代码中。 按照这个思路,我们先创建原料工厂 创建原料工厂 创建原料工厂的实现代码如下: # 原料 class FreshClams: def __str__(self): return 'Fresh Clams' class MarinaraSauce: def __str__(self): return "Marinara Sauce" class ThickCrustDough: def __str__(self): return "Thick Crust Dough" class ReggianoCheese: def __str__(self): return "Reggiano Cheese" class SlicedPepperoni: def __str__(self): return "Sliced Pepperoni" class Garlic: def __str__(self): return "Garlic" class Onion: def __str__(self): return "Onion" class RedPepper: def __str__(self): return "Red Pepper" # 披萨店原料工厂 class PizzaIngredientFactory: ''' 定义原料工厂 ''' def create_dough(self): raise NotImplementedError() def create_sauce(self): raise NotImplementedError() def create_cheese(self): raise NotImplementedError() def create_pepperoni(self): raise NotImplementedError() def create_clam(self): raise NotImplementedError() def create_veggies(self): raise NotImplementedError() 在这个工厂中,每个原料都是一个方法,原料的实现需要在具体的原料工厂中实现。 这里每个原料方法没有做任何工作,只是抛出了NotImplementedError 这样做是为了强制子类重新实现相应的方法,如果不重新实现用到时就会抛出 NotImplementedError。 ...

2017-10-28 · 3 min · 429 words

python设计模式-工厂方法模式

题目:假设你有一个 pizza 店,功能包括下订单、做 pizza,你的代码会如何写呢? def order_pizza(): pizza = Pizza() pizza.prepare() pizza.bake() pizza.cut() pizza.box() return pizza 但是现在你遇到了一个问题,你的 pizza 店需要更多的 pizza,所以现在你需要增加一些代码,来决定适合的 pizza 类型,然后再制造这个 pizza: def order_pizza(pizza_type): # 现在把 pizza 的类型传入 order_pizza() # 根据 pizza 类型,我们实例化正确的具体类,然后将其赋值给 pizza 实例变量 if pizza_type == 'cheese': pizza = CheesePizza() elif pizza_type == 'greek': pizza = GreekPizza() elif pizza_type == 'pepperoni': pizza = PepperoniPizza() # 一旦我们有了一个 pizza,需要做一些准备(擀面皮、加佐料),然后烘烤、切片、装盒 pizza.prepare() pizza.bake() pizza.cut() pizza.box() return pizza 但是经过几天的实践,你发现顾客喜欢点的 ClamPizza、Veggie Pizza 而 Greek pizza 并没有什么人喜欢,这个时候需要修改代码: ...

2017-10-15 · 4 min · 667 words

python设计模式-观察者模式

题目:现在你有一个数字,默认格式化程序是以十进制格式展示此数值,但需要提供一个功能,这个程序要支持添加/注册更多的格式化程序(比如:添加一个十六进制格式化程序和一个二进制格式化程序)。每次数值更新时,已注册的程序就会收到通知,并显示更新后的值。 我们看下需求: NumberFormatter 有一个 number 属性 当 number 值修改时,相关的格式化方式展示结果要改变 此系统必须可扩展已适应其他格式化方式的使用。 一个错误的实现可能是这样的: class NumberFormatter(object): def __init__(self, number): self.number = number def show_data(self): self.default_formatter() self.hex_formatter() self.binary_formatter() def default_formatter(self): pass def hex_formatter(self): pass def binary_formatter(self): pass 我们可以这么使用: number = NumberFormatter(10) number.show_data() 但是这样会有一个问题:这种针对实现的编程会导致我们在增加或者删除需要格式化方式时必须修改代码。比如我们现在不再需要十六进制数字格式的显示,就需要把 hex_formatter 相关的代码删除或者注释掉。 要解决这个问题,就可以用到我们这次要介绍的观察者模式了。 什么是观察者模式 认识观察者模式 我们先看看报纸和杂志的订阅是怎么回事: 报社的业务就是出版报纸 向某家报社订阅报纸,只要他们有新报纸,就会给你送来,只要你是他们的订户,你就会一直受到新报纸。 当你不再想看的时候,取消订阅,他们就不会在送新报纸给你 只要报社还在运营,就会一直有人向他们订阅报纸或取消订阅。 我们用图表示一下,这里出版者 改称为主题(Subject),订阅者改称为观察者(Observer): 1. 开始的时候,鸭子对象不是观察者 2. 鸭子对象过来告诉主题,它想当一个观察者(鸭子其实想说的是:我对你的数据改变感兴趣,一有变化请通知我) 3. 鸭子对象已经是观察者了(鸭子静候通知,一旦接到通知,就会得到一个整数)。 4. 主题有了新的数据(现在鸭子和其他所有观察者都会受到通知:主题已经改变) 5. 老鼠对象要求从观察者中把自己除名(老鼠已经观察次主题太久,决定不再当观察者了)。 6. 老鼠离开了(主题知道老鼠的请求后,把它从观察者中移除了)。 7. 主题有了一个新的整数(除了老鼠之外,每个观察者都会收到通知,如果老鼠又想当观察者了,它还可以再回来) 定义观察者模式 当你试图勾勒观察者模式时,可以利用报纸订阅服务,以及出版这和订阅者比你这一切。在程序设计中,观察者模式通常被定义为: 观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态是,它的所有依赖者都会收到通知并自动更新。 我们和之前的例子做个对比: ...

2017-09-24 · 3 min · 461 words