c++ - ejemplos - relaciones entre clases y objetos
C++: Asociación, Agregación y Composición (1)
Estoy empezando a estudiar OOAD y tengo dificultades para encontrar un ejemplo de código en C++
que ilustre cómo la Association
, la Aggregation
y la Composition
se implementan mediante programación. (Hay varias publicaciones en todas partes, pero están relacionadas con C # o Java). Encontré un ejemplo o dos, pero todos están en conflicto con las instrucciones de mi instructor y estoy confundido.
Mi entendimiento es que en:
- Asociación: Foo tiene un puntero al objeto Bar como miembro de datos
- Agregación: Foo tiene un puntero al objeto Barra y los datos de la barra se copian en profundidad en ese puntero.
- Composición: Foo tiene un objeto Bar como miembro de datos.
Y así es como lo he implementado:
//ASSOCIATION
class Bar
{
Baz baz;
};
class Foo
{
Bar* bar;
void setBar(Bar* _bar)
{
bar=_bar;
}
};
//AGGREGATION
class Bar
{
Baz baz;
};
class Foo
{
Bar* bar;
void setBar(Bar* _bar)
{
bar = new Bar;
bar->baz=_bar->baz;
}
};
//COMPOSTION
class Bar
{
Baz baz;
};
class Foo
{
Bar bar;
Foo(Baz baz)
{
bar.baz=baz;
}
};
¿Es esto correcto? Si no, entonces, ¿cómo debería hacerse en su lugar? Se agradecería que también me diera una referencia de un código de un libro (para que pueda hablar con mi instructor)
Voy a ignorar la Agregación. No es un concepto muy claramente definido y, en mi opinión, causa más confusión de la que vale la pena. La composición y la asociación son suficientes, Craig Larman me lo dijo. Puede que no sea la respuesta que su instructor estaba buscando, pero es poco probable que se implemente en C ++ de manera diferente.
No hay una sola manera de implementar Composición y Asociación. La forma en que los implemente dependerá de sus requisitos, por ejemplo, la multiplicidad de la relación.
Composición
La forma más sencilla de implementar la composición es utilizar una variable simple de miembro Bar
, como usted sugirió. El único cambio que haría es inicializar la bar
en la lista de inicializadores de miembros constructores:
// COMPOSITION - with simple member variable
class Foo {
private:
Bar bar;
public:
Foo(int baz) : bar(baz) {}
};
En general, es una buena idea inicializar las variables miembro utilizando la lista de inicialización del constructor, puede ser más rápida y, en algunos casos, como las variables miembro const es la única forma de inicializarlas.
También puede haber una razón para implementar la composición utilizando un puntero. Por ejemplo, Bar
podría ser de tipo polimórfico y no conoce el tipo concreto en el momento de la compilación. O quizás desee reenviar declarar Bar
para minimizar las dependencias de compilación (ver lenguaje PIMPL ). O quizás la multiplicidad de esta relación es de 1 a 0..1 y necesitas tener una Bar
nula. Por supuesto, debido a que esta es la Composición, Foo
debería poseer la Bar
y en el mundo moderno de C ++ 11 / C ++ 14 preferimos usar punteros inteligentes en lugar de poseer punteros sin formato:
// COMPOSITION - with unique_ptr
class Foo {
private:
std::unique_ptr<Bar> bar;
public:
Foo(int baz) : bar(barFactory(baz)) {}
};
He usado std::unique_ptr
aquí porque Foo
es el único propietario de Bar
pero es posible que desee usar std::shared_ptr
si algún otro objeto necesita un std::weak_ptr
para Bar
.
Asociación
La asociación normalmente se implementaría usando un puntero como lo ha hecho:
// Association - with non-owning raw pointer
class Foo {
private:
Bar* bar;
public:
void setBar(Bar* b) { bar = b; }
};
Por supuesto, debe estar seguro de que Bar
estará vivo mientras Foo
esté utilizando, de lo contrario tendrá un puntero colgante. Si la vida útil de Bar
es menos clara, entonces una std::weak_ptr
puede ser más apropiada:
// Association - with weak pointer
class Foo {
private:
std::weak_ptr<Bar> bar;
public:
void setBar(std::weak_ptr<Bar> b) { bar = std::move(b); }
void useBar() {
auto b = bar.lock();
if (b)
std::cout << b->baz << "/n";
}
};
Ahora Foo
puede usar la Bar
sin temor a quedarse con un puntero colgante:
Foo foo;
{
auto bar = std::make_shared<Bar>(3);
foo.setBar(bar);
foo.useBar(); // ok
}
foo.useBar(); // bar has gone but it is ok
En algunos casos en los que la propiedad de Bar
está realmente clara, se podría implementar una Asociación utilizando std::shared_ptr
pero creo que debería ser un último recurso.
Con respecto a su implementación de Agregación con una copia profunda de un puntero de Bar
. No hubiera dicho que esa era una implementación típica pero, como dije, depende de sus requisitos. Debe asegurarse de llamar a delete
en el puntero de miembro de la bar
en el destructor Foo
ya que de lo contrario tiene una pérdida de memoria (o usa un puntero inteligente).