首页 » iOS编程基础:Swift、Xcode和Cocoa入门指南 » iOS编程基础:Swift、Xcode和Cocoa入门指南全文在线阅读

《iOS编程基础:Swift、Xcode和Cocoa入门指南》1.15 设计

关灯直达底部

现在你已经知道了何为对象,何为实例。不过,程序需要什么对象类型呢,这些对象类型应该包含哪些方法与属性呢,何时以及如何实例化它们呢,该如何使用这些实例呢?遗憾的是,我无法告诉你这些,这是一门艺术,基于对象编程的艺术。我能告诉你的是,在设计与实现基于对象的程序时,你的首要关注点应该是什么,程序正是通过这个过程不断发展起来的。

基于对象的程序设计必须要基于对对象本质的深刻理解之上。你设计出的对象类型需要能够封装好正确的功能(方法)以及正确的数据(属性)。接下来,在实例化这些对象类型时,你要确保实例拥有正确的生命周期,恰到好处地公开给其他对象,并且要能实现对象之间的通信。

1.15.1 对象类型与API

程序文件只会包含极少的顶层函数与变量。对象类型的方法与属性(特别是实例方法与实例属性)实现了大多数动作。对象类型为每个具体实例赋予了专门的能力。它们还有助于更有意义地组织程序代码,并使其更易维护。

可以用两个短语来总结对象的本质:功能封装与状态维护(我最早是在REALbasic:The Definitive Guide一书中给出的这个总结)。

功能封装

每个对象都有自己的职责,并且在自身与外界对象(从某种意义上说是程序员)之间架起一道不透明的墙,外界只能通过这道墙访问方法与动作,而这是通过向其发送相应的消息实现的。在背后,这些动作的实现细节是隐藏起来的,其他对象无须知晓。

状态维护

每个实例都有自己所维护的一套数据。通常来说,这些数据是私有的,因此是被封装的;其他对象不知道这些数据是什么,也不清楚其形式是怎样的。对于外界来说,探求对象所维护的私有数据的唯一方式就是通过公有方法或是属性。

比如,假设有一个对象,它实现了栈的功能——也许是Stack类的实例。栈是一种数据结构,以LIFO(后进先出)的顺序维护着一组数据,它只会响应两个消息:push与pop。push表示将给定数据添加到集合中,pop表示从集合中删除最近刚加进去的数据。栈像是一摞盘子:盘子会一个接着一个地放到栈上面或从栈上移除,因此只有将后面添加的盘子都移除后才能移除第一个添加的盘子(如图1-3所示)。

图1-3:栈

栈对象很好地说明了功能的封装,因为外部对栈的实现方式一无所知。它可能是个数组,也可能是个链表,还有可能是其他实现。不过客户端对象(向栈对象发送push或pop消息的对象)对此却并不在意,只要栈对象坚持其行为要像一个栈这样的契约即可。这对于程序员来说也非常棒,在开发程序时,它们可以将一个实现替换为另外的实现,同时又不会破坏程序的机制。恰恰相反,栈对象不知道,也不关心到底是谁向其发送push或pop消息以及为何发送。它只不过以可靠的方式完成自己的工作而已。

栈对象也很好地说明了状态维护,因为它不仅仅是栈数据的网关,它就是栈数据本身。其他对象可以访问到栈的数据,但只能通过访问栈对象本身的方式才行,栈对象也只允许通过这种方式来访问。栈数据位于栈对象内部;其他对象是看不到的。其他对象能做的只是push或pop。如果某个对象位于栈对象的顶部,那么无论哪个对象向其发送pop消息,栈对象都会收到这个消息并将顶部对象返回。如果没有对象向这个栈对象发送pop消息,那么栈顶部的对象就会一直待在那里。

每个对象类型可以接收的消息总数(即API,应用编程接口)类似于你可以让这个对象类型所做的事项列表。对象类型将你的代码划分开来;其API构成了各部分之间通信的基础。

在实际情况下,当编写iOS程序时,你所使用的大多数对象类型都不是你自己的,而是苹果公司提供的。Swift本身自带了一些颇具价值的对象类型,如String和Int;你可能还会使用import UIKit,它包含了为数众多的对象类型,这些对象类型会涌入你的程序中。你无须创建这些对象类型,只要学习如何使用它们就可以了,你可以查阅公开的API,即文档。苹果公司自己的Cocoa文档包含了大量页面,每一页都列出并介绍了一种对象类型的属性与方法。比如,要想了解可以向NSString实例发送什么消息,你可以先研究一下NSString类的文档。其页面包含属性与方法的长长的列表,告诉你NSString对象能做什么。文档上并不会列出关于NSString的一切,不过大部分内容都可以在上面找到。

