《测试驱动数据库开发》目录—导读


0db053eff7c2186602d507bf9611eb757ed07c9c

版权声明
测试驱动数据库开发
Authorized translation from the English language edition, entitled Test-Driven Database Development: Unlocking Agility, 9780321784124 by Max Guernsey, III, published by Pearson Education, Inc., publishing as Addison-Wesley, Copyright © 2013 by Pearson Education, Inc.

All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc.

CHINESE SIMPLIFIED language edition published by PEARSON EDUCATION ASIA LTD. and POSTS & TELECOM PRESS Copyright © 2014.

本书中文简体字版由Pearson Education Asia Ltd.授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

本书封面贴有Pearson Education(培生教育出版集团)激光防伪标签,无标签者不得销售。

版权所有,侵权必究。


测试驱动数据库开发
我是一位具有多年测试驱动开发(Test-Driven Development, TDD)经验的实践者,并且以编写、讲授和教授TDD的一些课程作为我在Net Objectives软件咨询公司的部分工作职责。在课堂上讲课或在技术大会做讲座时,我经常被问及这样一个问题:TDD对于解决业务逻辑层和其他“中间层”(middle-tier)的问题似乎很完美,但是对于表示层(UI)和持久化层(数据库)又如何呢?

我的回答通常是:对于上面提到的表示层和持久化层,都分别存在下述两个问题。

1.如何管理中间层对其他两个层的依赖关系,从而使中间层的行为更加容易测试?

2.如何测试驱动其他两个层?

第一个问题大体上与接口定义、mock对象、设计模式和良好的关注点分离相关,这是有关技术的问题。对于将业务逻辑从其依赖的对象中分离出来,TDD社区已经有许多成熟的、经过验证的技术。我的课程的大量时间都花在传授这些“技巧”上了。

但是对于第二个问题——测试驱动用户界面和数据库开发——我总是说这些在很大程度上是尚未解决的问题。这并不是说我们不知道如何测试用户界面和数据库,而是我们不知道如何用测试驱动它们,不知道如何根据TDD的要求编写分离、快速和合适粒度的测试代码。面对系统中上述其他两个层时,我们不得不满足于更加传统的测试。这一直是我对上述问题一贯的回答。

据我所知,对于UI,测试驱动问题依然是尚未解决的。但是对于数据库,Max Guernsey已经找到了解决测试驱动问题的方法。

在我的《浮现式设计》一书中,我谈到了许多系统设计和自然界进化过程的相似性问题。如果你把自己的源代码想象成系统的“DNA”,把可执行文件想象成“生物个体”,那么这个比喻真的很恰当。DNA用来生成个体。要改变物种,首先DNA要发生改变,然后下一个生成的个体才会改变。大自然不进化个体,但是大自然让每一代的物种都发生变化。这与软件的编码/编译/运行的特点很相似。我们抛掉.exe文件,改变源代码,然后编译器就产生了一个新的、不同的、希望能更好的可运行文件。正因为如此,源代码为王。源代码是我们不可或缺的。

Max对上述问题的认识源自于他所认为的数据库不同于源代码的观点,这一点至少从我与Max的谈话中就可以看出。如果把数据库schema比作DNA,那么一个安装好的正在运行的数据库实例(存储了所有企业的关键数据)就好比一个个体,这时我们就不能再用大自然的比喻了。我们能很容易地重新创建数据库schema——只要运行DDL脚本或其他等价代码就可以了。但是一个给定的、安装好的、“活”的数据库包含的信息和知识,在数据库结构发生改变时必须保持完好。

因为上述原因,进化的范型(paradigm)就不适用了。在数据库中,个体是至关重要的。我们不能简单地抛弃个体,然后用一个改变了的DNA来重建。这再一次表明,大自然不会进化个体。但是大自然中还存在另一种范型能够解释数据库的问题,那就是变形(morphing),这正是一个生物个体从一种生命阶段转变到另一种阶段的方式,如蝌蚪变成了青蛙。在研究了上述认识及由此引发的所有结果后,Max发展了一种审视数据库的真正革命性的视角:如何创建、改变并最终测试驱动数据库开发。他将TDD实践者所喜爱的清晰、安全和有效的开发方法传授给了数据库开发实践者。

