c# - programming - ¿Pones tus cálculos en tus sets o tus juegos?
python exercises oop (13)
Al determinar si una propiedad debe derivarse / calcularse, es importante considerar si el valor en el momento del cálculo necesita persistir.
En este caso de TotalCash, si la lógica de negocios cambia para el cálculo, es posible que la propiedad de TotalCash no cambie de forma retroactiva para los registros existentes.
solo poniéndolo ahí ...
cual es mejor ???
public class Order
{
private double _price;
private double _quantity;
public double TotalCash
{
get
{
return _price * _quantity;
}
}
o
public class Order
{
private double _totalCash;
private double _price;
private double _quantity;
private void CalcCashTotal()
{
_totalCash = _price * _quantity
}
public double Price
{
set
{
_price = value;
CalcCashTotal();
}
}
public double Quantity
{
set
{
_price = value;
CalcCashTotal();
}
}
public double TotalCash
{
get
{
return _totalCash;
}
}
El primero es mejor porque:
- Es más corto.
- Es más fácil de entender
- Es ligeramente presuntuoso recalcular TotalCash cada vez que se establece el precio o la cantidad. Debe ser tan vago como sea posible y solo calcular cuando se solicite.
Dicho eso, poner el cálculo en el setter efectivamente lo almacena en la memoria caché, por lo que si se encuentra con un problema de rendimiento, podría ser un cambio útil (a costa de la claridad).
El primero, porque:
1) Menos código es mejor;
2) menos complejidad;
3) Menos variables ayuda a obtener menos problemas colaterales;
4) La propiedad será siempre actualizada;
5) Si cambias el nombre del procedimiento "CalcCashTotal" obtienes más puntos para cambiar ...
Hay compensaciones. Si los cálculos son sencillos y no toman mucho tiempo, póngalo en el get. Le hace la vida más fácil, porque no tiene que preocuparse por hacer el control en cada conjunto del que depende el precio total, lo que podría generar errores.
Si el cálculo lleva mucho tiempo, entonces también podría tomar un enfoque híbrido. Puede establecer un booleano IsDirtyTotalPrice en todos los conjuntos dependientes, y luego hacer el cálculo sobre la marcha en el get y almacenarlo en caché para que el get solo calcule la variable cuando sea necesario. No hace el cálculo en los conjuntos porque podría haber muchos, y quiere hacer el cálculo lo menos posible.
public class Order
{
private double _totalCash;
private double _price;
private double _quantity;
private _IsDirtyTotalCash = true;
private void CalcCashTotal()
{
_totalCash = _price * _quantity
}
public double Price
{
set
{
_price = value;
_IsDirtyTotalCash = true;
}
}
public double Quantity
{
set
{
_price = value;
_IsDirtyTotalCash = true;
}
}
public double TotalCash
{
get
{
if(_IsDirtyTotalCash)
{
_totalCash = CalcTotalCost();
_isDirtyTotalCash = false;
}
return _totalCash;
}
}
}
La primera opción es mejor. La diferencia en "optimización" es minúscula. Sin mencionar, ¿qué pasa si la configuración ocurre una y otra vez, pero solo necesitas obtener TotalCost una vez? Me preocuparía más perder horas de desarrollador intentando depurar la clase una vez que se vuelve muy compleja.
Pero hay un momento importante cuando se necesita la segunda opción, específicamente cuando el valor calculado cambia los objetos calculados. Me está costando dar un ejemplo, así que usaré uno de la vida real en el que la cantidad de bahías en una pared esté determinada por su ancho.
class Wall {
public decimal Width {
get {
...
}
set {
ValidateChanges();
width = value;
CreateBays();
}
}
public Bays[] Bays {
get {
...
}
set {
ValidateChanges();
...
}
}
private void CreateBays() {
// Delete all the old bays.
...
// Create a new bay per spacing interval given the wall width.
...
}
}
Aquí, cada vez que cambia el ancho, se vuelven a crear las bahías en la pared. Si esto ocurriera en Bay.getter, sería bastante desastroso para las propiedades del objeto Bay. El getter debería averiguar si el ancho ha cambiado o no desde la última instrucción get, aumentando la complejidad .
Me gustaría ir con el método de hacer el cálculo en el getter para TotalCash porque menos código es casi siempre mejor. También asegura que el valor de TotalCash sea siempre correcto. Como un ejemplo artificial, si tuviera otro método NewOrder (Price, Qty) y olvidara llamar a CalculateTotal al final de este método, podría terminar fácilmente con un valor incorrecto para TotalCash.
Cálculo en el colocador podría ser mejor si el cálculo tarda un tiempo en procesarse y cambiar los valores de solo una o dos propiedades requeriría un nuevo cálculo, pero casi siempre es mejor optar por el enfoque que deja menos margen para el error, incluso si tarda un poco más en ejecutarse.
Me gustaría ir con la sugerencia híbrida de Charles Graham, pero quiero agregar mi granito de arena sobre por qué.
Muchas de las sugerencias anteriores hablan de complejidad y optimización, pero olvídese de que todo se va por la ventana cuando tiene en cuenta al consumidor de su clase. Si usted es el único consumidor y utilizó la primera implementación, es probable que recuerde:
double subTotal = myOrder.TotalCash;
double tax = subTotal * 0.05;
double shipping = subTotal > 100 ? 0 : 5.95;
double grandTotal = subTotal + tax + shipping;
OutputToUser(subTotal, tax, shipping, grandTotal);
Otras personas podrían no. Al ver que myOrder.TotalCash
es una propiedad, no un método, al menos supondría que se trata de un valor en caché. Es decir, el acceso a subTotal
en el ejemplo anterior es comparable en eficiencia al acceso a myOrder.TotalCash
. Sin darse cuenta de que hay un cálculo detrás de escena, escriben:
double tax = myOrder.TotalCash * 0.05;
double shipping = myOrder.TotalCash > 100 ? 0 : 5.95;
double grandTotal = myOrder.TotalCash + tax + shipping;
OutputToUser(myOrder.TotalCash, tax, shipping, grandTotal);
dejando myOrder.TotalCash
para representar el subtotal. Ahora, se ha calculado 4 veces en lugar de 1.
En resumen, estoy seguro de que no soy el único que cree que una propiedad representa un valor variable o en caché y un método procesa algo y devuelve un valor . Tiene sentido almacenar CalculateTotalCash()
y solo llamarlo una vez, porque espera que sea un golpe de rendimiento. Por otro lado, espera que TotalCash
sea un valor en caché y puede acceder a él a voluntad. Por lo tanto, es importante recalcular TotalCash
cuando cambie.
La sugerencia híbrida gana en el caso de múltiples conjuntos entre lecturas. De esa forma no perderás el tiempo calculando un valor para tirar.
Normalmente trato de ponerlos en el set ya que el valor que generan se almacenará internamente y solo se tendrá que calcular una vez. Solo debe poner cálculos en get si es probable que el valor cambie cada vez que se lo consulta.
En su ejemplo de precio / cantidad, podría tener un único método separado que vuelva a calcular la cantidad cuando se establezca el precio o la cantidad.
Pondré un cálculo en un get de solo lectura o un conjunto.
Soy de la opinión de que una propiedad debería comportarse como si tuviera una variable de respaldo.
No me gustan los cálculos que tardan mucho en leerse.
Ponerlo en la función get no es muy ideal. Lo volverá a calcular sin ningún motivo. Ni siquiera tiene más sentido. Así que aquí hay un caso en el que :: optimización :: gasp :: tiene sentido y es preferible. Calcule una vez y obtenga los beneficios.
Siempre me han dicho que si hay un trabajo o cálculo considerable que se debe hacer, deberías hacerlo en un método. No hay grandes ventajas de compilación / tiempo de ejecución hasta donde yo sé, pero tendrá más sentido para el consumidor del código. Una propiedad que demora un tiempo en devolverme el valor arrojaría una señal de advertencia de que algo podría estar mal.
Esos son mis 2 centavos ... pero incluso yo probablemente iría con tu primer ejemplo si la clase es así de simple :-)
Depende. ¿Su aplicación lee pesada o escribe pesada? ¿El cálculo es costoso? Si el cálculo es costoso y su aplicación es pesada, hágalo en el set, de esa manera solo pagará la penalización de calcificación varias veces (en comparación con las lecturas).
Mi regla, y yo recomiendo esto a cualquiera:
Métodos = con efectos secundarios Getters = sin efectos secundarios (EXCEPTO la memorización, que también está permitida en getters)