c++ - Usando getter/setter vs "tell, do not ask"?
getter-setter (4)
Dígale, no pregunte, el principio aquí a menudo se pega cuando uso getters o setters, y la gente me dice que no los use. El sitio explica claramente lo que debería y lo que no debería hacer, pero realmente no explica POR QUÉ debo decir, en lugar de preguntar. Me parece mucho más eficiente usar getters y setters, y puedo hacer más con ellos.
Imagina un Warrior
clase con atributos health
y armor
:
class Warrior {
unsigned int m_health;
unsigned int m_armor;
};
Ahora alguien ataca a mi guerrero con un ataque especial que reduce su armadura durante 5 segundos. Usar setter sería así:
void Attacker::attack(Warrior *target)
{
target->setHealth(target->getHealth() - m_damage);
target->setArmor(target->getArmor() - 20);
// wait 5 seconds
target->setArmor(target->getArmor() + 20);
}
Y con decir, no preguntes principio, se vería así (corrígeme si estoy equivocado):
void Attacker::attack(Warrior *target)
{
target->hurt(m_damage);
target->reduceArmor(20);
// wait 5 seconds
target->increaseArmor(20);
}
Ahora el segundo obviamente se ve mejor, pero no puedo encontrar los beneficios reales de esto. Todavía necesita la misma cantidad de métodos ( increase
/ decrease
frente a set
/ get
) y pierde el beneficio de preguntar si alguna vez necesita preguntar. Por ejemplo, ¿cómo establecerías la salud de los guerreros en 100? ¿Cómo averiguas si deberías usar heal
o hurt
, y cuánta salud necesitas para sanar o lastimar?
Además, veo setters y getters utilizados por algunos de los mejores programadores del mundo. La mayoría de las API lo utilizan, y se está utilizando en la biblioteca estándar todo el tiempo:
for (i = 0; i < vector.size(); i++) {
my_func(i);
}
// vs.
vector.execForElements(my_func);
Y si tengo que decidir si creo que las personas aquí me vinculan con un artículo sobre contar, no preguntar o creer que el 90% de las grandes compañías (Apple, Microsoft, Android, la mayoría de los juegos, etc.) que han tenido éxito Gané un montón de dinero y programas de trabajo, es un poco difícil para mí entender por qué lo diría, no pregunte, sea un buen principio.
¿Por qué debería usarlo (debería?) Cuando todo parece más fácil con getters y setters?
Todavía necesita la misma cantidad de métodos (aumentar / disminuir frente a establecer / obtener) y pierde el beneficio de preguntar si alguna vez necesita preguntar.
Lo entendiste mal. El punto es reemplazar getVariable
y setVariable
con una operación significativa: inflictDamage
, por ejemplo. Reemplazar getVariable
con increaseVariable
solo le da diferentes nombres más oscuros para getter y setter.
¿Dónde importa esto? Por ejemplo, no es necesario que proporciones un setter / getter para rastrear la armadura y la salud de manera diferente, un solo inflictDamage
puede ser procesado por la clase al tratar de bloquear (y dañar el escudo en el proceso) y luego tomar daño en el personaje si el escudo no es suficiente o su algoritmo lo exige. Al mismo tiempo, puede agregar lógica más compleja en un solo lugar.
Agrega un escudo mágico que temporalmente aumentará el daño causado por tus armas por un corto tiempo cuando recibas daño, por ejemplo. Si tiene getter / setters todos los atacantes necesitan ver si tiene dicho elemento, luego aplique la misma lógica en múltiples lugares para obtener el mismo resultado. En el enfoque Tell , los atacantes aún necesitan descubrir cuánto daño hacen y decírselo a tu personaje. Luego, el personaje puede descubrir cómo se distribuye el daño entre los objetos y si afecta al personaje de cualquier otra manera.
Complica el juego y agrega armas de fuego, luego puedes inflictFireDamage
Daño de fuego (o pasar el daño de fuego como un argumento diferente a la función de inflictDamage
). El Warrior
puede determinar si se ve afectada por un hechizo de resistencia al fuego e ignorar el daño por fuego, en lugar de tener todos los otros objetos en el programa tratando de descubrir cómo su acción afectará a los demás.
Bueno, si eso es así, ¿para qué molestarse con getters y setters después de todo? Solo puede tener campos públicos.
void Attacker::attack(Warrior *target)
{
target->health -= m_damage;
target->armor -= 20;
// wait 5 seconds
target->armor += 20;
}
La razón es simple aquí. Encapsulación Si tiene setters y getters, no es mejor que el campo público. Usted no crea una estructura aquí. Usted crea un miembro adecuado de su programa con semántica definida.
Citando el artículo:
El mayor peligro aquí es que al solicitar datos de un objeto, solo obtiene datos. No obtienes un objeto, no en el sentido amplio. Incluso si lo que recibió de una consulta es un objeto estructuralmente (por ejemplo, una Cadena) ya no es un objeto semánticamente. Ya no tiene ninguna asociación con su objeto propietario. Solo porque obtuviste una cadena cuyo contenido era "ROJO", no puedes preguntarle a la cuerda qué significa eso. ¿Es el apellido de los dueños? El color del auto? La condición actual del tacómetro? Un objeto conoce estas cosas, los datos no.
El artículo aquí sugiere aquí que "decir, no preguntar" es mejor aquí porque no puedes hacer cosas que no tienen sentido.
target->setHealth(target->getArmor() - m_damage);
Aquí no tiene sentido, porque la armadura no tiene nada en relación con la salud.
Además, te equivocaste con la lib estándar aquí. Los getters y setters solo se usan en std::complex
y eso se debe a la falta de funcionalidad del lenguaje (C ++ no tenía referencias entonces). Es lo opuesto, en realidad. La biblioteca estándar de C ++ fomenta el uso de algoritmos, para decir qué hacer en los contenedores.
std::for_each(begin(v), end(v), my_func);
std::copy(begin(v), end(v), begin(u));
En mi punto de vista, ambos códigos hacen lo mismo. La diferencia está en la expresividad de cada uno. El primero ( setters y getters ) puede ser más expresivo que el segundo ( tell, don ''ask ).
Es verdad que cuando preguntas, vas a tomar una decisión. Pero no sucede en la mayoría de las veces. A veces solo quieres saber o establecer algún valor del objeto, y esto no es posible con tell, no preguntar .
Por supuesto, cuando creas un programa, es importante definir las responsabilidades de un objeto y asegurarte de que estas responsabilidades permanezcan solo dentro del objeto, dejando fuera la lógica de tu aplicación. Esto ya lo sabemos, pero si necesita pedir una decisión que no sea responsabilidad de su objeto, ¿cómo lo hace con tell, no pregunte ?
En realidad, los getters y setters prevalecen, pero es común ver la idea de contar, no preguntar junto con ella. En otras palabras, algunas API tienen getters y setters y también los métodos de tell, no preguntes idea.
Una razón que viene a la mente es la capacidad de decidir dónde quieres que esté el control.
Por ejemplo, con su ejemplo de setter / getter, quien llama puede cambiar la salud del Guerrero arbitrariamente. En el mejor de los casos, su incubadora podría imponer valores máximos y mínimos para garantizar que la salud siga siendo válida. Pero si usa el formulario "decir" puede aplicar reglas adicionales. Es posible que no permita más que una cierta cantidad de daño o curación a la vez, y así sucesivamente.
El uso de este formulario le da un control mucho mayor sobre la interfaz del Guerrero: puede definir las operaciones que están permitidas, y puede cambiar su implementación sin tener que reescribir todo el código que las llama.