Neste tutorial de nosso curso de C++, vamos aprender como diferentes Classes e Objetos se relacionam uns com os outros, e estudar melhor essa relação.
Classes e Objetos na vida real
Até o momento, em nosso curso, usamos classes de maneira bem isolada, criando e usando apenas uma classe. Mas o mundo real, isso raramente ocorre. Vivemos em um mundo de objetos.
Muitas vezes, só faz sentindo existir um objeto, se existirem outros. Por exemplo, podemos ter a classe Humano e a classe Alimento. Os objetos do tipo Humano é você, eu, nossos amigos, familiares...os objetos do tipo Alimento, é o arroz, feijão, carne...tem sentido objetos da classe Humano existirem sem os objetos da classe Alimento? Claro que não.
E mesmo que um objeto não dependa de outro pra existir, ele pode depender de outros pra 'funcionar'. Por exemplo, objetos das classes Motor, Câmbio, Freio etc, trabalham juntos, para fazer um objeto o tipo Carro funcionar.
Ou seja, no mundo real, os objetos não são isolados. Nos seus projetos, você vai sempre criar várias classes, instanciar vários objetos, fazer um criar outro, um enviar informação, outro receber um dado de outro objeto, o método de um objeto usar vários outros objetos, e por ai vai.
Associação, Composição e Agregação
Quando um objeto usa um método ou serviço de outro objeto, dizemos que há uma associação aí. Sim, é algo bem amplo e genérico mesmo. Vamos especificar mais os tipos de relação, em composição e agregação.
Em ambos casos, um objeto possui outro objeto. Porém, na composição, um objeto só existe se o outro existir.
Por exemplo, o objeto do tipo Carro só existe se tiver um objeto da classe Motor. Não existe carro sem motor.
Um objeto do tipo Humano, só existe se tiver objetos como Coração, Pulmão...não existe uma pessoa sem esses órgãos (pelo menos por enquanto, vai que algum programa projeta e cria órgãos no futuro - claro, iria usar C++).
Já na agregação, um objeto possui outro, mas poderia existir sem ele. Por exemplo, todo carro tem um dono. Ou seja, tem objetos do tipo Humano que possuem objetos do tipo Carro. Mas um ser humano não precisa, obrigatoriamente, de um carro para existir.
Você precisa de um coração, uma cabeça....obrigatoriamente. De um carro, não. A relação de um objeto do tipo Carro e de um objeto do tipo Humano, é agregação.
Na composição, quando um objeto-pai é destruído, os objetos-filhos também serão. Um não existe sem o outro. Na agregação, não. Ou seja, associação é algo bem genérico, depois vem agregação (um pode existir sem o outro) e por fim a composição, a relação mais específica, onde um objeto só existe se outro existir.
Mas vamos deixar essas conversas genéricas de lado e ver na prática algumas relações entre objetos.
Composição em C++
Vamos começar pelo mais específico. Pra facilitar, chamamos as classes Humano e Carro de classes pais. As classes Motor, Coração, Câmbio, Pulmão etc, são as classes filhas, membros ou componentes. Ou chamamos de Todo e Partes, ou Objeto e Membro.
Para se caracterizar como composição, a parte faz parte do todo. Cada parte pertence somente a um objeto (ou seja, o coração pertence somente a um humano, e o motor do carro pertence somente a um carro).
A classe que representa o Todo, é que vai gerenciar as partes, os componentes. Esses membros não sabem da existência do 'Todo'.
Vamos criar a classe Engine, no construtor avisamos que estamos ligando o motor e na função destruidor avisamos que estamos desligando o motor. Engine.h:
#ifndef ENGINE_H
#define ENGINE_H
class Engine
{
public:
Engine();
~Engine();
};
#endif // ENGINE_H
Engine.cpp
#include "Engine.h"
#include <iostream>
using namespace std;
Engine::Engine()
{
cout << "Ligando o motor..."<<endl;
}
Engine::~Engine()
{
cout << "Desligando o motor..."<<endl;
}
Agora vamos criar a classe Car, que vai fazer quase o mesmo da Engine, vai dizer 'ligando o carro' na construtor e 'desligando o carro' no destruidor. Porém, ele vai ter um membro, um ponteiro pro tipo Engine, a variável 'myEng'.
Na classe, apenas declaramos esse ponteiro, Engine.h:
#ifndef CAR_H
#define CAR_H
#include "Engine.h"
class Car
{
public:
Car();
~Car();
Engine *myEng;
};
#endif // CAR_H
Mas já na implementação, nós instanciamos esse ponteiro, fazendo que o método construtor do objeto 'myEng' seja invocado. Na função destruidor da Car, deletamos o ponteiro do tipo Engine, invocando automaticamente a função destructor, Car.pp:
#include "Car.h"
#include <iostream>
using namespace std;
Car::Car()
{
cout << "Vamos ligar o carro."<<endl;
myEng = new Engine();
}
Car::~Car()
{
delete myEng;
cout << "Carro desligado."<<endl;
}
Nossa main.cpp:
#include <iostream>
#include "Car.h"
using namespace std;
int main()
{
Car myCar;
return 0;
}
O resultado é o seguinte:
Vamos ligar o carro.
Ligando o motor...
Desligando o motor...
Carro desligado.
O objeto 'myEng' SÓ EXISTE por causa do objeto 'myCar'. Sem o 'myCar', sem 'myEng'. 'myEng' É DO 'myCar'. Um objeto pertence a outro.
Quem 'controla' o 'myEng' também é o 'myCar'. Ele que cria o objeto, instancia...e morreu o objeto da classe Car, morre também o objeto da classe Engine. O objeto 'myEng' pertence somente ao objeto 'myCar', a nenhum outro mais. E ele também não sabe da existência de nenhum outro objeto e não sabe de nada da classe Car.
Essas coisas que caracterizam bem a composição.
Agregação em C++
Vamos agora criar a classe Human, para criar pessoas. Elas que vão dirigir o carro. Para criar um objeto dessa classe, precisamos passar uma string com o nome da pessoa e um objeto do tipo Car, veja como fica a Human.h:
#ifndef HUMAN_H
#define HUMAN_H
#include <string>
#include "Car.h"
class Human
{
public:
std::string name{};
Car myCar;
Human(const std::string& str, Car car);
~Human();
};
#endif // HUMAN_H
Agora a implementação Human.cpp:
#include "Human.h"
#include <iostream>
#include <string>
using namespace std;
Human::Human(const std::string& str, Car car)
{
name = str;
myCar = car;
cout<<name;
myCar.liga();
}
Human::~Human()
{
cout<<name;
myCar.desliga();
}
Fizemos algumas alterações na Car.h:
#ifndef CAR_H
#define CAR_H
#include "Engine.h"
class Car
{
public:
void liga();
void desliga();
Engine *myEng;
};
#endif // CAR_H
E na implementação Car.cpp:
#include "Car.h"
#include <iostream>
using namespace std;
void Car::liga()
{
cout << " vai ligar o carro."<<endl;
myEng = new Engine();
}
void Car::desliga()
{
cout <<" desligou o carro."<<endl;
delete myEng;
}
Ou seja, agora temos as funções liga() e desliga(), nos objetos do tipo Car (antes a gente ligava e desligava na construtor e destruidor).
Veja como ficou nossa main.cpp:
#include <iostream>
#include "Car.h"
#include "Human.h"
using namespace std;
int main()
{
Car car;
Human *h1 = new Human("Neil Peart", car);
delete h1;
cout << endl;
Human *h2 = new Human("Bruce Dickinson", car);
delete h2;
return 0;
}
Ou seja, criamos um objeto da classe Car e dois da classe Human.
Vamos ver algumas coisas interessantes. O resultado se rodarmos o código acima é:
Neil Peart vai ligar o carro.
Motor ligado.
Neil Peart desligou o carro.
Motor desligado.
Bruce Dickinson vai ligar o carro.
Motor ligado.
Bruce Dickinson desligou o carro.
Motor desligado.
Primeiro, o mesmo objeto 'car' é usado tanto no objeto 'h1' quanto no 'h2'.
Segundo, são os objetos h1 e h2 que acionam as funções ligar() e desligar() do carro, ou seja, eles que manuseiam o objeto 'car'.
Por fim, o objeto 'car' não deixa de existir quando o objeto 'h1' deixou de existir. Ele continuou lá vivo, tanto que foi usado pelo objeto 'h2' logo em seguida. Ou seja, embora haja uma relação entre eles (uns objetos usam outros), eles são de certa forma independentes, diferente da relação entre Car e Engine, onde os motores SÓ EXISTEM enquanto existem os carros, e só é usado um motor para cada carro.
Essas são as características da agregação.
Entre Car e Engine, é composição.
Entre Human e Car, é agregação.
Associação em C++
Deixa eu te contar o maior segredo da linguagem C++: um sistema grande, complexo, como uma estação espacial ou Microsoft Excel, são nada mais nada menos que vários objetos pequenos, desempenhando papéis específicos.
Ou seja, um sistema complexo nada mais é que vários objetos pequenos, trabalhando juntos, como uma associação.
Assim, durante sua carreira como programador C++, procure criar sempre Classes e Objetos específicos, bem definidos, com funções certas e de preferências os mais simples e diretos possíveis.
Repetindo: um grande projeto ou sistema nada mais é que um mooooonte de objetos trabalhando juntos, em associação, seja por composição ou agregação.
Organização é a chave do sucesso para se trabalhar com orientação a objetos!