c# - sharp - ¿Las funciones de obtención y configuración son populares entre los programadores de C++?
lenguajes de programacion mas demandados 2018 (14)
A riesgo de ser argumentativo, respaldaré un punto de vista opuesto que encontré por primera vez al leer "Holub en patrones". Era un punto de vista que era muy desafiante, pero tenía sentido para mí al reflexionar:
Getters y Setters son el mal
El uso de captadores y definidores está en oposición a los fundamentos del diseño orientado a objetos: la abstracción y la encapsulación de datos. El uso excesivo de captadores y configuradores hará que su código sea menos ágil y mantenible a largo plazo. En última instancia, exponen la implementación subyacente de su clase, bloqueando los detalles de la implementación en la interfaz de la clase.
Imagine que su campo ''std :: string Foo :: bar'' necesita cambiar de std :: string a otra clase de cadena, que, por ejemplo, está mejor optimizada o admite un conjunto de caracteres diferente. Deberá cambiar el campo de datos privados, el captador, el configurador y todo el código de cliente de esta clase que llama a estos captadores y definidores.
En lugar de diseñar sus clases para "proporcionar datos" y "recibir datos", diseñelas para "realizar operaciones" o "proporcionar servicios". Pregúntese por qué está escribiendo una función "GetBar". ¿Qué estás haciendo con esos datos? Quizás estés mostrando esos datos o procesándolos. ¿Está este proceso mejor expuesto como un método de Foo?
Esto no quiere decir que los captadores y setters no tienen su propósito. En C # Creo que la razón fundamental para su uso es para interactuar con el IDE de diseño de GUI de Visual Studio, pero si te encuentras escribiendo en C ++, probablemente sea mejor dar un paso atrás, mirar tu diseño y ver si hay algo. Está perdido.
Trataré de maquilar un ejemplo para ilustrar.
// A class that represents a user''s bank account
class Account {
private:
int balance_; // in cents, lets say
public:
const int& GetBalance() { return balance_; }
void SetBalance(int b) { balance_ = b; }
};
class Deposit {
private:
int ammount_;
public:
const int& GetAmount() { return ammount_; }
void SetAmmount(int a) { _balance = a; }
};
void DoStuffWithAccount () {
Account a;
// print account balance
int balance = a.GetBalance();
std::cout << balance;
// deposit some money into account
Deposit d(10000);
a.SetBalance( a.GetBalance() + d.GetValue());
}
No lleva mucho tiempo ver que este está muy mal diseñado.
- Los enteros son un tipo de datos de moneda horrible
- Un depósito debe ser una función de la cuenta.
Los captadores y definidores hacen que sea más difícil solucionar los problemas, ya que el código de cliente DoStuffWithAccount ahora está vinculado al tipo de datos que utilizamos para implementar el saldo de la cuenta.
Entonces, vamos a aprobar este código y ver qué podemos mejorar.
// A class that represents a user''s bank account
class Account {
private:
float balance_;
public:
void Deposit(float b) { balance_ += b; }
void Withdraw(float w) { balance_ -= w; }
void DisplayDeposit(std::ostream &o) { o << balance_; }
};
void DoStuffWithAccount () {
Account a;
// print account balance
a.DisplayBalance(std::cout);
// deposit some money into account
float depositAmt = 1000.00;
a.Deposit(depositAmt);
a.DisplayBalance(std::cout);
}
El ''flotador'' es un paso en la dirección correcta. Concedido, podría haber cambiado el tipo interno a ''flotar'' y aún así admitir el lenguaje getter / setter:
class Account {
private:
// int balance_; // old implementation
float balance_;
public:
// support the old interface
const int& GetBalance() { return (int) balance_; }
void SetBalance(int b) { balance_ = b; }
// provide a new interface for the float type
const float& GetBalance() { return balance_; } // not legal! how to expose getter for float as well as int??
void SetBalance(float b) { balance_ = b; }
};
pero no se demora mucho en darse cuenta de que el arreglo de getter / setter duplica su carga de trabajo y complica las cosas, ya que necesita admitir tanto el código que usó ints como el nuevo código que usará flotadores. La función de depósito hace que sea un poco más fácil ampliar el rango de tipos para depositar.
Una clase similar a una cuenta probablemente no sea el mejor ejemplo, ya que "obtener" el saldo de la cuenta es una operación natural para una cuenta. El punto general, sin embargo, es que debes tener cuidado con los captadores y definidores. No adquiera el hábito de escribir captadores y definidores para cada miembro de datos. Es bastante fácil exponerse y encerrarse en una implementación si no tiene cuidado.
Soy del mundo de C # originalmente, y estoy aprendiendo C ++. Me he estado preguntando acerca de las funciones get y set en C ++. En C #, el uso de estos es bastante popular, y herramientas como Visual Studio promueven el uso al hacerlos muy fáciles y rápidos de implementar. Sin embargo, este no parece ser el caso en el mundo de C ++.
Aquí está el código C # 2.0:
public class Foo
{
private string bar;
public string Bar
{
get { return bar; }
set { bar = value; }
}
}
O, en C # 3.0:
public class Foo { get; set; }
Que la gente diga, bueno, ¿cuál es el punto en eso? ¿Por qué no crear un campo público y luego convertirlo en una propiedad si es necesario? Sinceramente, en realidad no estoy seguro. Lo hago por buena práctica porque lo he visto hacer tantas veces.
Ahora porque estoy tan acostumbrado a hacerlo, siento que debería llevar el hábito a mi código C ++, pero ¿es esto realmente necesario? No lo veo hecho tan a menudo como con C #.
De todos modos, aquí está el C ++ de lo que he recogido:
class Foo
{
public:
std::string GetBar() const; // Thanks for the tip Earwicker.
void SetBar(std::string bar);
private:
std::string bar;
}
const std::string Foo::GetBar()
{
return bar;
}
void Foo::SetBar(std::string bar)
{
// Also, I always wonder if using ''this->'' is good practice.
this->bar = bar;
}
Ahora, para mí eso parece mucho trabajo de piernas; considerando el uso de las herramientas de Visual Studio, la implementación de C # tomaría literalmente segundos en implementarse, y C ++ me tomó mucho más tiempo escribir, creo que no vale la pena el esfuerzo, especialmente cuando la alternativa es de 5 líneas:
class Foo
{
public:
std::string Bar;
}
De lo que recojo, estas son las ventajas:
- Puede cambiar los detalles de la implementación de las funciones de obtención y configuración, por lo que en lugar de devolver un campo privado, puede devolver algo más interesante.
- Puede eliminar un get / set más tarde y hacerlo de solo lectura / escritura (pero para una interfaz orientada al público, esto no parece bueno).
Y las desventajas:
- Se necesitan años para escribir, ¿ realmente vale la pena el esfuerzo? Generalmente hablando. En algunos casos, las ventajas hacen que valga la pena el esfuerzo, pero quiero decir, hablando en términos de "buena práctica", ¿verdad?
Responder:
¿Por qué elegí la respuesta con menos votos ? En realidad estaba muy cerca de elegir la respuesta de veefu ; Sin embargo, mi opinión personal (que aparentemente es controvertida), es que la respuesta sobre el pudín incitado.
La respuesta que elegí, por otro lado, parece discutir ambos lados; Creo que los captadores y los programadores son malvados si se usan excesivamente (me refiero a que, cuando no es necesario y rompen el modelo de negocios), ¿por qué no deberíamos tener una función llamada GetBalance()
?
Seguramente esto sería mucho más versátil que PrintBalance()
; ¿Qué pasa si quisiera mostrarlo al usuario de otra manera que la clase quería? Ahora, en cierto sentido, GetBalance()
puede no ser lo suficientemente relevante como para argumentar que los "captadores y definidores son buenos" porque no tiene (o tal vez, no debería ) un setter acompañante, y hablando de eso, una función llamada SetBalance(float f)
podría ser malo (en mi opinión) porque implicaría para el implementador de la función que la cuenta debe ser manipulada fuera de la clase, lo que no es algo bueno.
Casi nunca utilizo captadores y definidores en mi propio código. La respuesta de Veefu me parece buena.
Si insiste en tener captadores y / o instaladores, puede usar macros para reducir la placa de la caldera.
#define GETTER(T,member) const T& Get##member() const { return member; }
#define SETTER(T,member) void Set##member(const T & value) { member = value; }
class Foo
{
public:
GETTER(std::string, bar)
SETTER(std::string, bar)
private:
std::string bar;
}
El compilador emitirá set_ y get_ si define una propiedad, así que en realidad es solo guardar algo de escritura.
Esta ha sido una discusión interesante. Esto es algo de mi libro favorito "CLR via C #".
Aquí es lo que he citado.
Personalmente, no me gustan las propiedades y me gustaría que no fueran compatibles con Microsoftm.NET Framework y sus lenguajes de programación. La razón es porque las propiedades parecen campos pero son métodos. Se sabe que esto causa una cantidad fenomenal de confusión. Cuando un programador ve el código que parece estar accediendo a un campo, hay muchas suposiciones que el programador hace que no sean ciertas para una propiedad. Por ejemplo,
- Una propiedad puede ser de solo lectura o de solo escritura; el campo de acceso es siempre
legible y escribible. Si usted define
una propiedad, lo mejor es ofrecer tanto
Obtener y establecer métodos de acceso.Un método de propiedad puede lanzar una excepción; el campo de acceso nunca lanza
una excepción.Una propiedad no se puede pasar como un parámetro out o ref a un método; un campo puede
Un método de propiedad puede tardar mucho tiempo en ejecutarse; campo de acceso siempre
se completa de inmediato. Una común
razón para usar propiedades es
realizar la sincronización de hilos, que puede detener el hilo para siempre, y
por lo tanto, una propiedad no debe ser
se utiliza si la sincronización de hilos es
necesario. En esa situación, se prefiere un método. Además, si se puede acceder a su clase de forma remota (por ejemplo,
tu clase se deriva de
System.MashalByRefObject), llamando
El método de propiedad será muy
lento, y por lo tanto, un método es
Preferido a una propiedad. En mi
opinión, clases derivadas de
MarshalByRefObject nunca debe usar
propiedadesSi se llama varias veces seguidas, un método de propiedad puede devolver
un valor diferente cada vez; un
campo devuelve el mismo valor cada
hora. La clase System.DateTime tiene una propiedad ahora de solo lectura que devuelve
La fecha y hora actual. Cada vez que consulte esta propiedad,
Devuelve un valor diferente. Esto es un
error, y Microsoft desea que
Podrían arreglar la clase haciendo
Ahora un método en lugar de una propiedad.Un método de propiedad puede causar efectos secundarios observables; El acceso al campo nunca lo hace. En otras palabras, un usuario de un tipo debería poder configurar varios
propiedades definidas por un tipo en cualquier
para que él o ella elija sin
notando cualquier comportamiento diferente en
el tipo.- Un método de propiedad puede requerir memoria adicional o devolver un
referencia a algo que no es
en realidad parte del estado del objeto, por lo que la modificación del objeto devuelto tiene
ningún efecto sobre el objeto original;
consultar un campo siempre devuelve un
referencia a un objeto que es
Garantizado para ser parte del estado del objeto original. Trabajando con un
La propiedad que devuelve una copia puede ser
muy confuso para los desarrolladores, y
esta característica con frecuencia no está documentada.
En tu ejemplo:
class Foo
{
public:
const std::string GetBar(); // Should this be const, not sure?
Probablemente te refieres a esto:
std::string GetBar() const;
Poner la const
al final significa "Esta función no modifica la instancia de Foo a la que se llama", por lo que de alguna manera lo marca como un captador puro.
Los captadores puros ocurren frecuentemente en C ++. Un ejemplo en std::ostringstream
es la función str()
. La biblioteca estándar a menudo sigue un patrón de usar el mismo nombre de función para un par de funciones getter / setter, siendo un ejemplo nuevamente.
En cuanto a si es demasiado trabajo para escribir, y vale la pena, ¡parece una pregunta extraña! Si necesita dar a los clientes acceso a alguna información, proporcione un captador. Si no lo haces, entonces no lo hagas.
Los argumentos en contra de Get / Set en términos de diseño de API en el ejemplo bancario son acertados. No exponga campos o propiedades si permitirán que los usuarios rompan sus reglas comerciales.
Sin embargo, una vez que haya decidido que necesita un campo o propiedad, use siempre una propiedad.
Las propiedades automáticas en c # son muy fáciles de usar, y hay muchos escenarios (enlace de datos, serialización, etc.) que no funcionan con campos, pero requieren propiedades.
Los métodos Get y Set son útiles si tiene restricciones en un valor variable. Por ejemplo, en muchos modelos matemáticos hay una restricción para mantener una determinada variable flotante en el rango [0,1]. En este caso, Get y Set (especialmente Set) pueden jugar un buen papel:
class Foo{
public:
float bar() const { return _bar; }
void bar(const float& new_bar) { _bar = ((new_bar <= 1) && (new_bar >= 0))?new_bar:_bar; } // Keeps inside [0,1]
private:
float _bar; // must be in range [0,1]
};
Además, algunas propiedades deben recalcularse antes de leer. En esos casos, puede llevar mucho tiempo de computación innecesario recalcular cada ciclo. Por lo tanto, una forma de optimizarlo es recalcular solo cuando se lee. Para hacerlo, sobrecargue el método Get para actualizar la variable antes de leerla.
De lo contrario, si no hay necesidad de validar los valores de entrada o actualizar los valores de salida, hacer que la propiedad sea pública no es un delito y usted puede aceptarla.
No hay una convención realmente estricta sobre esto, como existe en C # o Java. Muchos programadores de C ++ solo harían pública la variable y se salvarían el problema.
Como han dicho otras respuestas, a menudo no debería ser necesario establecer, y hasta cierto punto, obtener métodos.
Pero si y cuando los haces, no hay necesidad de escribir más de lo necesario:
class Foo
{
public:
std::string Bar() const { return bar; }
void Bar(const std::string& bar) { this->bar = bar; }
private:
std::string bar;
};
Al declarar las funciones en línea en la clase, se guarda la escritura y se sugiere al compilador que desea que las funciones estén en línea. Y no es mucho más tipeo que los equivalentes de C #. Una cosa a tener en cuenta es que eliminé los prefijos get / set. En su lugar, solo tenemos dos sobrecargas de Bar (). Eso es bastante común en C ++ (después de todo, si no toma ningún argumento, sabemos que es el captador, y si toma un argumento, es el colocador. No necesitamos el nombre para decirnos eso), y Se ahorra un poco más al escribir.
Obtención y configuración de miembros de datos en cuanto miembros de datos: incorrecto
Obteniendo y configurando elementos de la abstracción: Bien .
Sí, obtener y configurar son populares en el mundo de c ++.
Si está desarrollando componentes COM, entonces sí, es muy popular.
Si usa C ++ / CLI como su variante de C ++, entonces tiene soporte de propiedad nativa en el idioma, por lo que puede usar
property String^ Name;
Esto es lo mismo que
String Name{get;set;}
Cª#. Si necesita un control más preciso sobre los métodos de obtención / ajuste, puede utilizar
property String^ Name
{
String^ get();
void set(String^ newName);
}
en el encabezado y
String^ ClassName::Name::get()
{
return m_name;
}
void ClassName::Name::set(String^ newName)
{
m_name = newName;
}
en el archivo .cpp. No puedo recordar de antemano, pero creo que puede tener diferentes permisos de acceso para los métodos de obtención y configuración (público / privado, etc.).
Colin
Yo diría que proporcionar accesores es más importante en C ++ que en C #.
C ++ no tiene soporte incorporado para propiedades. En C # puede cambiar un campo público a una propiedad principalmente sin cambiar el código de usuario. En C ++ esto es harder .
Para escribir menos, puede implementar setters / getters triviales como métodos en línea:
class Foo
{
public:
const std::string& bar() const { return _bar; }
void bar(const std::string& bar) { _bar = bar; }
private:
std::string _bar;
};
Y no olvides que los captadores y setters son un tanto malvados.
[editar] Parece que debo enfatizar que los definidores necesitan validar los parámetros y hacer cumplir los invariantes, por lo que generalmente no son tan simples como lo son aquí. [/editar]
No con todos, porque de la tipificación extra. Tiendo a usarlos mucho más a menudo ahora que Visual Assist me da "campo de encapsulado".
El trabajo preliminar no es más si implementas solo los setters / getters predeterminados en línea en la declaración de clase (lo que tiendo a hacer, aunque los setters más complejos se mueven al cuerpo).
Algunas notas:
constness: Sí, el getter debe ser const. Sin embargo, no sirve de nada hacer que el valor de retorno sea constante si lo devuelve por valor. Para valores de retorno potencialmente complejos, es posible que desee utilizar const y aunque:
std::string const & GetBar() const { return bar; }
Encadenamiento de Setter: a muchos desarrolladores les gusta modificar el setter como tal:
Foo & SetBar(std::string const & bar) { this->bar = bar; return *this; }
Lo que permite llamar a múltiples configuradores como tales
Foo foo;
foo.SetBar("Hello").SetBaz("world!");
Sin embargo, no es universalmente aceptado como algo bueno.
__declspec(property) : Visual C ++ proporciona esta extensión no estándar para que las personas que llaman puedan usar la sintaxis de propiedad nuevamente. Esto aumenta un poco el trabajo de la clase, pero hace que el código de la persona que llama sea mucho más amigable.
Entonces, en conclusión, hay un poco más de trabajo de piernas, pero un puñado de decisiones que tomar en C ++. Típico;)
obtener y configurar es un dolor infligido a las personas si tiene que usarlas en cualquier idioma.
Eiffel lo tiene mucho mejor donde todo lo que difiere es la cantidad de información que debe proporcionar para obtener la respuesta: una función con 0 parámetros es lo mismo que acceder a una variable miembro, y puede cambiar libremente entre ellos.
Cuando controla ambos lados de una interfaz, la definición de la interfaz no parece ser un problema tan grande. Sin embargo, cuando desea cambiar los detalles de la implementación e inflige la recompilación del código del cliente, como es el caso común en C ++, desea poder minimizarlo tanto como sea posible. Como tal, pImpl y get / set se utilizarían más en las API públicas para evitar tales daños.