Skip to content

Latest commit

 

History

History
273 lines (154 loc) · 17.7 KB

16.3 — Aggregation.md

File metadata and controls

273 lines (154 loc) · 17.7 KB

16.3 — Aggregation

In the previous lesson on Composition, we noted that object composition is the process of creating complex objects from simpler ones. We also talked about one type of object composition, called composition. In a composition relationship, the whole object is responsible for the existence of the part.

In this lesson, we’ll take a look at the other subtype of object composition, called aggregation.

Aggregation

To qualify as an aggregation, a whole object and its parts must have the following relationship:

去验证一个聚合关系,一个整体对象和他的组成部分应该有下面的关系:

  • The part (member) is part of the object (class)

    成员是整体的一部分

  • The part (member) can belong to more than one object (class) at a time

    成员可以在同一时间属于多个对象

  • The part (member) does not have its existence managed by the object (class)

    成员的生命周期不由这个整体对象管理。

  • The part (member) does not know about the existence of the object (class)

    成员不知道这个整体对象的存在性。

Like a composition, an aggregation is still a part-whole relationship, where the parts are contained within the whole, and it is a unidirectional relationship. However, unlike a composition, parts can belong to more than one object at a time, and the whole object is not responsible for the existence and lifespan of the parts. When an aggregation is created, the aggregation is not responsible for creating the parts. When an aggregation is destroyed, the aggregation is not responsible for destroying the parts.

聚合依然是一种部分和整体的关系,部分被整体包括,是一种单向的关系。然而不像组合关系,部分可以属于超过一个的对象,而且这个整体对象对于这个部分对象也没有责任去管理它的生命期。在一个聚合对象创建的时候,聚合对象不负责创建其成分。当一个聚合对象被销毁的时候,聚合对象也不负责成分的销毁。

For example, consider the relationship between a person and their home address. In this example, for simplicity, we’ll say every person has an address. However, that address can belong to more than one person at a time: for example, to both you and your roommate or significant other. However, that address isn’t managed by the person -- the address probably existed before the person got there, and will exist after the person is gone. Additionally, a person knows what address they live at, but the addresses don’t know what people live there. Therefore, this is an aggregate relationship.

思考一下一个人和他的家庭地址的关系。在这个例子中,为了简便我们说所有人都有一个地址。然而一个地址可以属于超过一个人不仅仅属于一个人。例如你和你的室友。然而地址不由人来管理,地址可能在人到那之前就已经出现,而且在人走之后还留在那里。此外,人知道自己在哪个地址居住。地址却不知道自己身上住了什么人。这样就是一个聚合关系

Alternatively, consider a car and an engine. A car engine is part of the car. And although the engine belongs to the car, it can belong to other things as well, like the person who owns the car. The car is not responsible for the creation or destruction of the engine. And while the car knows it has an engine (it has to in order to get anywhere) the engine doesn’t know it’s part of the car.

另外,思考一下汽车和引擎的关系,引擎是汽车的一部分,尽管引擎属于汽车,但是同样也属于别的东西,比如引擎也属于一个人,因为这个人拥有这两车。这个车是不对引擎的产生和销毁负责的。然而汽车知道它有一个引擎,引擎却不知道它是汽车的一部分。

When it comes to modeling physical objects, the use of the term “destroyed” can be a little dicey. One might argue, “If a meteor fell out of the sky and crushed the car, wouldn’t the car parts all be destroyed too?” Yes, of course. But that’s the fault of the meteor. The important point is that the car is not responsible for destruction of its parts (but an external force might be).

用来描述物理对象的时候,销毁这个术语有点dicey。某人可能会争辩说“如果流星从天上掉下来,难道车的所有部分不会都被毁掉吗?”确实会,但是那是流星的错,关键的事情是车对组成它的部分的销毁不负责(但是一个外部力可能会)

We can say that aggregation models “has-a” relationships (a department has teachers, the car has an engine).

我们可以说聚合构建来一个“has-a”的关系模型。

Similar to a composition, the parts of an aggregation can be singular or multiplicative.

和组合一样,聚合对象的组成部分可以是一个也可以是多个。

Implementing aggregations

Because aggregations are similar to compositions in that they are both part-whole relationships, they are implemented almost identically, and the difference between them is mostly semantic. In a composition, we typically add our parts to the composition using normal member variables (or pointers where the allocation and deallocation process is handled by the composition class).

因为聚合和组合形成的组合关系是相似的,所以他们的实现方法基本上一样。关于他们的区别基本上是语义上的。在组合中我们经常把部分作为普通成员加到组合的整体对象中。

In an aggregation, we also add parts as member variables. However, these member variables are typically either references or pointers that are used to point at objects that have been created outside the scope of the class. Consequently, an aggregation usually either takes the objects it is going to point to as constructor parameters, or it begins empty and the subobjects are added later via access functions or operators.

