iOS 和很多其它现代编程语言都有一个设计模式叫做
模型-视图-控制器 ,简称 MVC 。(个人认为,mvc应该是框架模式)
MVC 背后的理念主要是,视图永远只关心如何呈现,模型永远只关心数据,控制器应该能在不需要了解二者太多的内部结构的前提下,很好的将二者嫁接起来。
使用MVC最大的好处就是,当你的数据模型变了,你只需要修改一次就够了。
新人最容易犯的错误就是在
UIViewController 类里密密麻麻的的写了过多的逻辑。这就使得视图和
UIViewControllers 的连接太过于紧密,以至于这个视图很难再被重用于显示其它不同的内容。
为什么要在你的应用中实现MVC模型呢?设想如果你想往
VehicleDetailViewController 中添加更多有关汽车的详细内容 。你可以回到
configureView 方法中,并添加更多有关于汽车的具体内容,就像这样:
- //Car-specific details
- [basicDetailsString appendString:@"nnCar-Specific Details:nn"];
- [basicDetailsString appendFormat:@"Number of doors: %d", self.detailVehicle.numberOfDoors];
但是你要注意,这样会有一个小问题:
![](http://www.cocoachina.com/cms/uploads/allimg/131213/4196_131213113956_1.jpg)
VehicleDetailsViewController 只知道 在
Vehicle 父类中定义的参数;它并不知道任何关于Car 子类的内容。
有很多方法可以解决这个问题。
一种最直观的方法就是导入
Car.h文件, 那么
VehicleDetailViewController 就知道Car子类的所有参数了。但是那就意味着要为每一个子类添加大量的逻辑来处理这些参数。
每次你发现你自己在做这种事的时候,你都应该问问你自己:“我的视图控制器是不是做了太多了呢?”
这种情况下,你的答案是肯定的。你可以利用继承的特性,用同一个方法来为不同的子类提供对应的字符串来显示相应的内容。
通过继承创建子类
首先,将下面的新方法添加到
Vehicle.h:
- //Convenience method to get the vehicle's details.
- -(NSString *)vehicleDetailsString;
这是公开声明的方法,它可以被像 VehicleDetailsViewController的其它类调用。它们不需要知道每一个参数,相反,它们仅仅通过调用vehicleDetailsString一个方法就可以获取完全格式化的字符串,然后使用它。
打开
Vehicle.m 文件并添加如下实现:
- -(NSString *)vehicleDetailsString
- {
- //Setup the basic details string based on the properties in the base Vehicle class.
- NSMutableString *basicDetailsString = [NSMutableString string];
- [basicDetailsString appendString:@"Basic vehicle details:nn"];
- [basicDetailsString appendFormat:@"Brand name: %@n", self.brandName];
- [basicDetailsString appendFormat:@"Model name: %@n", self.modelName];
- [basicDetailsString appendFormat:@"Model year: %dn", self.modelYear];
- [basicDetailsString appendFormat:@"Power source: %@n", self.powerSource];
- [basicDetailsString appendFormat:@"# of wheels: %d", self.numberOfWheels];
- return [basicDetailsString copy];
- }
这个方法和你添加到 VehicleDetailViewController.m中的方法非常类似,只是它返回的是一个字符串,而不是直接将它在某个地方显示出来。
现在你可以继承父类vehicle 的基础字符串并为 Car 类添加特殊的内容。打开 Car.m 并覆盖
vehicleDetailsString:方法的实现:
- - (NSString *)vehicleDetailsString
- {
- //Get basic details from superclass
- NSString *basicDetails = [super vehicleDetailsString];
- //Initialize mutable string
- NSMutableString *carDetailsBuilder = [NSMutableString string];
- [carDetailsBuilder appendString:@"nnCar-Specific Details:nn"];
- //String helpers for booleans
- NSString *yes = @"Yesn";
- NSString *no = @"Non";
- //Add info about car-specific features.
- [carDetailsBuilder appendString:@"Has sunroof: "];
- if (self.hasSunroof) {
- [carDetailsBuilder appendString:yes];
- } else {
- [carDetailsBuilder appendString:no];
- }
- [carDetailsBuilder appendString:@"Is Hatchback: "];
- if (self.isHatchback) {
- [carDetailsBuilder appendString:yes];
- } else {
- [carDetailsBuilder appendString:no];
- }
- [carDetailsBuilder appendString:@"Is Convertible: "];
- if (self.isConvertible) {
- [carDetailsBuilder appendString:yes];
- } else {
- [carDetailsBuilder appendString:no];
- }
- [carDetailsBuilder appendFormat:@"Number of doors: %d", self.numberOfDoors];
- //Create the final string by combining basic and car-specific details.
- NSString *carDetails = [basicDetails stringByAppendingString:carDetailsBuilder];
- return carDetails;
- }
汽车版本的这个函数首先调用了父类的相应方法以获取有关车的详细内容。接着它将和带有汽车特点的详细内容存入 carDetailsBuilder 字符串,最后再将它们二者结合起来。
现在将
VehicleDetailViewController.m 文件中的
configureView 函数替换为如下的实现,以显示我们刚刚创建完成的字符串:
- - (void)configureView
- {
- // Update the user interface for the detail vehicle, if it exists.
- if (self.detailVehicle) {
- //Set the View Controller title, which will display in the Navigation bar.
- self.title = [self.detailVehicle vehicleTitleString];
- self.vehicleDetailsLabel.text = [self.detailVehicle vehicleDetailsString];
- }
- }
编译并运行你的程序;选择一辆车,除了看到一般信息以外,你还应该能看到带有汽车特点的信息,就像下面这样:
![](http://www.cocoachina.com/cms/uploads/allimg/131213/4196_131213114151_1.png)
你的
VehicleDetailViewController 类现在已经能让
Vehicle 和 Car 类来判断所要显示的数据了。
ViewController 所做的唯一的事情就是将信息和视图连接起来。
这种方法的优势在你继续为 Vehicle 创建其它子类的时候被显现出来。就拿一个最简单的摩托车来说。
打开
FileNewFileCocoaTouchObjective-C Class, 创建一个
Vehicle 的新的子类
Motorcycle。
因为有的摩托车会发出深沉的引擎噪音,而有的摩托车的引擎声音是高亮的,所以你创建的每一个 Motorcycle 对象,你都应该为它指定它能发出的噪音种类。
在
Motorcycle.h 中,添加一个代表噪音种类的参数,位于
@interface 行后面:
- @property (nonatomic, strong) NSString *engineNoise;
接着,打开
Motorcycle.m. 添加如下 init 方法:
- #pragma mark - Initialization
- - (id)init
- {
- if (self = [super init]) {
- self.numberOfWheels = 2;
- self.powerSource = @"gas engine";
- }
- return self;
- }
因为所有的摩托车都有两个轮子,并且都是汽油驱动的(在这个例子中,所有用电驱动的都被看作电动车,而不叫摩托车),你可以在初始化对象的时候设置它轮子的个数已经动力源。
接下来,添加下面的方法以覆盖父类中那些返回是 nil 的方法:
- #pragma mark - Superclass Overrides
- -(NSString *)goForward
- {
- return [NSString stringWithFormat:@"%@ Open throttle.", [self changeGears:@"Forward"]];
- }
- -(NSString *)goBackward
- {
- return [NSString stringWithFormat:@"%@ Walk %@ backwards using feet.", [self changeGears:@"Neutral"], self.modelName];
- }
- -(NSString *)stopMoving
- {
- return @"Squeeze brakes.";
- }
- -(NSString *)makeNoise
- {
- return self.engineNoise;
- }
最后,覆盖 vehicleDetailsString 方法以添加有 Motorcycle-特点的内容,就像下面这样:
- - (NSString *)vehicleDetailsString
- {
- //Get basic details from superclass
- NSString *basicDetails = [super vehicleDetailsString];
- //Initialize mutable string
- NSMutableString *motorcycleDetailsBuilder = [NSMutableString string];
- [motorcycleDetailsBuilder appendString:@"nnMotorcycle-Specific Details:nn"];
- //Add info about motorcycle-specific features.
- [motorcycleDetailsBuilder appendFormat:@"Engine Noise: %@", self.engineNoise];
- //Create the final string by combining basic and motorcycle-specific details.
- NSString *motorcycleDetails = [basicDetails stringByAppendingString:motorcycleDetailsBuilder];
- return motorcycleDetails;
- }
现在,是时候创建一些
Motorcycle 的实例了。
打开
VehicleListTableViewController.m 确保它导入了 Motorcycle 类,否则加入下面这句话:
- #import "Motorcycle.h"
接下来,找到 setupVehicleArray 方法,并添加如下代码,位于你之前添加的 Car 对象的下面,但是位于数组排序代码的上面:
- //Create a motorcycle
- Motorcycle *harley = [[Motorcycle alloc] init];
- harley.brandName = @"Harley-Davidson";
- harley.modelName = @"Softail";
- harley.modelYear = 1979;
- harley.engineNoise = @"Vrrrrrrrroooooooooom!";
- //Add it to the array.
- [self.vehicles addObject:harley];
- //Create another motorcycle
- Motorcycle *kawasaki = [[Motorcycle alloc] init];
- kawasaki.brandName = @"Kawasaki";
- kawasaki.modelName = @"Ninja";
- kawasaki.modelYear = 2005;
- kawasaki.engineNoise = @"Neeeeeeeeeeeeeeeeow!";
- //Add it to the array
- [self.vehicles addObject:kawasaki];
上面的代码简单的初始化了两个摩托车对象,并将它们添加到车的数组中。
编译并运行你的应用程序;你将会在列表中看到你刚刚添加的 摩托车 对象 :
![](http://www.cocoachina.com/cms/uploads/allimg/131213/4196_131213114437_1.png)
点击其中的一个,你将会被带到这个摩托车 的详情页面,就像下面这样:
![](http://www.cocoachina.com/cms/uploads/allimg/131213/4196_131213114446_1.png)
无论是汽车还是摩托车(甚至是一个普通的老爷车),你都可以调用
vehicleDetailsString 并获得响应的详情。
适当的分离模型,视图和控制器,并运用继承,你就能够为一个父类的不同子类显示数据,而避免了为不同的子类撰写大量额外的代码。代码越少==程序员越开心:]
提供模型类中的逻辑
运用这种方法,你还可以将更多的更复杂的逻辑包装在模型类里面。想想 卡车 对象:很多不同类型的车都被称为“卡车”,从小货车到半挂车。你的卡车类需要一些逻辑,以基于这辆开车能拉多少货物而改变它的行为。
进入 FileNewFileCocoaTouchObjective-C Class, 创建一个名为Truck 的
Vehicle 的子类。
添加如下整型变量到
Truck.h 文件中,用于存储卡车的载重数据:
- @property (nonatomic, assign) NSInteger cargoCapacityCubicFeet;
因为卡车的类型太多了,所以你也不需要创建初始化方法以自动提供所有的详情。你可以只是简单的重写父类中那些对于任何类型的卡车都适用的方法。
打开
Truck.m 文件并添加如下方法:
- #pragma mark - Superclass overrides
- - (NSString *)goForward
- {
- return [NSString stringWithFormat:@"%@ Depress gas pedal.", [self changeGears:@"Drive"]];
- }
- - (NSString *)stopMoving
- {
- return [NSString stringWithFormat:@"Depress brake pedal. %@", [self changeGears:@"Park"]];
- }
接着,你需要重写一些方法,以便它能根据货车拉货量的多少返回不同的字符串。大的卡车在倒车的时候需要发出警报声,所以你可以为此创建一个私有函数(一个不声明在 .h 文件中的函数,因此对于其它的类是不可见的)。
将如下帮助代码添加到
Truck.m 文件中:
- #pragma mark - Private methods
- - (NSString *)soundBackupAlarm
- {
- return @"Beep! Beep! Beep! Beep!";
- }
然后回到刚刚重写的那个方法中,现在你可以在
goBackward 方法中调用
soundBackupAlarm 方法,这样大卡车在后退的时候就可以发出警报声了:
- - (NSString *)goBackward
- {
- NSMutableString *backwardString = [NSMutableString string];
- if (self.cargoCapacityCubicFeet > 100) {
- //Sound a backup alarm first
- [backwardString appendFormat:@"Wait for "%@", then %@", [self soundBackupAlarm], [self changeGears:@"Reverse"]];
- } else {
- [backwardString appendFormat:@"%@ Depress gas pedal.", [self changeGears:@"Reverse"]];
- }
- return backwardString;
- }
不同的卡车喇叭也不同;比如小型的卡车喇叭声和汽车的喇叭声很像,然而越大的卡车就会拥有更大的喇叭声。为了解决这种情况,你只需要在 makeNoise 方法中添加一些简单的 if/else 语句就行了。
像下面这样添加
makeNoise 方法:
- - (NSString *)makeNoise
- {
- if (self.numberOfWheels <= 4) {
- return @"Beep beep!";
- } else if (self.numberOfWheels > 4 && self.numberOfWheels <= 8) {
- return @"Honk!";
- } else {
- return @"HOOOOOOOOONK!";
- }
- }
最后,你可以重写
vehicleDetailsString 方法以从你的 Truck 对象中获取对应的信息。就像下面这样:
- -(NSString *)vehicleDetailsString
- {
- //Get basic details from superclass
- NSString *basicDetails = [super vehicleDetailsString];
- //Initialize mutable string
- NSMutableString *truckDetailsBuilder = [NSMutableString string];
- [truckDetailsBuilder appendString:@"nnTruck-Specific Details:nn"];
- //Add info about truck-specific features.
- [truckDetailsBuilder appendFormat:@"Cargo Capacity: %d cubic feet", self.cargoCapacityCubicFeet];
- //Create the final string by combining basic and truck-specific details.
- NSString *truckDetails = [basicDetails stringByAppendingString:truckDetailsBuilder];
- return truckDetails;
- }
现在你的 Truck 对象已经写好了,你可以试着创建一些实例。回到
VehicleListTableViewController.m 中,添加如下的导入语句到文件头部以便它能使用 Truck 类:
- #import "Truck.h"
找到 setupVehicleArray 方法,在数组排序之前添加如下代码:
- //Create a truck
- Truck *silverado = [[Truck alloc] init];
- silverado.brandName = @"Chevrolet";
- silverado.modelName = @"Silverado";
- silverado.modelYear = 2011;
- silverado.numberOfWheels = 4;
- silverado.cargoCapacityCubicFeet = 53;
- silverado.powerSource = @"gas engine";
- //Add it to the array
- [self.vehicles addObject:silverado];
- //Create another truck
- Truck *eighteenWheeler = [[Truck alloc] init];
- eighteenWheeler.brandName = @"Peterbilt";
- eighteenWheeler.modelName = @"579";
- eighteenWheeler.modelYear = 2013;
- eighteenWheeler.numberOfWheels = 18;
- eighteenWheeler.cargoCapacityCubicFeet = 408;
- eighteenWheeler.powerSource = @"diesel engine";
- //Add it to the array
- [self.vehicles addObject:eighteenWheeler];
这将会在汽车和摩托车所在的数组中添加一些带有卡车特点的 Truck 对象。
编译并运行程序;点击卡车其中的一个,确保你能够看到带有卡车特点的详情,就像下面显示的这样:
![](http://www.cocoachina.com/cms/uploads/allimg/131213/4196_131213114705_1.png)
看起来很棒!这些卡车信息的得来要归功于 vehicleDetailsString 方法,继承以及重写的实现。