本书是一部开创性的著作,Max发现了数据库开发的“罗塞塔石碑”(Rosetta Stone)。当你在面对系统的持久化层时,如果能认真地跟随他,你将踏上一条能有效完成工作任务的康庄大道,你将获得知识、工具和整体方案,从而让测试驱动数据库开发成为可能。

——Scott Bain

Net Objectives软件咨询公司高级咨询师

致谢
测试驱动数据库开发
本书的背后有众多人几乎跨越10年的帮助。

首先感谢我的妻子Amy,在本书创作的过程中,她一直是本书的动力和校验的源泉。在我们一起度过的15年中,她几乎校对了我写的每一段文字。

Bill Zietzke在本书创作的初期提供了帮助,正是我和他之间的一次谈话,引发了本书的创作灵感,当时我们二人在位于华盛顿州贝勒维市的一家保险公司做软件开发。

Beau Bender协助发现了我设计的用于控制数据库和客户端之间耦合关系的机制,为此我感谢他。

同时也感谢我的好朋友和导师Scott L. Bain,正是他最先鼓励我写作并出版图书的,也正是他的影响使得我达到了这个目的。在这一方面,他一直通过提出有价值的问题、批评和观察来扮演一个重要的角色。

在我职业生涯的主要时间里,Alan Shalloway也是我的一位朋友和导师,他帮助我判断我的第一个想法——向所有人传授所有事情——是不正确的。我确信,如果没有他非常有建设性的批评,你或许正在读一本完全不同的书,并可能由一个完全不同的作者所写。

Alan和Scott在我最近作为专业软件开发者的开发工作中扮演了关键性的角色。他们之中每一位都能看到我所具备的技能,并帮助我继续发展这些技能,同时能用一种避免我对新的事物和想法产生抵触的方式,为我提供我所欠缺的技能。

他们对于如何与人相处的建议给我带来了同样甚至更大的价值。在撰写本书的时候,我还不完全是一个受人欢迎的、容易促成共识的人,但是与遇到Scott和Alan之前相比,我在说服人的方面已经有了很大的改善。如果没有他们的指导,没有他们向我展示用易于接受的形式来分享我们的知识是多么的重要,我可能开始的时候不会把写书的事放在心上。

我最近结交的一些朋友和同事甘愿充当“小豚鼠”,为我试读了本书各章的早期版本。这让我能够很快获得反馈而采取行动,并帮我决定对本书的表现形式进行第二次修改。如果没有Seth McCarthy和Michael Gordon Brown的反馈,我就会试图撰写一部更厚的有关“敏捷”数据库开发的书,而不是你现在正在阅读的这本更加有专注性和技术性的书。

不言而喻,我父母也为本书的撰写承担了部分责任,因为如果没有他们,也就没有我。然而,在我还是年轻程序员进行软件开发的时候,我父亲扮演了特殊的角色,如果没有他的影响,或许我就像数学家或华尔街分析师那样没有用处。

让数据库应用开发不再裸奔
测试驱动数据库开发
1993年,我从大学计算机专业毕业后,开启我的IT职业生涯的第一份工作就是在一家国营单位,用dBase III做MIS的数据库应用开发。从那以后,我做了11年的程序员,先后开发和维护过内容管理系统(CMS)、电信运营增值服务系统、通信设备网络管理系统和电子商务系统。这些系统无一例外都使用了像Oracle和MySQL这样的数据库来保存持久化数据。

在这些系统中,有些系统甚至是以数据库的设计为核心来驱动应用程序代码开发的。也就是说,一旦客户的需求到了我们这些程序员手里,先找出需求中的实体(entity),再分析这些实体之间的关系,并画出E-R图(entity-relationship diagram,实体关系图),然后确定各个实体的属性,并找出主键,最后根据这份E-R图在数据库中生成数据库表,之后就可以用像Java这样的编程语言来进行应用系统的开发了。

