lincode +

Swift 面向对象编程

Swift的主要编程范式是面向对象编程,这一篇介绍Swift的面向对象特性。

面向对象

面向对象的三个基本特征是:封装,继承,和多态。

Swift完整支持了面向对象编程范式。所以,这三个基本概念在Swift中都得到了完整体现。对于使用Java或者Objective-C这样面向对象编程语言的程序员而言,Swift在面向对象方面会显得很熟悉,上手也很快。

文章会分这三部分封装,继承,和多态介绍Swift的面向对象编程范式的特点。

封装

封装,指将从客观事物抽象得到的数据和行为组成一个有机的整体。在大部分面向对象语言中,就是抽象成“类”。类对外部隐藏其数据和方法实现细节,仅对外公开接口,控制对对象的数据的读写的访问级别。

类(class)和结构体(struct)

在Swift中,结构体和类更为相似,这不同于C和Objective-C。

结构体和类的相同之处:

结构体和类的不同之处:

什么时候选择结构体:

一些选择结构体的列子:

总结而言,结构体适合相对小的和可以被拷贝的数据值。这是因为,结构体的值类型特性有某些的优势:例如,不会有内存泄漏的问题,多线程环境下也更为安全。如果,不是数据结构很大,或者必须使用继承的话,优先选择结构体。

枚举(enum)

在Objective-C中,枚举类型被用于管理一组相关常量集合。通过使用枚举将相关常量统一管理,可以使代码更清晰可读,使用更安全,更易维护。而在Swift中的枚举,不再仅仅只是一组常量的集合,而成为一等类型(first class)。枚举支持属性;支持实例方法;可以有构造函数;可以被扩张(extension);还可以遵守协议(protocol)。

Swift的枚举采用了更多传统上只被类(class)所支持的特性。

类,枚举,和结构体

Swfit中,结构体以及枚举类型,相对于类(class)有一些特别的地方:

首先,相同的地方是,结构体和枚举类型,都是用于封装数据和行为的。对比C++和Java,Swift赋予了结构体和枚举类型更多的面向对象特征。在其他一些语言需要用类来解决的场景中,Swift中可以使用结构体和枚举类型,而且更为合适。

但是,结构体和枚举是不能继承或者被继承的,所以,这两种数据类型也就没有多态性。

总结一下,Swift中的类和其他面向对象编程语言的类一样是面向对象语言的核心概念,具有面向对象的基本特征。而结构体和枚举类,只具有封装性,但不可继承,也就没有了多态性。

协议(protocol)

Swift中,协议用于定义完成某项任务或者功能所必须的方法和属性。但是,协议自身并不实现具体的功能,而只是描述这些功能实现是怎么样的。类,枚举和结构体都可以通过实现协议所要求的方法,而遵守协议。

协议类似于Java中的接口(interface)。和Java的接口一样,Swift的协议是一种抽象类型。它并不实现特点功能,而只是描述一组功能如何实现。所以,协议不能被实例化,只能被实现(adopt)。

扩展(extension)

扩展是一种向已有的类,枚举或者结构体添加新功能的方法。扩展和Objective-C中的分类(Category)类似,但是与Objective-C中的分类不同的是,Swift中的扩展没有名字。

访问级别

Swift中,对于类,结构体和枚举中声明的实例变量和方法,都可以设置访问级别:

继承

继承,指在一个类基础上定义一个新类。新类可以使用原有类的数据和方法。

Swift只能继承自单一基类,但可以实现多个协议(protocol)。这类似于Java只能单继承,但可以实现多个接口,而不同于C++的多重继承。

在Swift中,只有类可以继承另一个类。其余类型,包括结构体和枚举都不能继承。但是,类,结构体,枚举都可以遵守协议,可以被扩展。

初始化方法

Swift中有一类特别的方法,被作为初始化方法,它们没有func前缀,并以init为方法名。这类似于Java对于初始化方法的处理,而不同于Objective-C中的初始化方法只是一个普通的方法。对于初始化方法的特殊处理可以在语言机制上保证初始化方法只被调用一次,这种机制在Objective-C中是不存在的。

