C语言学习笔记—C中两个对象及其对应类之间的关系:聚合

0、简述

        聚合也涉及包含另一个对象的容器对象。它与组合的主要区别就是,在聚合中被包含对象的生存期独立于容器对象的生存期。

        在聚合中,被包含的对象可以在构造容器对象之前构造。这与组合方式相反,组合方式中被包含的对象的生存期应该短于或等于容器对象的生存期。

1、汽车对象和引擎对象之间的聚合关系示例

        此示例展现了聚合关系。现在描述了一个非常简单的游戏场景,玩家拿起一把枪,射击多次,然后放下枪!

        player对象在一段时间内是一个容器对象,而当player对象拿起gun对象后,gun对象就是一个被包含的对象。gun对象的生存期独立于player对象的生存期。

        Gun类的头文件:

        可以发现,这里只声明了属性结构gun_t,并没有定义它,这叫前向声明,只能得到不能实例化的不完整的类型。即,头文件声明,源文件定义。

        Player类的头文件:

        

        可见,上述声明了所有player对象的公共接口,同样,我们必须前向声明 gun_t 和 player_t 结构。由于Player类的一些行为函数具有gun_t类型的参数,所以必须声明gun_t类型。

        Gun类的源文件:

#include<stdlib.h>
#include<stdbool.h>

typedef struct
{
    int bullets;
}gun_t;


gun_t* gun_new(void)
{
    return (gun_t*)malloc(sizeof(gun_t));
}


void gun_ctor(gun_t* gun, int init_Bullets)
{
    gun->bullets = 0;
    if(init_Bullets > 0)
    {
        gun->bullets = init_Bullets;
    }
}

void gun_dtor(gun_t* gun)
{
    //什么也不做
}


bool gun_has_bullets(gun_t* gun)
{
    return (gun->bullets > 0);
}


void gun_trigger(gun_t* gun)
{
    gun->bullets--;
}


void gun_refill(gun_t* gun)
{
    gun->bullets = 7;
}

        上述的代码很简单,他的编写方式使gun对象不知道它将被包含在任何对象中。

        Player类的源文件:

#include<stdlib.h>
#include<string.h>
#include<stdio.h>
#include"gun.h"


typedef struct
{
    char *name;
    struct gun_t *gun;
}player_t;


player_t* player_new(void)
{
    return (player_t*)malloc(sizeof(player_t));
}


void player_ctor(player_t* player, const char* name)
{
    player->name = (char*)malloc( (strlen(name)+1) * sizeof(char) );
    strcpy(player->name, name);
    player->gun = NULL; //构造时,初始化该指针
}


void player_dtor(player_t* player)
{
    free(player->name);
}


void player_pickup_gun(player_t* player, struct gun_t* gun)
{
    //这里,两个对象就建立了聚合关系
    player->gun = gun;
}


void player_shoot(player_t* player)
{
    if(player->gun)
    {
        gun_trigger(player->gun);
    }
    else
    {
        printf("玩家想射击,但是没有枪!");
        exit(1);
    }
}

void player_drop_gun(player_t* player)
{
    /*这里,两个对象之间的聚合关系结束。注意,gun对象不应该被释放,
    * 因为与组合关系不同,palyer对象不是gun对象的拥有者。
    */
    player->gun = NULL;    
}

        在 player_t 结构中,我们声明了指针属性gun,这个指针很快就指向一个gun对象。在构造函数中需要先使其置空,因为与组合不同,这个属性不需要设置为构造函数的一部分。

        如果需要在构造时设置聚合指针,则应该将目标对象的地址作为参数传递给构造函数。那么,这种情况称为强制聚合。

        如果在构造函数中,可以将聚合指针保留为空,那么它就是一个可选聚合,如上述代码所示,在构造函数中将可选聚合指针置空非常重要。                

        聚合关系由函数player_pickup_gun中玩家拾取枪开始,并在函数player_drop_gun中当玩家放下枪时结束。

        注意:在聚合关系结束后,应该将gun指针置空。与组合不同,容器对象不是被包含对象的所有者,所以它无法控制被包含对象的生存期。因此,我们不应该在player实现代码中去释放gun对象。

        在可选聚合关系中,在程序的某个时刻可能还没有设置所包含的对象。因此,在使用聚合指针时应该小心,因为访问没有设置的指针或是等于NULL的指针,都会导致分段错误。这就是为什么在函数player_shoot中要检查gun是否有效。如果聚合指针为空,则意味着使用player对象的代码误用了它,在这种情况下则中止返回,返回1作为处理过程的退出码。

         main源文件:

        最后,在上述主逻辑源文件中给出一个简短的情景,首先创建一个player对象和一个gun对象。然后,player拿起gun并开火射击,直到子弹耗尽。在此之后,player将重新填满枪的子弹并执行相同的操作,最后,他放下了枪。

        可以看到,gun和player对象是相互独立的。负责创建和销毁这些对象的逻辑是main函数。在执行的某个环节,它们形成了一个聚合关系并执行它们的角色,然后在另一个环节中,它们分离。

        聚合中最重要的一点就是,容器对象不应该改变被包含对象的生存期,而且只要遵循此规则,就不会出现内存问题。

        总结:

        在为实际项目创建的对象模型中,聚合关系的数量通常大于组合关系的数量。此外,聚合关系在外部可见,因为为了创建聚合关系,至少在容器对象的公共接口中需要一些专门的行为函数来设置和复位所包含的对象。

        在示例中,我们可以看到,gun和player对象从一开始就是分开的。它们在短时间内相互联系,然后有分开。这意味着聚合关系是暂时的,而组合关系是永久的。这表明组合是对象之间较强的“拥有”关系形式,而聚合则表现出较弱的关系。

        现在,自然会出现一个问题。如果两个对象之间的聚合关系是临时的,那么它们对应的类之间的关系也是临时的吗?答案是否定的。类型之间的聚合关系是永久的。如果将来两个不同类型的对象可能存在聚合关系,那么它们的类型应该永久地具有了聚合关系。这也适用于组合关系

        即使存在聚合关系的可能性很低,我们也要在容器对象的属性结构中声明一些指针,这意味着属性结构还是永久的改变了。当然,这只适用于基于类的编程语言。

        组合和聚合都描述了对某些对象的占有。每当你认为一个对象占有另一个对象时,它们(及其对应的类)之间就应该存在组合关系或者聚合关系。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值