上面这样看似完美的开发过程,被印在20多年前我在大学修的“关系型数据库管理系统”的教科书上,并多年来一直指导着我做数据库应用开发。但直到最近,我才意识到,这种方法无法解决实际数据库应用开发工作中的最大的难题:一套数据库应用系统交付给相互之间具有不同需求的多个客户时所出现的版本控制问题。回想我所工作的一家软件公司,曾为另一家在国内某领域领先的“巨无霸”甲方公司开发一套以数据库为核心的应用系统。该系统陆续在这家甲方公司的国内8个省得到了部署。俗话说:“龙生九子,各有不同。”这8个省所部署的数据库应用系统虽然都来自最初的那个系统,但是每个省的需求各不相同,这家软件公司只好成立8个庞大团队来维护这8个省的系统。在不堪重负地挣扎了很长一段时间后,这家软件公司下决心要建立一个新系统来统一这8个系统,以减少维护成本。但不幸的是,这个新系统最后也无奈地成为“龙的第九个儿子”。

所幸的是,本书作者Guernsey先生不仅在工作中解决了上述问题,还把解决方案条理清晰地写在了这本书中,功德无量!Guernsey先生洞察到,导致上述问题的原因是各个数据库实例的数据库结构因需求变化而发生变更时,数据库原先保存的数据的版本在此期间没有得到有效的管理。Guernsey先生敏锐地观察到,当发生变更时,面向对象编程中的“类”与数据库开发领域中的“数据库实例的结构”会表现出不同的特点。前者在变更时不需要管理历史信息,只要把运行在服务器上的服务停下来,把旧的“类”替换成新的“类”,再重新启动服务就好了。但是,后者在发生变更时,不可能仅仅把数据库结构替换为新的,而把原有结构中所保存的数据全都清除掉。数据库实例的结构在发生变更时,需要保持以前存储的数据。毕竟,数据库的价值就体现在这些被保存的宝贵的历史数据中。

在洞察到数据库应用开发的上述特点之后,Guernsey先生开创性地把在面向对象编程中得到广泛应用的测试驱动开发(Test-Driven Development,TDD)的理念,引入到数据库应用开发——这个几乎还处于类似面向机器编码的“汇编语言”时代的蛮荒之地,就像本书英文版封面所展示的那样,在一片广袤的沙漠中,赫然出现了一片环绕一眼清泉的生机勃勃的绿洲。在把TDD的理念与在数据库应用开发时保存历史数据这个特点相结合后,就如同给以前无奈地进行“裸奔”的传统数据库应用开发的程序员们穿上了“测试”这层坚实的铠甲。

Guernsey先生不仅讨论了传统关系型数据库的测试驱动开发方法和技术,还在本书最后一章将这些方法和技术运用到XML应用、文件系统及其他对象目录和序列化数据对象之上。相信本书所阐述的测试驱动数据库开发的概念和原则,都能适用于任何需要对数据进行各种形式的持久化应用系统的测试驱动开发之上。

Guernsey先生的上述开创性的工作使我决定暂时中断写自己的《驯服烂代码》一书,而花费4个月中全部的空闲时间来翻译本书。我在翻译本书时,力求用通顺的语句来表达作者的原意,争取让自己阅读翻译后的文字就如同喜欢阅读原文一样。如有翻译不当之处,恳请通过我运营的“北京设计模式学习组”的微信公众号bjdp.org给予指点,以求改进。我个人也会在该微信公众号中创建和维护本书中文版的勘误表,以方便读者。

本书的翻译工作得以在4个月内顺利完成,离不开我妻子薛静、儿子乐乐、岳母大人及其他亲属的理解和支持;离不开我20年前的大学同学杨光从远在万里的大洋彼岸给我的有关美国“对冲基金”公司运作模式的专业介绍;离不开人民邮电出版社杨海玲编辑对我的信任;离不开我的微信、微博和北京设计模式学习组(bjdp.org)这些圈子里各位亲友的关心;离不开我的父母的无私养育之恩和点滴帮助之举。

最后,希望Guernsey先生所带来的测试驱动数据库开发的理念,能为在中文世界里辛苦加班的程序员带来能够化解数据库应用开发的种种烦恼的一剂良药。

——伍斌,

独立匠艺程序员,公益免费编程操练社区“bjdp.org北京设计模式学习组”创办者

作者介绍
测试驱动数据库开发
Max Guernsey目前是Hexagon软件有限公司的管理成员,他有15年的专业软件开发经验,这期间的几乎一半时间,他一直就敏捷和测试驱动数据库开发主题,写博客、写作和发表演讲。

