使用UML类图的组合和聚合示例

16

我似乎无法完全理解代码中聚合和组合之间的区别。

客户端 <.>---->银行账户

(这应该是客户端 - 银行账户组合类图)。

因此,在此示例中,客户拥有一个银行账户,这意味着当客户对象死亡时,他的银行账户对象也会死亡。这是否意味着我们必须在客户类中拥有一个银行账户对象?

Class Client
{

    BankAccount acc = new BankAccount();

    public void addMoneyToBankAccount(decimal amount)
    {         
        acc.AddMoney(amount);
    }

    public decimal CheckBalance()
    {
        return acc.CheckAccountBalance();
    }

}

那么,这段代码是组合起来的吗?在这个例子中,聚合会是什么样子? 抱歉问一个新手问题,请纠正我如果代码有误。先谢谢。

4个回答

15

是的,你所做的被称为组合,如果你想要聚合,可以这样做:

Class Client
{

    BankAccount acc;

    public Client(BankAccount p_acc)
    {
      acc=p_acc;
    }

    public void addMoneyToBankAccount(decimal amount)
    {         
        acc.AddMoney(amount);
    }

    public decimal CheckBalance()
    {
        return acc.CheckAccountBalance();
    }

}

聚合:

如果继承给我们“是一个(is-a)”,组合给我们“部分-整体(part-of)”,那么我们可以说聚合给我们“具有(has-a)”的关系。在聚合中,零件的生命周期不由整体管理。为了使这一点更清晰,我们需要一个例子。在过去的12个月中,我一直参与实现CRM系统,所以我将以此作为例子的一部分。

CRM系统拥有客户数据库和一个单独的数据库,保存着特定地理区域内的所有地址。在这种情况下,使用聚合是有意义的,因为客户'具有'地址。说地址是客户的'部分(part-of)'是不合适的,因为它并不是客户的一部分。考虑这样一个问题,如果客户不存在了,地址还会存在吗? 我认为它不会消失。在UML图表上,聚合显示为一个未填充的菱形。

正如我在答案开头所说的,这是我的对组合和聚合的看法。决定使用组合还是聚合不应该是棘手的。在对象建模时,应该只是问自己这是'部分(part-of)'还是'具有(has-a)'的关系。


我有点困惑。考虑这个句子:“在聚合中,部件的生命周期不由整体管理。”假设运行时代码像这样调用:new Client(new BankAccount())。那么 BankAccount 作为参数传递给构造函数,它的生命周期不是由 Client 管理吗?因此只会在 Client 的生命周期内存在? - Bruno Barros

8
你的客户-BankAccount代码是一种组合关系。
你的代码满足组合的所有特性:
->部分类别器(BankAccount)的生命周期取决于整个分类器(Client)的生命周期。
->数据通常只在一个方向流动(即从整个分类器(Client)到部分类别器(BankAccount))。
聚合可以通过将BankAccount作为参数传递给客户端的方法来表示。
所以,这段代码是一种聚合关系。
class client
{
    public bool updateAccount(BankAccount ba){....}
}

正如您所看到的,它满足聚合的所有属性:

->它可以独立于client存在

->数据从整个分类器(client)流向部分(BankAccount


我现在感到困惑的是:如果我们在运行时调用updateAccount(new BanckAccount()),那么bankAccount仍然只存在于整个(客户端)的上下文中,因为它除了在整个上下文中没有被引用到其他地方。 - Bruno Barros
@BrunoBarros 这取决于你如何称呼它。大多数情况下,您将使用已创建的对象调用该方法,例如 updateAccount(my_bank_account);,这将满足聚合关系。 - Anirudha

3

这篇IBM的解释对我很有帮助:

例如,我们可以将汽车视为整体实体,而汽车轮胎则是整个汽车的一部分。轮胎可以提前几周制造,并在装配期间放置在仓库中。在这个例子中,轮胎类的实例显然独立于汽车类的实例之外存在。然而,有时候零件类的生命周期不独立于整体类的生命周期——这被称为组合聚合。例如,考虑公司与其部门之间的关系。公司和部门都被建模为类,一个部门在公司存在之前是不存在的。在这里,部门类的实例依赖于公司类的实例的存在。

对我来说,一个作品的创建/销毁生命周期(新建、发布)应该放在实例内部;而聚合必须有添加对象的选项,方法只需将对象ID保存为其自身属性。因此,在上述客户和银行账户示例中,真正取决于业务是否确定即使客户记录被删除(孤立账户),账户是否仍然存在。如果是聚合器,则会有客户端方法:

Class Client {
- (void) addToAccount:(BankAccount *)acc;
- (void) removeFromAccount:(BankAccount *)acc;
}

关闭账户的方法将成为BankAccount对象实例的一部分,因为它可以独立于客户存在。

相比之下,组合方法需要客户存在,如果客户不存在,则该账户所有者拥有的所有账户都将被删除。因此,您需要要求客户对象为您创建一个账户:

Class Client {
- (BankAccount *) openAccount;
- (BOOL) closeAccount:(BankAccount *)acc;
}

1

是的,你说得对。这是一个简单的组合

对于一个聚合,你的客户端类应该保持银行账户类的引用,但不控制其对象生命周期。

class Client
{
     private readonly BankAccount _account;

     public Client(BankAccount account)
     {
         _account = account;
     }

     //...
}

客户端对象销毁后,其中使用的银行账户对象可以被分配给另一个客户端。

谢谢您的回答,但我有一个后续问题: private readonly BankAccount _account; public Client(BankAccount account) { _account = account; }在这里的Client构造函数中,它是否会初始化一个新的BankAccount对象,然后将我们的只读变量_account分配为引用该对象,由account变量引用? - Mefhisto1
你应该通过指定的Bank对象内部独立集合所包含的账户对象来传递。主要思想是你无法控制BanckAccount对象的生命周期(就像组合关系一样),但是你可以引用它。如果你只是简单地写var client = new Client(new BankAccount) - 它将不会是聚合(因为账户对象将与客户端对象同时被销毁)。你可以为BankAccount类创建私有构造函数,并使用包含指定银行所有账户的AccountFactory来创建新对象。 - Dzmitry Martavoi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接