Swift中初始化方法都必须保证所有实例变量都被初始化。Swift初始化方法要求特殊的初始化顺序。先保证当前类的实例变量被初始化,再调用父类的初始化方法完成父类实例变量的初始化。

Swift中,不加修饰的init方法被认为是designated初始化方法。Swift对designated初始化方法赋予了更特殊的地位,以保证的初始化方法中所有实例变量都被初始化:

重写(overridding)

Swift提供了重写(overriding)保护机制。如果要重写基类的方法,就必须在子类的重写方法前加上overriding关键字。这么做是向编译器声明你想提供一个重写版本。编译器会确认,基类里确实存在具有相同方法定义。如果,基类中没有相同的方法定义,编译器就会报错。另一方面,如果,没有加上overriding关键字的方法和基类的某个方法定义相同,编译器也会报错,以防止意外的重写行为。这样就能从两方面保证重写行为的正确性。

Swift中可以通过添加final关键字,防止方法和属性被子类重写。

多态

多态(polymorphism),是面向对象编程的核心概念之一。指接口可以有多种不同的实现方式,并且可以在运行时替换具有相同接口的对象。

动态绑定(dynamic binding)是一个和多态紧密联系的概念。当给对象发送请求时,所引起的具体操作既与请求本身有关,又与接受对象有关。支持相同请求的不同对象可能对请求激发的操作有不同的实现。发送给对象的请求和它的相应操作在运行时刻的连接就称之为动态绑定。动态绑定允许你在运行时刻彼此替换有相同接口的对象。这种可替换性就称为多态。

类型转换

Swift中,使用isas两个关键字实现类型转换。

is可以用于检查一个实例是否属于特定类型。

示例代码如下:

var movieCount = 0
var songCount = 0
	
for item in library { 
	if item is Movie {
		++movieCount
	} else if item is Song {
		++songCount 
	}
}

as可以用于将一个实例向下转型为它的子类。

示例代码:

for item in library {
	if let movie = item as? Movie {
		println("Movie: '\(movie.name)', dir. \(movie.director)") 
	} else if let song = item as? Song {
		println("Song: '\(song.name)', by \(song.artist)")
	}
}

不确定类型:Any和AnyObject

Swift 为不确定类型提供了两种特殊的类型别名:

Any和AnyObject,是妥协的产物。Swift的目标仍然是服务苹果的iOS和Mac OS开发。那么,作为上一代语言的Objective-C的实际语言框架Cocoa会是Swift无法回避的遗产或者说是负担。Objectiive-C中有一个id类型,可以代表所有的类型,甚至包括nil。这是Objective-C动态性的表现。Swift在与Cocoa协作时,Cocoa中的id被对应为AnyObject。同id类型一样,编译器也不会对AnyObject类型做类型检查。

在Swift中,我们应该尽力明确地指出确定的类型,避免使用Any和AnyObject。如果代码中大量出现Any和AnyObject,几乎可以肯定代码结构设计上存在问题。

总结

Swift完整地支持面向对象编程,拥有完备的面向对象基础工具。这使得熟悉面向对象编程的程序员学习和使用Swift的成本降低了。Java或者Objective-C程序员对Swift的很多概念可能会觉得很熟悉,所以学习Swift并不会太困难,很快就能将Swift投入到实际生产之中。

现代编程语言,面向对象仍然占据绝对领先地位。绝大部分程序员也是编写面向对象程序。大公司在这面向对象编程上的投资巨大,学术界也在面向对象编程上有诸多的研究成果。虽然,我们现在已经了解到了面向对象编程在处理并发,多线程场景下会有严重的问题。但是,面向对象仍然有其他的很多的好处,比如,接近于人类思维的建模方式。大部分场景下,面向对象编程仍然具有优势。所以,在很长一段时间内,我们仍然无法,也没有必要完全抛弃面向对象编程。Swift作为一门实用的工业语言,拥有对面向对象编程的良好支持是十分必要且明智的。

点击查看评论

Blog

Opinion

Project