在Max的职业生涯的大部分时间里,他作为咨询师,使用多种编程和数据库技术,为各种不同行业的软件公司提供指导。在上述大部分工作中,他花费数月甚至数年的时间帮助团队运用像测试驱动开发、面向对象的设计、验收测试驱动开发和敏捷规划这样前沿的技术。

Max总是作为一位“实操型”的顾问和团队一起长期工作,以帮助他们构建软件和技能。这一系列多样且深入的工作,帮助他获得了那些阻碍大多数敏捷团队的、与数据库相关的测试和设计的问题的独特理解。从2005年以来,他一直在思考、写作、写博客、演讲和创建面向开发人员的软件,以解决上述问题。

读者可以通过Max的邮箱max@hexsw.com与他联系。他也会定期在他的Twitter(@MaxGuernseyIII)和博客(maxg3prog.blogspot.com)上发表文章。

前言
测试驱动数据库开发
本书讲述如何将测试驱动开发的概念应用于数据库开发。

谁应该阅读本书
这个问题的简短回答是:“任何想要学习如何对数据库进行测试驱动开发,并且甘愿为此辛苦工作的人。”详细的回答见下文。

本书主要是针对这样的程序员,他们以某种方式负责基于至少一个数据库设计的开发工作,其次是针对那些认为自己主要是做数据库开发,并且对在其开发流程中加入测试驱动开发有兴趣的人。

这绝对不是削弱上面第二组人的价值。在写本书时,本书中涉及的技术是构筑在获得上述第一组人广泛接受的原则和方法的基础之上的,而且仍然努力地从第二组人那里获取推动力。这不是说事情就不会发生变化,我希望会,但是如果我试图仅仅关注从第一组人那里获取技术的原则的话,本书就可能会失去平衡。

本书的目标是帮助人们将测试驱动开发过程运用到数据库开发的新领域,在这些新领域中,施加在TDD上的影响力会多多少少与其他领域有所不同。

如果你阅读了本书,并能够持续地通过测试来驱动自己的数据库的开发,那将是双赢的结果。如果你开始使用这些原则来将其他的技术移植过来,比如面向模式(pattern-oriented)的开发,那么你就会得到双倍的效果。如果你开始将学到的原则移植到涉及长期保存数据的其他领域,如系统安装程序,那么你会有更多收获。

需要做什么
为了回答这个问题,我从测试驱动开发为什么能够解决问题讲起,然后再看为什么想要在数据库世界获取测试驱动开发的推动力会有那么多麻烦。注意,我不是认为数据库开发基本上是未经测试的,只是根据我的经验判断,数据库开发既没有达到持续测试,也没有达到测试驱动的程度。

使数据库测试变得很困难的主要原因是类(class)概念的缺失和错位,甚至是最“狂野的西部风格”(Wild-West-style)的现代编程语言也支持类与实例的概念。

数据库引擎要么通过为数据结构提供类来做些小恩小惠的工作,要么就干脆对建立真正可以测试的类完全无所作为。对我来说,出现上述问题的原因是,人们通常没有认识到什么是数据库领域真正的头等公民对象(first-class object)——数据库本身。所以,第一步是建立一个数据库对象。

变化是测试驱动开发过程的核心。为了支持新的需求和一个不断扩充的产品特性集合的可测试性,需要经常改变设计。一个使测试驱动开发难以被采用和支持的影响力是:与在其他种类的设计中相比,变化在数据库设计中,被认为更加危险。

如果在你的中间层中搞乱了一个设计的变化,就必须回滚。如果你在数据层搞乱了一个设计的变化,就得删除存储在其中的有价值的知识。解决方案是不仅测试你的数据库做了什么,还要测试数据库是如何变化的。

另一个在改变数据库设计时需要面对的问题是,数据库与其客户端之间的耦合是弱遵循的(weakly enforced)1。可能存在这种情况,即在修改了数据库接口的同时,却没有意识到你已经将下游应用程序也搞坏了,并使该状况持续了很长时间。通过运用数据库的类来固化数据库设计与客户端的关联关系,能够缓解上述风险。

通过创建对变化进行有效控制的强大的数据库的类,解决了数据库世界中阻碍TDD应用的基本问题,就好比在支持现代软件开发实践方面,把开发人员带入了20世纪90年代早期。

