2018-05-23 学习如何使用复合

作者: 肠粉白粥_Hoben | 来源:发表于2018-05-23 14:09 被阅读4次

    一.书上的例子

    这次书上是用Car和Engine、Tire的故事来介绍复合。
    所谓复合,就是组合多个对象,使整个类和其子类能够正常分工协作。

    1.description

    OC里面有个方法叫- (NSString * ) description,即可以实现对自己的描述,重写里面的方法,只需要return @(NSString *)即可。
    我们来看看Tire里面的description。

    #import <Foundation/Foundation.h>
    
    @interface Tire : NSObject
    
    @end
    
    #import "Tire.h"
    
    @implementation Tire
    - (NSString *)description
    {
        return (@"I am a tire!");
    }
    @end
    

    不难发现,在.h文件里面并没有定义description这个方法,因为这是OC自带的一个方法,只需要在.m文件输入description关键词即可修改自己想要的描述。

    2.用init方法初始化

    在Car.h里面定义自己的轮子和引擎。

    @interface Car : NSObject
    {
        Engine *engine;
        Tire *tires[4];
    }
    

    在Car.m里面初始化

    - (id)init
    {
        self = [super init];
        if (self) {
            engine = [Engine new];
            tires[0] = [Tire new];
            tires[1] = [Tire new];
            tires[2] = [Tire new];
            tires[3] = [Tire new];
        }
        return self;
    }
    

    这里init也是个自带的方法,至于[super init],即要超类(这里是NSObject)完成所需的一次性初始化,init方法返回的值(id型数据,即泛型对象指针,在Java里面叫Object)描述了被初始化的对象。

    3.用set方法和get方法初始化

    上文是创建了一台车,但是是要车自己造轮子和引擎,以现在的AI水平应该还不能达到这一点,所以,我们必须提供一个set方法和一个get方法,要“人”造车,而不是“车”造车。
    来活学活用一下,昨天刚学的@property可不能就这样丢掉。
    在Car.h加上set、get函数的定义。

    @property (nonatomic, assign) Engine *engine;
    
    -(void) setTire: (Tire *) tire
            atIndex: (int) index;
    
    -(void) print;
    
    -(Tire *) tireAtIndex: (int) index;
    

    数组尽量还是自己自定义一个set方法吧,这语法看得我有点迷= =
    来默写一遍:

    -(void) setTire: (Tire*) tire 
            atIndex: (int) index
    

    再默写一遍:

    -(void) setTire: (Tire *) tire
            atIndex: (int) index
    

    然后就可以实现了,在Car.m文件实现这些方法:

    @synthesize engine = _engine;
    
    -(void) setEngine:(Engine *)engine
    {
        _engine = engine;
    }
    
    -(Engine*) engine
    {
        return _engine;
    }
    
    -(void) setTire:(Tire *)tire
            atIndex:(int)index
    {
        if (index < 0 || index > 3) {
            NSLog(@"bad index (%d) in setTire:atIndex", index);
            exit(1);
        }
        
        tires[index] = tire;
    }
    
    -(Tire *) tireAtIndex:(int)index
    {
        if (index < 0 || index > 3) {
            NSLog(@"bad index (%d) in tireAtIndex", index);
            exit(1);
        }
        return (tires[index]);
    }
    

    注意两点:
    1.有关数组的都要防止错误的输入导致的溢出问题。
    2.get方法是不加get在前面的,Java里面是getObject,但是在OC,get方法是object,不接受反驳,语法习惯问题。
    最后完善一下print方法:

    - (void) print
    {
        NSLog(@"%@", _engine);
        for (int i = 0; i < 4; i++) {
            NSLog(@"%@", tires[I]);
        }
    }
    

    4.main函数的调用

    关键点来了!再次要面对很迷的OC初始化的语法,来看看怎么搞:

    int main(int argc, char * argv[]) {
        //造新车
        Car *car = [Car new];
        //装引擎
        Engine *engine = [Engine new];
        [car setEngine: engine];
        //装轮子
        for (int i = 0; i < 4; i++) {
            Tire *tire = [Tire new];
            [car setTire:tire atIndex:i];
            NSLog(@"Setting tire No.%d", i);
        }
        //看效果
        [car print];
        
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    

    可以看到,一个用户调用了main创建了一个新的Car、Engine和四个Tires,再用set方法把Engine和Tires装到车上。
    车倒是装好了,但是如果我要把Engine和Tire拿出来检查一下该怎么办呢,这就要用到我们之前定义的get方法了:

        //看引擎
        NSLog(@"Getting engine...");
        NSLog(@"%@", [car engine]);
        //看轮子
        NSLog(@"Getting tires...");
        for (int i = 0; i < 4; i++) {
            NSLog(@"%@", [car tireAtIndex: I]);
        }
    

    说实话,OC的get方法不加get是个很明智的选择,你看这一句[car engine]就很简洁,对于我们【获得的目的】一目了然,所以我觉得OC也是一个很好看的语法,甚至有点爱上了她~

    5.给汽车换上新装备(继承的复习)

    现在我们有最新的引擎slant-6和最新的轮子,来让我们的跑车更酷炫吧!
    首先slant-6是个引擎,那么我们就让他继承引擎。

    #import <Foundation/Foundation.h>
    #import "Engine.h"
    
    @interface Slant6 : Engine
    
    @end
    

    重写一下描述:

    - (NSString *)description
    {
        return (@"I am Slant6, a new engine!");
    }
    

    轮子哥也同理,所以在这里就不赘述了。
    好了,main函数里面已经初始化了原始装备了,怎么装上新的装备呢?没错,就是set方法!

        //换新引擎
        Slant6 *slant = [Slant6 new];
        [car setEngine: slant];
        
        //换新轮子
        for (int i = 0; i < 4; i++) {
            AllWeatherRadial *newTire = [AllWeatherRadial new];
            NSLog(@"Setting tire No.%d", i);
            [car setTire: newTire atIndex: I];
        }
        
        //看效果
        [car print];
    

    自此,我们的跑车已经是最新版本,复合的学习也告一段落啦!

    6.总结与复习

    继承复合是一对好基友,继承是"is a",复合是"has a"
    slant-6 ''is a'' Engine,所以要继承Engine;Car "has a" Engine,所以要用复合。
    在解决一个问题的时候,一定要分清楚到底是什么关系,才用到相关的知识,如果让Car去继承Engine,那真的就贻笑大方了。

    二.学习文件间的依赖

    因为之前是先看这章的一部分来实现不同类的封装的,所以就简单看了一下,还是有点知识有用场的。

    1.@class

    如果是Car复合了Engine的话,那么这两句话是等价的:
    1)在Car.h定义 @class Engine,同时在Car.m定义import "Engine.h"
    2)在Car.h定义 import "Engine.h"。
    虽然第二种相对来说比较简单,但是,因为如果Car和Engine相互依赖(或者更复杂的循环依赖)的话,那么IDE会报错的,所以还是用第一种比较好。
    不信?(我也不太相信)来做个试验,我们来看看第二种到底会发生什么情况:
    在Engine.h定义:

    #import <Foundation/Foundation.h>
    #import "Car.h"
    @class Car;
    @interface Engine : NSObject
    
    @end
    

    在Car.h定义:

    #import <Foundation/Foundation.h>
    #import "Engine.h"
    

    接下来发生的bug出乎我的意料:


    image.png

    可怕,这个错误如果真的出现在实际编程的话,是很难找到bug的!
    所以乖乖地用第一种方法吧,虽然有点繁琐,但是保证不出bug,什么?又不信?(我也不太相信),做个试验呗:
    现在Engine.h依旧是:

    #import <Foundation/Foundation.h>
    #import "Car.h"
    @class Car;
    @interface Engine : NSObject
    
    @end
    

    但是我把Car.h改成了这样:

    #import <Foundation/Foundation.h>
    //#import "Engine.h"
    @class Engine;
    @class Tire;
    @class Slant6;
    @class AllWeatherRadial;
    

    跑一跑,看到这个锤子没有!成功了,哈哈


    image.png

    不过main函数可就不能仅仅包一个Car.h就完事了,现在改成@class命名方法的话,我们需要包所有用上的头文件。

    #import <UIKit/UIKit.h>
    #import "AppDelegate.h"
    #import "Car.h"
    #import "Engine.h"
    #import "Tire.h"
    #import "Slant6.h"
    #import "AllWeatherRadial.h"
    

    相关文章

      网友评论

        本文标题:2018-05-23 学习如何使用复合

        本文链接:https://www.haomeiwen.com/subject/vjwljftx.html