在编写代码前,苹果公司已经替你做了很多思考与规划。因此,你会大量使用苹果公司提供的对象类型。你也可以创建全新的对象类型,但相对于使用现有的对象类型来说,自己创建的比例不是很大。

1.15.2 实例创建、作用域与生命周期

Swift中重要的实体基本上就是实例了。对象类型定义了实例的种类以及每一种实例的行为方式。不过这些类型的具体实例都是持有状态的“实体”,这也是程序最为关注的内容,实例方法与属性就是可以发送给实例的消息。因此,程序能够发挥作用是离不开实例的帮助的。

不过在默认情况下,实例是不存在的!回头看看示例1-1,我们定义了一些对象类型,但却并没有创建实例。如果运行程序,那么对象类型从一开始就会存在,但仅仅只是存在而已。我们实现了一种可能性,能够创建一些可能存在的对象的类型,但在这种情况下,其实什么都不会发生。

实例并不会自动产生。你需要对类型进行实例化才能得到实例。因此,程序的很多动作都会包含实例化类型。当然,你希望保留这些实例,因此还会将每个新创建的实例赋给一个变量,让这个变量来持有它、为其命名,并保持其生命周期。实例的生命周期取决于引用它的变量的生命周期。根据引用实例的变量的作用域,一个实例可能会对其他实例可见。

基于对象编程的艺术的要点就在于此,赋予实例足够的生命周期并让它对其他实例可见。你常常会将实例放到特定的盒子中(将其赋给某个变量、在某个作用域中声明),这多亏了变量生命周期与作用域规则,如果需要,实例会留存足够长的时间供程序使用,其他代码也可以获得它的引用并与之通信。

规划好如何创建实例、确定好实例的生命周期以及实例之间的通信这件事让人望而生畏。幸好,在实际情况下,当编写iOS程序时,Cocoa框架本身会再一次为你提供初始框架。

比如,你知道对于iOS应用来说,你需要一个应用委托类型和一个视图控制器类型;实际上,在创建iOS应用项目时,Xcode会帮你完成这些事情。此外,当应用启动时,运行时会帮你实例化这些对象类型,并且构建好它们之间的关系。运行时会创建出应用委托实例,并且让其在应用的生命周期中保持存活状态;它会创建一个窗口实例,并将其赋给应用委托的一个属性;它还会创建一个视图控制器实例,并将其赋给窗口的一个属性。最后,视图控制器实例有一个视图,它会自动出现在窗口中。

这样,你无须做任何事情就拥有了在应用生命周期中一直存在的一些对象,包括构成可视化界面的基础对象。重要的是,你已经拥有了定义良好的全局变量,可以引用所有这些对象。这意味着,无须编写任何代码,你就已经可以访问某些重要对象了,并且有了一个初始位置用于放置生命周期较长的其他对象,以及应用所需的其他可视化界面元素。

1.15.3 小结

在构建基于对象的程序来执行特定的任务时,我们要理解对象的本质。它们是类型与实例。类型指的是一组方法,用于说明该类型的所有实例可以做什么(功能封装)。相同类型的实例只有属性值是不同的(状态维护)。我们要做好规划。对象是一种组织好的工具,一组盒子,用于封装完成特定任务的代码。它们还是概念工具。程序员要能以离散对象的思维进行思考,他们要能将程序的目标与行为分解为离散的任务,每个任务都对应一个恰当的对象。

与此同时,对象并不是孤立的。对象之间可以合作,这叫作通信,方式则是发送消息。通信途径是多种多样的。对此做出妥善的安排(即架构),从而实现对象之间的协作、有序的关系是基于对象编程最具挑战性的一个方面。在iOS编程中,你可以得到Cocoa框架的帮助,它提供了初始的对象类型集合以及基础的架构框架。

使用基于对象的编程能够很好地让程序实现你的需求,同时又会保持程序的整洁性和可维护性;你的能力会随着经验的增加而增强。最后,你可能想要进一步阅读关于高效规划及基于对象编程架构方面的读物。我推荐两本经典、值得收藏的好书。Martin Fowler所写的Refactoring(Addison-Wesley,1999)介绍了如何根据哪些方法应该属于哪些类而重新调整代码的原则(如何战胜恐惧以实现这一点)。Erich Gamma、Richard Helm、Ralph Johnson及John Vlissides(又叫作“四人组”)所写的Design Patterns(本书中文版《设计模式:可复用面向对象软件的基础》已由机械工业出版社引进出版,书号:978-7-111-07575-2。)是架构基于对象程序的宝典,书中列举了如何根据正确的原则以及对象之间的关系来安排对象的所有方法(Addison-Wesley,1994)。