现在已经进入了21世纪,你必须得走得更远一点。为此,我会帮助你理解,测试的范围应该是软件行为的验证。我会针对数据库开发来定义什么是软件行为。

使浮现式设计成为可能
我也会展示如何将长期的可维护性最大化,这可以通过将数据库的范围限定到满足你当前的需求,并使用本书描述的技术来使将来能容易地增加更多的产品特性,也就是说,我将帮助你消除因为没有提前做数据库设计规划而产生的恐惧。

没有一个过程是完美的,即使有完美的过程,执行过程的人也没有一个是完美的。尽管你尽了所有努力去避免错误,但是错误总会发生。如果你在做真正的测试驱动数据库开发,很多时候错误会以下面这种形式表现出来,即某种行为没有在你的测试套件(test suite)中被表达出来。我将展示如何最佳地修正错误。

了解了如何为数据库的类开发和编写测试,同时保证数据库的设计尽量简单和贴切(problem-appropriate),将会把数据库开发带入21世纪,从而让数据库开发仅仅落后于现代面向对象开发一个短暂的时代。

开发现代化
支持测试驱动过程的数据库开发现代化的最终阶段,包含了采用和适应我称之为“高级的”面向对象的一套方法。

掌握“类/数据库”级别的设计是第一阶段。如果可以在一个大数据库的设计和两个小数据库的设计之间进行选择,你应该选择那两个小数据库的设计。如果数据库技术不允许,那么使用组合(composition)方法来将两个逻辑的数据库实例置于一个物理的数据库实例中。

另一个重要的行为是重构。你需要保持数据库的设计在数据库的整个生命周期中对所涉及的问题都十分贴切。这意味着设计一定要从小的设计开始,当你的设计包含了越来越多的问题领域的内容,设计也需要跟着改变样子。我将为你展示如何在测试驱动过程中重构数据库设计。

这些就是你为一个典型和崭新的数据库设计做测试驱动数据库开发所需要做的所有事情。本书其余部分是专门帮助你对付那些出生在一个没有测试的环境中、偏离了被控制和可测试轨道、用非面向数据库应用开发过程进行开发的数据库,并与之进行“缠斗”。

做测试驱动数据库开发并不容易,尤其是开始的时候。如果你已经牢固掌握了“正规的”测试驱动开发方法,那么做测试驱动数据库开发就完全没有什么令人惊讶的感觉。然而,不管你学习新技能的速度有多快,学习TDD会比其他技能花费更长的时间。

花费更长的时间意味着带来更高的价值。读完本书,你将对测试驱动数据库开发有一个理论上的理解。1个月、3个月甚至18个月之后,你将精通这门技能。

当精通这门技能之后,你将能够满怀信心地频繁、快速和安全地改变自己的数据库设计,能够在恰好你需要的时候构建恰好是你需要的系统。最终的结果会使数据库的改进能够成为软件开发流程中一个运转流畅的环节。另外,你将能够保持自己的数据库设计整洁、简单和快速。

逐章内容简介
下面对本书章节逐一做一下介绍。

在第1章中,我解释为什么我写这本书,谁应该阅读这本书,什么是在做数据库设计时挡住通往TDD的拦路虎。我之所以写本书,是因为真正的测试驱动开发没有从数据库领域中真正地获得任何推动力。本书的目标读者是那些认为自己是软件开发人员,并且在工作中必须与数据库设计打交道的人。在数据库开发领域中,最大的问题是不存在一个类的清晰的概念,而类是传统的TDD开发中的核心元素。

为了构建一个数据库的类,需要把那些在数据库上运行的脚本原封不动地作为永久记录保存起来,并用一个清晰的方式跟踪哪一条脚本已经运行。用基础设施工具软件能够确保每一个数据库的类的实例是用完全相同的方式创建出来的。在第2章中,我将展示如何能做到这一点。

为数据库设计实施可持续操作的TDD过程会涉及许多步骤。第一步是定义一个基本的TDD过程,以便于在开发后期能在此基础上添加更深入、更面向数据的行为。在第3章中,我会展示如何针对一个数据库的类做一些简单的测试驱动开发。

