文章推荐
Selenium 自动化测试从零实战【阅读原文】
原来这样做,才能向架构师靠近【阅读原文】
Cordova App 打包全揭秘【阅读原文】
TensorFlow on Android:物体识别【阅读原文】
TensorFlow on Android:训练模式【阅读原文】
图解敏捷教练和 ScrumMaster【阅读原文】
[运营专题]零预算引爆个人和企业品牌【阅读原文】
一学就会的 WordPress 实战课【阅读原文】
快速学习 Spring Boot 技术栈【阅读原文】
Webpack 达人的成长之路【阅读原文】
React Native 移动开发入门与实战【阅读原文】
如何从生活中领悟设计模式【阅读原文】
罗伟富,CSDN 博客专家,ABC360 客户端资深工程师。3 年 SDK 开发,2 年客户端开发,从底层到应用层积累了丰富的开发经验,熟悉 C++、Java、Python 等多种不同开发语言。热爱生活,乐于分享,善于用生活的场景解读难懂的技术。
设计模式(Design pattern)是一套被反复使用、多数人知晓的、无数工程师实践的代码设计经验的总结,它是面向对象思想的高度提炼和模板化。使用设计模式将会使你的代码具有更高的可重用性,更好的灵活性和可拓展性,更易被人阅读和理解。
我一直坚信:程序源于生活,又高于生活。程序的灵魂在于思维的方式,而思维的灵感来源于生活的精彩。本系列课程我将以全新的方式,从生活中你我的故事开始,由浅入深地逐步阐述设计模式的思想,并抽象出代码模型(骨架)。 程序不应只是冷冰冰的代码,更应赋予它生活的乐趣和特殊的意义。
本课程将会从生活的角度,在生活的每一个细节和故事中解读一个个设计模式。力求用最通俗的语言阐述最难懂的概念;用最简单的语法实现最复杂的逻辑;用最短小的代码写出最强悍的程序。希望能给您带来一种全新的阅读体验和思考方式。
两年前 CSDN 出一个产品叫 ink,旨在提供一个高质量写作环境。那时就有写这一系列的想法了,而且也确实写了,就在 ink 里写了三篇文章,后来不知道因为什么原因这个产品下架了,我的三篇文章也没了,这事也就一直被我搁置下来。今天,我以全新的方式和思路重写这一系列文章!
我们从生活的小故事开始,由浅入深,逐步阐述设计模式的思想,并抽象出代码模型(骨架)。
什么是设计模式
设计模式最初是被 GoF 于1995年提出的。GoF 全称是Gang of Four(四人帮),即 Erich Gamma,Richard Helm,Ralph Johnson 和 John Vlissides。他们四人于1995年出版了一本书 (中文翻译为“设计模式:可复用面向对象软件的基础”),第一次将设计模式提升到理论高度并将之规范化。该书提出了23种经典的设计模式。
设计模式(Design pattern)是一套被反复使用、多数人知晓的、无数工程师实践的代码设计经验的总结,它是面向对象思想的高度提炼和模板化。使用设计模式是为了让代码具有更高的可重用性,更好的灵活性和可拓展性,更易被人阅读和理解。GoF 提到模式有四个基本要素:
我一直坚信:程序源于生活,又高于生活。程序的灵魂在于思维的方式,而思维的灵感来源于生活的精彩。互联网是一个虚拟的世界,而程序本身就是对生活场景的虚拟和抽象,每一个模式我都能在生活中找到他的影子。比如,说到状态模式我能想到水有冰、水、气三种状态,而人也有少、壮、老三个不同的阶段;提起中介模式我能立马想到房产中介;看到单例模式,脑海中会即刻浮现心目中的那个她……
设计模式是面向对象的高度抽象和总结,而越抽象的东西越难以理解。本系列文章的目地就是为了降低设计模式的阅读门槛,以生活中的小故事开始,用风趣的方式,由浅入深地讲述每一个模式。让你再次看到设计模式不只是一个模式,还是生活中的一个个小确幸!程序不是冷冰冰的代码,它还有生活的乐趣和特殊意义。
设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间试验和错误总结出来的。所以不管你是新手还是老手,学习设计模式对你都有莫大的帮助。
学习设计模式的理由有很多,我这里只列出几个最现实的:
首先,你至少要熟悉一门面向对象的计算机语言。如果没有,请根据自己学习、爱好,或希望从事的工作,先选择一门面向对象语言(C++,Java,Python,Go 等都可以)进行学习和实战,对抽象、继承、多态、封装有一定的基础之后,再来看本系列的文章。
对 Python 基本语法有一个简单了解。Python 语法非常简单,只要你有一定的编程语言基础,通过下文的介绍很快就能理解的。
UML(Unified Modeling Language)称为统一建模语言或标准建模语言,是面向对象软件的标准化建模语言。UML 规范用来描述建模的概念有:类(对象的)、对象、关联、职责、行为、接口、用例、包、顺序、协作以及状态。
UML 类图表示不同的实体(人、事物和数据)如何彼此相关;换句话说,它显示了系统的静态结构。想进一步了解类图中的各种关系,可参考以下文章: UML 类图关系大全 UML 类图关系(泛化 、继承、实现、依赖、关联、聚合、组合)
通过阅读本系列文章,以轻松愉快的方式学习设计模式和编程思想。本系列文章每一章都是单独成文,可从任意一篇文章开始。
虽然说设计模式与编程语言没有关系,它是对面向对象思想的灵活应用和高度概括,你可以用任何一种语言来实现它,但总归是需要用一种语言进行举例的。本系列文章的所有示例代码均使用 Python 编写,为什么选择 Python,主要基于以下两个原因。
设计模式于1995年被 GoF 提出,被广泛应用于热门的面对象语言。目前用 Java、C++ 描述的设计模式的书籍和资料已经非常多了,但用 Python 来描述的真是太少了。我在当当上搜索了一下“Python 设计模式”,只有那零星的几本书。而作为已经挤进 Top4 的 Python 语言,这明显是不够的。Python 已经越来越成熟,也越来越多地被使用,作为一个有技术追求的 IT 人有必要了解一下基于 Python 代码设计。
C 语言诞生于1972年,确随着 Unix 的诞生才深深植根于各大操作系统;
C++ 诞生于1983年,确因微软的可视化桌面操作系统才得以广泛传播;
Java 诞生于1995年,确因互联网的迅速崛起才变得家喻户晓;
Python 诞生于1991年,而下一场技术革命已然开始,AI 时代已然成风。在 AI 领域中已经被广泛使用的 Python 必将成为下一个时代的第一开发语言!
最热门的 AI 开源框架 PyTorch 和 TensorFlow 都已经采用了 Python 作为接口和开发语言。除此之外,还有一堆 AI 相关的框架库,也都纷纷采用,如 AIMA、pyDatalog、SimpleAI、PyBrain、PyML 等。
作为这么一门有前途的语言,必然是要去学习和使用的。
(这一部分内容,如果你已经熟悉 Python,可直接跳过。)
Python 崇尚优美、清晰、简单,是一个优秀并广泛使用的语言。
与 Java 和 C++ 这些语言相比,Python 最大的两个特点是:
刚转过来的时候可能会有点不适,用一段时间就好了!
个人觉得,在所有的高级计算机语言中,Python 是最接近人类自然语言的。Python 的语法、风格都与英文的书写习惯非常接近,Python 的这种风格被称为 Pythonic。如条件表达式,在 Java 和 C++ 中是这样的:
int min = x < y ? x : y
而 Python 是这样的:
min = x if x < y else y
有没有觉得第二种方式更接近人类的自然思维?
数据类型
Python 是一种弱类型的语言,变量的定义不需要在前面加类型说明, 而且不同类型之间可以方便地相互转换。Python 有五个标准的数据类型:
其中 List,Tuple,Dictionary 为容器,将在下一部分介绍。Python 支持四种不同的数字类型:int(有符号整型)、long(长整型)、float(浮点型)、complex(复数)。
每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。
Demo:
age = 18 # int
weight = 62.51 # float
name = "Tony" # string
print("age:", age)
print("weight:", weight)
print("name:", name)
# 变量的类型可以直接改变
age = name
print("age:", age)
a = b = c = 5
# a,b,c三个变量指向相同的内存空间,具有相同的值
print("a:", a, "b:", b, "c:", c)
print("id(a):", id(a), "id(b):", id(b), "id(c):", id(c))
结果:
age: 18
weight: 62.51
name: Tony
age: Tony
a: 5 b: 5 c: 5
id(a): 1457772400 id(b): 1457772400 id(c): 1457772400
List(列表)
List(列表)是 Python 中使用最频繁的数据类型,用 [ ] 标识。
列表可以完成大多数集合类的数据结构实现。类似于 Java 中的 ArrayList,C++ 中的 Vector。此外,一个 List 中还可以同时包含不同类型的数据,支持字符、数字、字符串,甚至可以包含列表(即嵌套)。
列表中值的切割也可以用到变量 [头下标:尾下标] ,就可以截取相应的列表,从左到右索引默认 0 开始,从右到左索引默认 -1 开始,下标可以为空表示取到头或尾。
加号(+)是列表连接运算符,星号(*)是重复操作。
Demo:
list = ['Thomson', 78, 12.58, 'Sunny', 180.2]
tinylist = [123, 'Tony']
print(list) # 输出完整列表
print(list[0]) # 输出列表的第一个元素
print(list[1:3]) # 输出第二个至第三个元素
print(list[2:]) # 输出从第三个开始至列表末尾的所有元素
print(tinylist * 2) # 输出列表两次
print(list + tinylist) # 打印组合的列表
list[1] = 100 # 修改第二个元素的值
print(list) # 输出完整列表
list.append("added data")
print(list) # 输出增加后的列表
结果:
['Thomson', 78, 12.58, 'Sunny', 180.2]
Thomson
[78, 12.58]
[12.58, 'Sunny', 180.2]
[123, 'Tony', 123, 'Tony']
['Thomson', 78, 12.58, 'Sunny', 180.2, 123, 'Tony']
['Thomson', 100, 12.58, 'Sunny', 180.2]
['Thomson', 100, 12.58, 'Sunny', 180.2, 'added data']
Tuple(元组)
Tuple(元组)是另一个数据类型,元组用“()”标识,内部元素用逗号隔开。元组不能二次赋值,相当于只读列表,用法与 List 类似。Tuple 相当于 Java 中的 final 数组,C++ 中的 const 数组。
Demo:
tuple = ('Thomson', 78, 12.58, 'Sunny', 180.2)
tinytuple = (123, 'Tony')
print(tuple) # 输出完整元组
print(tuple[0]) # 输出元组的第一个元素
print(tuple[1:3]) # 输出第二个至第三个的元素
print(tuple[2:]) # 输出从第三个开始至列表末尾的所有元素
print(tinytuple * 2) # 输出元组两次
print(tuple + tinytuple)# 打印组合的元组
# tuple[1] = 100 # 不能修改元组内的元素
结果:
('Thomson', 78, 12.58, 'Sunny', 180.2)
Thomson
(78, 12.58)
(12.58, 'Sunny', 180.2)
(123, 'Tony', 123, 'Tony')
('Thomson', 78, 12.58, 'Sunny', 180.2, 123, 'Tony')
Dictionary(字典)
Dictionary(字典)是 python 中除列表以外最灵活的内置数据结构类型。字典用”{ }”标识,字典由索引(key)和它对应的值 value 组成。相当于 Java 和 C++ 中的 Map。
列表是有序的对象集合,字典是无序的对象集合。两者之间的区别在于:字典当中的元素是通过键来存取的,而不是通过偏移存取。
Demo:
dict = {}
dict['one'] = "This is one"
dict[2] = "This is two"
tinydict = {'name': 'Tony', 'age': 24, 'height': 177}
print(dict['one']) # 输出键为'one' 的值
print(dict[2]) # 输出键为 2 的值
print(tinydict) # 输出完整的字典
print(tinydict.keys()) # 输出所有键
print(tinydict.values())# 输出所有值
结果:
This is one
This is two
{'name': 'Tony', 'age': 24, 'height': 177}
dict_keys(['name', 'age', 'height'])
dict_values(['Tony', 24, 177])
类的定义
使用 class 语句来创建一个新类,class 之后为类的名称并以冒号结尾,如下实例:
class ClassName:
'类的帮助信息' #类文档字符串
class_suite #类体
类的帮助信息可以通过 ClassName.doc 查看,class_suite 由类成员、方法、数据属性组成。如:
class Test:
"这是一个测试类"
def __init__(self):
self.__ivalue = 5
def getvalue(self):
return self.__ivalue
其中 init 为初始化函数,相当于构造函数。
访问权限:
foo:定义的是特殊方法,一般是系统定义名字 ,类似 init() 之类的。
_foo:以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于 from module import 。
__foo:双下划线的表示的是私有类型(private)的变量,只能是允许这个类本身进行访问了。
类的继承:
继承语法结构
class 派生类名(基类名):
类体
python 中继承中的一些特点:
1.在继承中基类的构造(init()方法)不会被自动调用,它需要在其派生类的构造中亲自专门调用。
2. 在调用基类的方法时,需要使用 super() 前缀。
3. Python 总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)。
如果在继承元组中列了一个以上的类,那么它就被称作“多重继承”。
基础重载方法
Python 的类中有很多内置的方法,我们可以通过重写这些方法来实现一些特殊的功能。这些方法有:
序号 方法 描述 简单的调用
1 init ( self [,args…] ) 构造函数 obj = className(args)
2 del( self ) 析构方法, 删除一个对象 del obj
3 repr( self ) 转化为供解释器读取的形式 repr(obj)
4 str( self ) 用于将值转化为适于人阅读的形式 str(obj)
5 cmp ( self, x ) 对象比较 cmp(obj, x)
Demo 让你顿悟
我们将一段 Java 的代码对应到 Python 中来实现,进行对比阅读,相信你很快就能明白其中的用法。
Java 代码:
class Person {
public static int visited;
Person(String name, int age, float height) {
this.name = name;
this.age = age;
this.height = height;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void showInfo() {
System.out.println("name:" + name);
System.out.println("age:" + age);
System.out.println("height:" + height);
System.out.println("visited:" + visited);
Person.visited ++;
}
private String name;
protected int age;
public float height;
}
class Teacher extends Person {
Teacher(String name, int age, float height) {
super(name, age, height);
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public void showInfo() {
System.out.println("title:" + title);
super.showInfo();
}
private String title;
}
public class Test {
public static void main(String args[]) {
Person tony = new Person("Tony", 25, 1.77f);
tony.showInfo();
System.out.println();
Teacher jenny = new Teacher("Jenny", 34, 1.68f);
jenny.setTitle("教授");
jenny.showInfo();
}
}
对应的 Python 代码:
class Person:
"人"
visited = 0
def __init__(self, name, age, height):
self.__name = name
self._age = age
self.height = height
def getName(self):
return self.__name
def getAge(self):
return self._age
def showInfo(self):
print("name:", self.__name)
print("age:", self._age)
print("height:", self.height)
print("visited:", self.visited)
Person.visited = Person.visited +1
class Teacher(Person):
"老师"
def __init__(self, name, age, height):
super().__init__(name, age, height)
self.__title = None
def getTitle(self):
return self.__title
def setTitle(self, title):
self.__title = title
def showInfo(self):
print("title:", self.__title)
super().showInfo()
def testPerson():
"测试方法"
tony = Person("Tony", 25, 1.77)
tony.showInfo()
jenny = Teacher("Jenny", 34, 1.68);
jenny.setTitle("教授");
jenny.showInfo();
testPerson()
自己测试一下,会发现结果是一样的:
name: Tony
age: 25
height: 1.77
visited: 0
title: 教授
name: Jenny
age: 34
height: 1.68
visited: 1
【故事剧情】
刚刚大学毕业的 Tony 只身来到北京这个硕大的城市,开始了北漂的生活。但刚刚毕业的他身无绝技、包无分文,为了生活只能住在沙河镇一个偏僻的村子里,每天坐着程序员专线(13号线)来回穿梭于昌平区与西城区……
在一个寒冷的冬天,下班之后要坐2个小时的地铁+公交才能回到住处,Tony 拖着疲惫的身体回到家。准备洗一个热水澡暖暖身体,耐何简陋的房子中用的还是90年代的热水器。因为热水器没有警报更没有自动切换模式的功能,所以烧热水必须得守着;不然时间长了成杀猪烫,时间短了又冷成狗。无奈的 Tony 背靠着墙,头望着天花板,深夜中做起了白日梦:一定要努力工作,过两个月我就可以自己买一个智能热水器了:水烧好了就发一个警报,我就可以直接去洗操。还要能自己设定模式,既可以烧开了用来喝,可以烧暖了用来洗澡……
Tony 陷入白日梦中……他的梦在实现世界里虽然不能立即实现,但在程序世界里可以。程序来源于生活,下面我们就用代码来模拟 Tony 的白日梦。
源码示例:
class WaterHeater:
"热水器:战胜寒冬的有利武器"
def __init__(self):
self.__observers = []
self.__temperature = 25
def getTemperature(self):
return self.__temperature
def setTemperature(self, temperature):
self.__temperature = temperature
print("current temperature is:", self.__temperature)
self.notifies()
def addObserver(self, observer):
self.__observers.append(observer)
def notifies(self):
for o in self.__observers:
o.update(self)
class Observer:
"洗澡模式和饮用模式的父类"
def update(self, waterHeater):
pass
class WashingMode(Observer):
"该模式用于洗澡用"
def update(self, waterHeater):
if waterHeater.getTemperature() >= 50 and waterHeater.getTemperature() < 70:
print("水已烧好,温度正好!可以用来洗澡了。")
class DrinkingMode(Observer):
"该模式用于饮用"
def update(self, waterHeater):
if waterHeater.getTemperature() >= 100:
print("水已烧开!可以用来饮用了。")
测试代码:
def testWaterHeater():
heater = WaterHeater()
washingObser = WashingMode()
drinkingObser = DrinkingMode()
heater.addObserver(washingObser)
heater.addObserver(drinkingObser)
heater.setTemperature(40)
heater.setTemperature(60)
heater.setTemperature(100)
输出结果:
current temperature is: 40
current temperature is: 60
水已烧好,温度正好!可以用来洗澡了。
current temperature is: 100
水已烧开!可以用来饮用了。
这个代码非常简单,水烧到50-70度时,会发出警告:可以用来洗澡了!烧到100度也会发出警告:可以用来喝了!在这里洗澡模式和饮用模式扮演了监听的角色,而热水器则是被监听的对象。一旦热水器中的水温度发生变化,监听者都能及时知道并做出相应的判断和动作。其实这就是程序设计中监听模式的生动展现。
监听模式又名观察者模式,顾名思意就是观察与被观察的关系,比如你在烧开水得时时看着它开没开,你就是观察者,水就是被观察者;再比如说你在带小孩,你关注她是不是饿了,是不是喝了,是不是撒尿了,你就是观察者,小孩就是被观察者。
观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。当你看这些模式的时候,不要觉得陌生,它们就是观察者模式。
观察者模式一般是一种一对多的关系,可以有任意个(一个或多个)观察者对象同时监听某一个对象。监听的对象叫观察者(后面提到监听者,其实就指观察者,两者是等价的),被监听的对象叫被观察者(Observable,也叫主题 Subject)。被观察者对象在状态或内容发生变化时,会通知所有观察者对象,使它们能够做出相应的变化(如自动更新自己的信息)。
上面的示例代码还是相对比较粗糙,我们可以对它进行进一步的重构和优化,抽象出监听模式的框架模型。
class Observer:
"观察者的基类"
def update(self, observer, object):
pass
class Observable:
"被观察者的基类"
def __init__(self):
self.__observers = []
def addObserver(self, observer):
self.__observers.append(observer)
def removeObserver(self, observer):
self.__observers.remove(observer)
def notifyObservers(self, object = 0):
for o in self.__observers:
o.update(self, object)
类图
上面的代码框架可用类图表示如下:
addObserver,removeObserver 分别用于添加和删除观察者,notifyObservers 用于内容或状态变化时通知所有的观察者。因为 Observable 的 notifyObservers 会调用 Observer 的 update 方法,所有观察者不需要关心被观察的对象什么时候会发生变化,只要有变化就是自动调用 update,只需要关注 update 实现就可以了。
有了上面的代码框架之后,我们要实现示例代码的功能就会更简单了。最开始的示例代码我们假设它为 version 1.0,那么再看看基于框架的 version 2.0 吧。
class WaterHeater(Observable):
"热水器:战胜寒冬的有利武器"
def __init__(self):
super().__init__()
self.__temperature = 25
def getTemperature(self):
return self.__temperature
def setTemperature(self, temperature):
self.__temperature = temperature
print("current temperature is:", self.__temperature)
self.notifyObservers()
class WashingMode(Observer):
"该模式用于洗澡用"
def update(self, observable, object):
if isinstance(observable,
WaterHeater) and observable.getTemperature() >= 50 and observable.getTemperature() < 70:
print("水已烧好,温度正好!可以用来洗澡了。")
class DrinkingMode(Observer):
"该模式用于饮用"
def update(self, observable, object):
if isinstance(observable, WaterHeater) and observable.getTemperature() >= 100:
print("水已烧开!可以用来饮用了。")
测试代码不用变。自己跑一下,会发现输出结果和之前的是一样的。
设计要点
在设计观察者模式的程序时要注意以下几点:
推模型和拉模型
观察者模式根据其侧重的功能还可以分为推模型和拉模型。
推模型:被观察者对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。一般这种模型的实现中,会把被观察者对象中的全部或部分信息通过 update 的参数传递给观察者 [update(Object obj) ,通过 obj 参数传递]。
如某应用 App 的服务要在凌晨1:00开始进行维护,1:00-2:00期间所有服务将会暂停,这里你就需要向所有的 App 客户端推送完整的通知消息:“本服务将在凌晨1:00开始进行维护,1:00-2:00期间所有服务将会暂停,感谢您的理解和支持!” 不管用户想不想知道,也不管用户会不会在这段期间去访问,消息都需要被准确无误地通知到。这就是典型的推模型的应用。
拉模型:被观察者在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到被观察者对象中获取,相当于是观察者从被观察者对象中拉数据。一般这种模型的实现中,会把被观察者对象自身通过 update 方法传递给观察者 [update(Observable observable ),通过 observable 参数传递 ],这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。
如某应用 App 有新的版本推出,则需要发送一个版本升级的通知消息,而这个通知消息只会简单地列出版本号和下载地址,如果你需要升级你的 App 还需要调用下载接口去下载安装包完成升级。这其实也可以理解成是拉模型。
推模型和拉模型其实更多的是语义和逻辑上的区别。我们上面的代码框架,从接口 [update(self, observer, object)] 上你应该知道是可以同时支持推模型和拉模型的。推模型时,observer 可以传空,推送的信息全部通常 object 传递;拉模型时,observer 和 object 都传递数据,或只传递 observer,需要更具体的信息时通过 observer 引用去取数据。
学习设计模式,更应该领悟其设计思想,不应该应该局限于代码的层面。 观察者模式还可以用于网络中的客户端和服务器,比如手机中的各种 App 的消息推送,服务端是被观察者,各个手机 App 是观察者,一旦服务器上的数据(如 App 升级信息)有更新,就会被推送到手机客户端。在这个应用中你会发现服务器代码和 App 客户端代码其实是两套完全不一样的的代码,它们是通过网络接口进行通迅的,所以如果你只是停留在代码层面是无法理解的!
导读 关于设计模式的思考
第01课 生活中的监听模式:坑爹的热水器
第02课 生活中的状态模式:人与水的三态
第03课 生活中的单例模式:你是我生命的唯一
第04课 生活中的职责模式:我的假条去哪了
第05课 生活中的中介模式:找房子问中介
第06课 生活中的代理模式:帮我拿一下快递
第08课 生活中的装饰模式:你想怎么穿就怎么穿
第09课 生活中的工厂模式:你要拿铁还是摩卡
第10课 生活中的迭代模式:下一个就是你了
第11课 生活中的组合模式:自己组装电脑