在聚合中我们也还是添加为一个成员变量。然而,这些成员往往是引用或者是指针来指向一个从外部创建的对象。因此聚合经常从构造函数的参数中拿他的组成部分,或者它开始是空的,后来再慢慢的通过访问函数或者是运算符把子对象添加进去。

Because these parts exist outside of the scope of the class, when the class is destroyed, the pointer or reference member variable will be destroyed (but not deleted). Consequently, the parts themselves will still exist.

因为这些部分存在于类之外,当这个类对象死亡的时候,指针和引用会死掉,但是指向的对象则不会。所以组成的部分仍然还会存在下来。

Let’s take a look at a Teacher and Department example in more detail. In this example, we’re going to make a couple of simplifications: First, the department will only hold one teacher. Second, the teacher will be unaware of what department they’re part of.

看一下下面这个例子。这个例子中有两个简化的点。第一个点是这个系只有一位老师,另一个简化是这个老师不知道自己在哪个系里面

#include <iostream>
#include <string>
 
class Teacher
{
private:
  std::string m_name{};
 
public:
  Teacher(const std::string& name)
      : m_name{ name }
  {
  }
 
  const std::string& getName() const { return m_name; }
};
 
class Department
{
private:
  const Teacher& m_teacher; // This dept holds only one teacher for simplicity, but it could hold many teachers
 
public:
  Department(const Teacher& teacher)
      : m_teacher{ teacher }
  {
  }
};
 
int main()
{
  // Create a teacher outside the scope of the Department
  Teacher bob{ "Bob" }; // create a teacher
 
  {
    // Create a department and use the constructor parameter to pass
    // the teacher to it.
    Department department{ bob };
 
  } // department goes out of scope here and is destroyed
 
  // bob still exists here, but the department doesn't
 
  std::cout << bob.getName() << " still exists!\n";
 
  return 0;
}

In this case, bob is created independently of department, and then passed into department‘s constructor. When department is destroyed, the m_teacher reference is destroyed, but the teacher itself is not destroyed, so it still exists until it is independently destroyed later in main().

在这个例子中bob是独立于系而创建的,然后传入到系的构造函数中,当系被析构的时候,系的成员指针也会被析构,但是bob不会被析构。

Pick the right relationship for what you’re modeling

Although it might seem a little silly in the above example that the Teacher’s don’t know what Department they’re working for, that may be totally fine in the context of a given program. When you’re determining what kind of relationship to implement, implement the simplest relationship that meets your needs, not the one that seems like it would fit best in a real-life context.

尽管看起来有点傻,一个老师竟然不知道他自己在哪个系上班。但是在一个给定的程序里面这是完全没问题的。当你在决定什么样的关系去实现的时候,实现能满足你需要的最简单的关系,而不要去实现和现实生活最搭的那种。

For example, if you’re writing a body shop simulator, you may want to implement a car and engine as an aggregation, so the engine can be removed and put on a shelf somewhere for later. However, if you’re writing a racing simulation, you may want to implement a car and an engine as a composition, since the engine will never exist outside of the car in that context.

举个例子,如果你正在写一个车身修理厂模拟器,你可能想用聚合来实现车和引擎,所以引擎是可以插拔的。然而,如果你在写一个赛车模拟的时候,你可能想用组合的方式把车和引擎联系起来,因为在那种情景下,引擎不会出现在车的外面。

Rule: Implement the simplest relationship type that meets the needs of your program, not what seems right in real-life.

实现能满足你需要的最简单的关系,而不要去实现和现实生活最搭的那种。

Summarizing composition and aggregation

Compositions:

  • Typically use normal member variables

    通常用普通的成员变量

  • Can use pointer members if the class handles object allocation/deallocation itself

    可以使用指针,如果这个对象拥有一个动态分配内存空间的成员

  • Responsible for creation/destruction of parts

    对成员的创建和销毁负责

Aggregations:

  • Typically use pointer or reference members that point to or reference objects that live outside the scope of the aggregate class

    通常用指针或者引用成员指向被引用的外部对象

  • Not responsible for creating/destroying parts

    对组成部分的创建和销毁不负责任

It is worth noting that the concepts of composition and aggregation are not mutually exclusive, and can be mixed freely within the same class. It is entirely possible to write a class that is responsible for the creation/destruction of some parts but not others. For example, our Department class could have a name and a Teacher. The name would probably be added to the Department by composition, and would be created and destroyed with the Department. On the other hand, the Teacher would be added to the department by aggregation, and created/destroyed independently.

值得注意的是组合和聚合的概念不是互斥的,可以在一个类里面混合。写一个只对某些组成的生命期负责的类是完全可能的。举一个例子,我们的Department类可以有一个名字和一个老师。这个名字可能通过组合的方式加入到Department中,由Department来管理这个名字的生命周期。而老师可以通过聚合的方式加入到Department中,老师的创建和销毁是独立出来的。

