patterns - modern flat ui c#
Mantener una relaciĆ³n bidireccional entre las clases (6)
Es bastante común, especialmente en aplicaciones con un ORM, tener un mapeo bidireccional entre las clases. Me gusta esto:
public class Product
{
private List<Price> HistoricPrices { get; private set;}
}
public class Price
{
private Product Product { get; set; }
}
¿Hay una forma aceptada de mantener esta relación en el código? ¿De modo que cuando agregue un precio a un producto, la propiedad del Producto se configura automáticamente?
Idealmente, estoy buscando una solución fácilmente reutilizable. Parece incorrecto tener que agregar algo a una colección y luego establecer las relaciones opuestas manualmente.
Tenga en cuenta que esta no es una pregunta sobre cómo modelar productos y precios. Se trata de cómo modelar una relación bidireccional. Hay muchas situaciones donde esto es perfectamente razonable.
Ahora estoy trabajando en un problema similar a este y estoy usando el # 3 en la respuesta de LBushkin.
Tengo un objeto de almacenamiento de datos con listas para todas mis clases de datos. Las clases de datos tienen identificadores en ellas para referencias a las otras clases y llamo de nuevo a la clase de almacenamiento de datos para obtener el elemento de referencia.
Lo encontré realmente útil porque necesito poder filtrar los datos y eso se hace cuando solicito los datos del almacenamiento de datos.
En el pasado, he agregado métodos de "enlace", es decir, en lugar de "establecer" el precio en un producto, vincula el producto y el precio.
public void linkPrice(Price toLink) {
this.price = toLink;
toLink.setProduct(this);
}
(Si usa setPrice para hacer esto, entonces setProduct también haría esto, y siempre se llamarían entre sí en la segunda línea, por lo tanto creo un método de enlace explícito en lugar de usar los setters y getters. De hecho, el setter podría estar protegido contra paquetes .
YMMV, tu situación podría ser diferente.
En el pasado, he hecho algo como esto cuando necesitaba mantener este tipo de relación ...
public class Owner
{
public List<Owned> OwnedList { get; set; }
}
public class Owned
{
private Owner _owner;
public Owner ParentOwner
{
get
{
if ( _owner == null )
_owner = GetParentOwner(); // GetParentOwner() can be any method for getting the parent object
return _owner;
}
}
}
Esta no es la forma correcta de modelar este problema.
Un Product
debe tener un Price
, un Price
no debe tener un Product
:
public class Product
{
public Price CurrentPrice {get; private set; }
public IList<Price> HistoricPrices { get; private set;}
}
public class Price { }
En su configuración particular, ¿qué significa para un Price
tener un Product
? En la clase que creé anteriormente, usted podría manejar todos los precios dentro de la clase de Product
.
Lo primero que pensé es que, en su función / propiedad utilizada para agregar precios, agregue una línea de código como esta:
public void addPrice(Price p) {
//code to add price goes here
p.Product = this;
}
Primero, creo que el ejemplo de su presente es confuso: no es común que algo como un Precio se modele como un objeto o que se haga referencia a las entidades que tendrían un precio. Pero creo que la pregunta es legítima: en el mundo de ORM, esto se conoce como consistencia de gráficos. Que yo sepa, no hay una forma definitiva de abordar este problema, hay varias formas.
Comencemos cambiando ligeramente el ejemplo:
public class Product
{
private Manufacturer Manufacturer { get; private set;}
}
public class Manufacturer
{
private List<Product> Products { get; set; }
}
Entonces, cada Producto tiene un Fabricante, y cada Fabricante podría tener una lista de productos. El desafío con el modelo es que si la clase Producto y la clase Fabricante mantienen referencias desconectadas entre sí, la actualización de uno puede invalidar el otro.
Hay varias formas de abordar este problema:
Elimina la referencia circular. Esto resuelve el problema pero hace que el modelo de objeto sea menos expresivo y más difícil de usar.
Cambie el código para que la referencia del Fabricante en la lista Productos y Productos en Fabricante sea reflexiva . En otras palabras, cambiar uno afecta al otro. Esto generalmente requiere un código para que el colocador y la colección intercepten los cambios y los reflejen entre sí.
Administre una propiedad en términos de la otra. Por lo tanto, en lugar de almacenar una referencia a un fabricante dentro del Producto, lo calcula buscando entre todos los Fabricantes hasta que encuentre el que le pertenece. Por el contrario, puede mantener una referencia al Fabricante en la clase de Producto y crear la lista de Productos dinámicamente. En este enfoque, generalmente harías que un lado de la relación sea de solo lectura. Por cierto, este es el enfoque estándar de la base de datos relacional: las entidades se refieren entre sí a través de una clave externa que se gestiona en un solo lugar.
Externalice la relación de ambas clases y adminístrela en un objeto separado (a menudo llamado contexto de datos en ORM). Cuando Product quiere devolver su fabricante, le pide a DataContext. Cuando el fabricante desea devolver una lista de productos, hace lo mismo. Internamente, hay muchas maneras de implementar un contexto de datos, un conjunto de diccionarios bidireccionales no es infrecuente.
Finalmente, mencionaré que debe considerar el uso de una herramienta ORM (como NHibernate o CSLA) que puede ayudarlo a administrar la coherencia de los gráficos. En general, este no es un problema fácil de resolver correctamente, y puede complicarse fácilmente una vez que comienzas a explorar casos como relaciones de muchos a muchos, relaciones uno a uno y cargas de objetos perezosos. Es mejor utilizar una biblioteca o producto existente, en lugar de inventar un mecanismo propio.
Aquí hay algunos enlaces que hablan sobre las asociaciones bidireccionales en NHibernate que pueden serle útiles.
Aquí hay un ejemplo de código de administración de las relaciones directamente con el método n. ° 3, que suele ser el más simple. Tenga en cuenta que solo un lado de la relación es editable (en este caso, el fabricante): los consumidores externos no pueden establecer directamente el fabricante de un producto.
public class Product
{
private Manufacturer m_manufacturer;
private Manufacturer Manufacturer
{
get { return m_manufacturer;}
internal set { m_manufacturer = value; }
}
}
public class Manufacturer
{
private List<Product> m_Products = new List<Product>();
public IEnumerable<Product> Products { get { return m_Products.AsReadOnly(); } }
public void AddProduct( Product p )
{
if( !m_Products.Contains( p ) )
{
m_Products.Add( p );
p.Manufacturer = this;
}
}
public void RemoveProduct( Product p )
{
m_Products.Remove( p );
p.Manufacturer = null;
}
}