在第4章中,我将展示如何克服一个很大的障碍:与变化相关联的风险。频繁地引入变化会让很多人心生恐惧。令人恐惧的根源是数据库存储了大量有价值的数据,因为匆忙地做出变更而造成的数据丢失在大部分情况下是不可接受的。如果你不仅能测试数据库的行为,而且能测试构建和修改数据库的脚本,那么就能够征服恐惧和风险。

在软件产业中,数据库是最被其他子系统所依赖的子系统,并且对一个数据库设计的修改可能会造成不可预知的后果。这个问题的核心是许多大规模的易传播的重复数据被简单地看做“自然的”事情而被人接受了。在第5章中,我将展示如何通过消除重复数据来控制一个快速演进的数据库设计的成本。

从TDD过程的角度来看,测试在对象中规定了行为。于是,问题就变成“在数据库中什么是行为?”在第6章中,我通过回答这个问题来为测试定一个良好的范围。

一旦单个测试的范围被定义好了,就能获得探索更大的主题的自由,即探知哪一种数据库设计对变化有利,而哪一种很难维护。与试图预测几个月之后的需求相比,在第7章中我会展示让数据库保持轻量、精益和简单是一条更好地支持未来需求的道路。

“对,如果你从不把事情搞糟,这一切都很棒。”有人可能会说,“但如果我们把事情搞糟了该怎么办?”在第8章中,我会展示一些技术,这些技术能让你处理任何可能影响你的数据库设计的计划外变化。

在第9章中,我针对如何设计一个能将可测试性最大化的数据库的类,提出一些建议。然后进一步展示如何在数据库的类中应用面向对象的设计概念。

测试经常被不必要的耦合所困扰。行为之间的依赖能产生涟漪效应(ripple effects),一个单独的变化,能引发几十个测试失败。在第10章中,我会展示如何运用第9章讨论的设计技术来将不同的行为彼此分离。

测试覆盖率越高,引入变化的速度越快,修改设计的频率也会越高。在第11章中,我演示了如何在保持行为不变的情况下改变数据库设计。

一个过程不能算作完备,除非它包含了能够处理在该过程引入之前就已开发了的软件的机制。在第12章中,我讨论了两种方法之中的其中一种,逐步用测试保护那些没有使用本书提及的实践方法进行开发的数据库。

第13章讨论了处理遗留数据库的另一种方法。当采用Façade模式时,可以将一个遗留设计封装到一个经过良好测试的新设计之内,然后逐步地将行为从旧的设计转移到新的设计中。

我不会将本书提到的方法作为解决将TDD带入数据库开发领域而引发的问题的“一刀切的”解决方案来进行兜售。不加修改地运用本书提到的实践,对于很多人来说都是有成效的。然而,有些人操作这些实践的前提条件无法与本书前13章的内容所涉及的条件完全符合。在第14章2中,我讨论了一些我见过的人们过去在实施过程中采取的一些变通做法。

最后,在第15章中,我演示了将本书讨论的各种技术运用到除数据库之外的其他数据持久化方案的一些方法。这些其他存储机制的例子有文件系统、XML文件和令人恐惧的序列化中间层对象。

1即遵循数据库的接口,参见第5章。——译者注
2第14章英文标题Variations除了有“变异”的含义外,还有“变奏曲”的意思,如此翻译是用音乐术语“变奏曲”来比喻测试驱动数据库开发的一些变通的做法。根据英文版《牛津高阶英语词典》(Oxford Advanced Learner’s Dictionary)的解释,变奏曲指:根据一个简单的曲调不断以不同的和更加复杂的形式进行重复的一组简短的乐章。——译者注
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

目录
前言
第 1 章 为何改变书的内容、谁是
1.1 为何改变书的内容
1.2 谁是目标读者
1.3 什么是障碍
1.4 小结
第 2 章 建立数据库的类
2.1节TDD中类的角色
2.2 面向对象编程语言中的类
2.3 数据库的类
2.4 增量构建
2.5 实现
2.6 小结
第 3 章 讲一点TDD
第 4 章 安全地改变设计
第 5 章 遵循接口
第 6 章 定义行为
第 7 章 为可维护性而构建
第 8 章 错误与修复
第 9 章 设计
第 10 章 mocking
第 11 章 重构
第 12 章 遗留数据库
第 13 章 Façade模式
第 14 章 变奏曲
第 15 章 其他应用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值