While aggregations can be extremely useful, they are also potentially more dangerous, because aggregations do not handle deallocation of their parts. Deallocations are left to an external party to do. If the external party no longer has a pointer or reference to the abandoned parts, or if it simply forgets to do the cleanup (assuming the class will handle that), then memory will be leaked.

聚合是非常有用的,同时也潜藏着更多的危险。因为聚合不会处理组成他们的成员的销毁。销毁留给了外部的第三方来完成,所以如果外部没有指针指向这个成员的话,或者仅仅是忘记了去做销毁的清理工作,内存就会被泄漏。

For this reason, compositions should be favored over aggregations.

出于这个原因,应该更优先使用组合。而不是聚合。

few warnings/errata

For a variety of historical and contextual reasons, unlike a composition, the definition of an aggregation is not precise -- so you may see other reference material define it differently from the way we do. That’s fine, just be aware.

由于各种各样的历史原因 ,不像组合那样,聚合的定义不明确的。所以你可能会看到别的材料会用别的方式来定义,没关系知道就行了。

One final note: In the lesson Structs, we defined aggregate data types (such as structs and classes) as data types that group multiple variables together. You may also run across the term aggregate class in your C++ journeys, which is defined as a struct or class that has no provided constructors, destructors, or overloaded assignment, has all public members, and does not use inheritance -- essentially a plain-old-data struct. Despite the similarities in naming, aggregates and aggregation are different and should not be confused.

最后需要注意的是,在结构体的课程中,我们定义了聚合数据类型来把多个变量组合起来。你可能会在C++学习的过程中遇到aggregate class这样的术语来指示那种没有提供构造函数析构函数或者重载任何赋值运算符,所有的成员都是public的,而且也没有使用继承的结构体或者是类,本质上是纯粹的数据结构。不管aggregates和aggregation的名字有多么的相象,aggregation是不一样的,你不应该在比较的时候感到迷惑。

std::reference_wrapper

In the Department/Teacher example above, we used a reference in the Department to store the Teacher. This works fine if there is only one Teacher, but if there is a list of Teachers, say std::vector, we can’t use references anymore.

在上面的例子中Department只有一个Teacher成员,但是如果有一个列表的老师的话,比如说是std::vector,我们不能再使用引用了。

std::vector<const Teacher&> m_teachers{}; // Illegal

List elements cannot be references, because references have to be initialized and cannot be reassigned. Instead of references, we could use pointers, but that would open the possibility to store or pass null pointers. In the Department/Teacher example, we don’t want to allow null pointers. To solve this, there’s std::reference_wrapper.

list的元素不能是引用,因为引用必须被初始化,而且不能被重新赋值。但是我们可以用指针,而用指针的话,我们也会引入存储或者传递空指针的可能。为了解决这个问题,有一个std::reference_wrapper出现了。

Essentially, std::reference_wrapper is a class that acts like a reference, but also allows assignment and copying, so it’s compatible with lists like std::vector.

实质上,std::reference_wrapper是一个工作起来像引用的类,但是允许赋值和拷贝。所以它在list里面是可用的。

The good news is that you don’t really need to understand how it works to use it. All you need to know are three things:

好消息是你不需要知道它是怎么工作的。你只需要知道下面的三件事请:

  1. std::reference_wrapper lives in the header.

1.这个东西在functional头文件中。

  1. When you create your std::reference_wrapper wrapped object, the object can’t be an anonymous object (since anonymous objects have expression scope would leave the reference dangling).

2.当你在创建你的std::reference_wrapper 对象的时候,这个对象不可以是匿名对象,因为匿名对象只有一个表达式作用域会导致这个引用被悬挂起来。

  1. When you want to get your object back out of std::reference_wrapper, you use the get() member function.

3.当你想要获取std::reference_wrapper 引用指向的对象的时候,你需要使用get方法来获取。

Here’s an example using std::reference_wrapper in a std::vector:

#include <functional> // std::reference_wrapper
#include <iostream>
#include <vector>
#include <string>
 
int main()
{
  std::string tom{ "Tom" };
  std::string berta{ "Berta" };
 
  std::vector<std::reference_wrapper<std::string>> names{ tom, berta };
 
  std::string jim{ "Jim" };
 
  names.push_back(jim);
 
  for (auto name : names)
  {
    // Use the get() member function to get the referenced string.
    name.get() += " Beam";
  }
 
  std::cout << jim << '\n'; // Jim Beam
 
  return 0;
}

To create a vector of const references, we’d have to add const before the std::string like so

要创建常引用的时候,我们要给类型参数里面的类型添加const关键字,像下面这样。

// Vector of const references to std::string
std::vector<std::reference_wrapper<const std::string>> names{ tom, berta };

If this seems a bit obtuse or obscure at this point (especially the nested types), come back to it later after we’ve covered template classes and you’ll likely find it more understandable.

如果这样看起来有一点不容易理解的话(特别是嵌套了类型),在我们学习了模板类之后你再回来看看,你就会发现它很容易理解。