Diseño de clase vs. IDE: ¿Las funciones que no son de amigos no miembros realmente valen la pena?

StackOverflow https://stackoverflow.com/questions/135634

Pregunta

En el excelente libro (de lo contrario) Estándares de codificación de C ++ , artículo 44, titulado " Prefiere escribir funciones de no amigos no miembros , Sutter y Alexandrescu recomiendan que solo las funciones que realmente necesitan acceso a los miembros de una clase sean ellos mismos miembros de esa clase. Todas las demás operaciones que se pueden escribir utilizando solo las funciones miembro no deben formar parte de la clase. Deben ser no miembros y no amigos. Los argumentos son que:

  • Promueve la encapsulación, porque hay menos código que necesita acceso a las partes internas de una clase.
  • Facilita la escritura de plantillas de funciones, ya que no tiene que adivinar cada vez si alguna función es miembro o no.
  • Mantiene a la clase pequeña, lo que a su vez facilita la prueba y el mantenimiento.

Aunque veo el valor en estos argumentos, veo un gran inconveniente: ¡mi IDE no puede ayudarme a encontrar estas funciones! Cada vez que tengo un objeto de algún tipo, y quiero ver En qué operaciones están disponibles, no puedo escribir " pMysteriousObject- > " y obtener una lista de funciones de miembro más.

Al final, mantener un diseño limpio es lo que hace que tu programación sea más fácil. Pero esto en realidad haría que la mía sea mucho más difícil.

Así que me pregunto si realmente vale la pena. ¿Cómo lidias con eso?

¿Fue útil?

Solución

Voy a tener que estar en desacuerdo con Sutter y Alexandrescu en este caso. Creo que si el comportamiento de la función foo () cae dentro del ámbito de las responsabilidades de la clase Bar , entonces foo () debería ser parte de bar () .

El hecho de que foo () no necesite acceso directo a los datos de los miembros de Bar no significa que no sea conceptualmente parte de la barra de . También puede significar que el código está bien factorizado. No es raro tener funciones miembro que realicen todo su comportamiento a través de otras funciones miembro, y no veo por qué debería ser así.

Estoy totalmente de acuerdo en que las funciones relacionadas periféricamente no deberían ser parte de la clase, pero si algo es esencial para las responsabilidades de la clase, no hay razón para que no sea un miembro, independientemente de si está directamente en contacto con los datos del miembro.

En cuanto a estos puntos específicos:

  

Promueve la encapsulación, porque hay menos código que necesita acceso a las partes internas de una clase.

De hecho, cuantas menos funciones accedan directamente a las partes internas, mejor. Eso significa que tener funciones miembro hacer todo lo posible a través de otras funciones miembro es una buena cosa. La división de funciones bien factorizadas de la clase solo te deja con media clase, lo que requiere un montón de funciones externas para que sean útiles. Alejar las funciones bien factorizadas de sus clases también parece desalentar la escritura de funciones bien factorizadas.

  

Facilita la escritura de plantillas de funciones, ya que no tiene que adivinar cada vez si alguna función es miembro o no.

No entiendo esto en absoluto. Si sacas un montón de funciones de las clases, has puesto más responsabilidad en las plantillas de funciones. Se ven obligados a asumir que los argumentos de la plantilla de clase proporcionan la funcionalidad incluso menos , a menos que asumamos que la mayoría de las funciones extraídas de sus clases se convertirán en una plantilla (ugh).

  

Mantiene la clase reducida, lo que a su vez facilita la prueba y el mantenimiento.

Um, claro. También crea muchas funciones externas adicionales para probar y mantener. No veo el valor en esto.

Otros consejos

Scott Meyers tiene una opinión similar a Sutter, consulte aquí .

También dice claramente lo siguiente:

"Basado en su trabajo con varias clases de cuerdas, Jack Reeves ha observado que algunas funciones simplemente no se sienten" justo cuando se hacen no miembros, incluso si podrían ser no amigos no miembros. El " mejor " la interfaz para una clase solo se puede encontrar al equilibrar muchas preocupaciones en conflicto, de las cuales el grado de encapsulación es solo uno. "

Si una función fuera algo que " simplemente tiene sentido " para ser una función miembro, hazlo uno. Del mismo modo, si no es realmente parte de la interfaz principal, y " simplemente tiene sentido " para ser un no miembro, haz eso.

Una nota es que con las versiones sobrecargadas de, por ejemplo, operator == (), la sintaxis permanece igual. Por lo tanto, en este caso, no tiene ninguna razón no para convertirla en una función flotante que no sea un miembro no miembro declarada en el mismo lugar que la clase, a menos que realmente necesite acceso a miembros privados (en mi experiencia, rara vez lo hará). E incluso así puede definir operator! = () No miembro y en términos de operator == ().

No creo que sea incorrecto decir que entre ellos, Sutter, Alexandrescu y Meyers han hecho más por la calidad de C ++ que cualquier otra persona.

Una pregunta simple que hacen es:

  

Si una función de utilidad tiene dos clases independientes como parámetros, ¿qué clase debería " poseer " la función miembro?

Otro problema, es que solo puedes agregar funciones miembro donde la clase en cuestión está bajo tu control. Cualquier función auxiliar que escriba para std :: string tendrá que ser no miembros ya que no puede volver a abrir la definición de clase.

Para estos dos ejemplos, su IDE proporcionará información incompleta, y usted tendrá que usar la "manera antigua".

Dado que los expertos en C ++ más influyentes del mundo consideran que las funciones no miembros con un parámetro de clase son parte de la interfaz de clases, esto es más un problema con su IDE que con el estilo de codificación.

Es probable que su IDE cambie en una o dos versiones, e incluso puede hacer que agreguen esta función. Si cambia su estilo de codificación para adaptarlo al IDE de hoy, es posible que descubra que tiene problemas más grandes en el futuro con un código que no se puede extender / mantener.

Es cierto que las funciones externas no deben formar parte de la interfaz. En teoría, su clase solo debe contener los datos y exponer la interfaz para lo que se pretende y no para funciones utilitarias. Agregando funciones de utilidad a la interfaz solo crece la base de código de clase y haz que sea menos mantenible. Actualmente mantengo una clase con alrededor de 50 métodos públicos, eso es una locura.

Ahora, en realidad, estoy de acuerdo en que esto no es fácil de hacer cumplir. A menudo es más fácil simplemente agregar otro método a su clase, incluso más si está utilizando un IDE que realmente puede simplemente agregar un nuevo método a una clase existente.

Para mantener mis clases simples y poder centralizar la función externa, a menudo uso una clase de utilidad que funciona con mi clase, o incluso espacios de nombres. Empiezo creando la clase que envolverá mis datos y expondrá la interfaz más simple posible. Entonces creo una nueva clase para cada tarea que tengo que hacer con la clase.

Ejemplo: cree un punto de clase, luego agregue un PointDrawer de clase para dibujarlo en un mapa de bits, PointSerializer para guardarlo, etc.

Si les da un prefijo común, tal vez su IDE le ayude si escribe

::prefix

o

namespace::prefix

En muchos idiomas de OOP, los métodos que no pertenecen a una clase que no son amigos son ciudadanos de tercera clase que residen en un orfanato que no está conectado a nada. Cuando escribo un método, me gusta escoger buenos padres, una clase apropiada, donde tengan las mejores oportunidades de sentirse bienvenidos y ayudar.

Pensé que el IDE en realidad te estaba ayudando.

El IDE oculta las funciones protegidas de la lista porque no están disponibles para el público como lo hizo el diseñador de la clase .

Si estuvo dentro del alcance de la clase y escribió this-> , las funciones protegidas se mostrarán en la lista.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top