¿Cuál de estos diseños es mejor? ¿Cuáles son los pros y los contras de cada uno? ¿Cuál usarías? Se agradece cualquier otra sugerencia sobre cómo tratar con métodos como.
Es razonable suponer que Draw () es el único lugar desde el que se llama a los otros métodos de dibujo. Esto necesita expandirse a muchos más métodos Draw * y Show *, no solo a los tres que se muestran aquí.
public void Draw()
{
if (ShowAxis)
{
DrawAxis();
}
if (ShowLegend)
{
DrawLegend();
}
if (ShowPoints && Points.Count > 0)
{
DrawPoints();
}
}
private void DrawAxis()
{
// Draw things.
}
private void DrawLegend()
{
// Draw things.
}
private void DrawPoints()
{
// Draw things.
}
O
public void Draw()
{
DrawAxis();
DrawLegend();
DrawPoints();
}
private void DrawAxis()
{
if (!ShowAxis)
{
return;
}
// Draw things.
}
private void DrawLegend()
{
if (!ShowLegend)
{
return;
}
// Draw things.
}
private void DrawPoints()
{
if (!ShowPoints || Points.Count <= 0))
{
return;
}
// Draw things.
}
Respuestas:
No creo que pueda tener una regla general para este tipo de cosas, depende de la situación.
En este caso, sugeriría tener las cláusulas if fuera de los métodos porque los nombres de los métodos Draw implican que solo dibujan las cosas sin condiciones especiales.
Si encuentra que tiene que hacer las verificaciones antes de llamar a los métodos en muchos lugares, entonces puede poner la verificación dentro de los métodos y cambiarles el nombre para aclarar que esto está sucediendo.
fuente
Yo digo el segundo.
Los métodos deben tener el mismo nivel de abstracción.
En el segundo ejemplo, la responsabilidad del
Draw()
método es actuar como un controlador, llamando a cada uno de los métodos de dibujo individuales. Todo el código en esteDraw()
método está en el mismo nivel de abstracción. Esta guía entra en juego en escenarios de reutilización. Por ejemplo, si estuvieras tentado a reutilizar elDrawPoints()
método en otro método público (llamémosloSketch()
), no necesitarías repetir la cláusula de guardia (la declaración if para decidir si dibujar los puntos).Sin embargo, en el primer ejemplo, el
Draw()
método es responsable de determinar si se debe llamar a cada uno de los métodos individuales y luego llamar a esos métodos.Draw()
tiene algunos métodos de bajo nivel que funcionan, pero delega otros trabajos de bajo nivel a otros métodos y, por lo tanto,Draw()
tiene código en diferentes niveles de abstracción. Con el fin de reutilizaciónDrawPoints()
enSketch()
, lo que se necesita para replicar la cláusula de guardiaSketch()
también.Esta idea se discute en el libro de Robert C. Martin "Código limpio", que recomiendo encarecidamente.
fuente
DrawAxis()
et. Alabama. para indicar que su ejecución es condicional, tal vezTryDrawAxis()
. De lo contrario, debe navegar desde elDraw()
método a cada submétodo individual para confirmar su comportamiento.Mi opinión sobre el tema es bastante controvertida, pero tengan paciencia conmigo, ya que creo que casi todos están de acuerdo con el resultado final. Simplemente tengo un enfoque diferente para llegar allí.
En mi artículo Function Hell , explico por qué no me gusta dividir los métodos solo por crear métodos más pequeños. Solo los separo cuando sé que serán reutilizados, o por supuesto, cuando pueda reutilizarlos.
El OP declaró:
Esto me lleva a una tercera opción (intermedia) no mencionada. Creo 'bloques de código' o 'párrafos de código' para los que otros crearían funciones.
El OP también declaró:
... entonces este método crecerá muy grande rápidamente. Casi todos están de acuerdo en que esto disminuye la legibilidad. En mi opinión, la solución adecuada no es solo dividir en varios métodos, sino dividir el código en clases reutilizables. Mi solución probablemente se vería así.
Por supuesto, los argumentos aún tendrían que ser aprobados y tal.
fuente
Para los casos en los que está marcando un campo que controla si dibujar o no, tiene sentido que esté dentro
Draw()
. Cuando esa decisión se vuelve más complicada, tiendo a preferir la última (o dividirla). Si termina necesitando más flexibilidad, siempre puede expandirla.Observe que los métodos adicionales están protegidos, lo que permite que las subclases amplíen lo que necesitan. Esto también le permite forzar el dibujo para la prueba o cualquier otra razón que pueda tener.
fuente
DrawPointsIfItShould()
método que los atajos delif(should){do}
:)De esas dos alternativas, prefiero la primera versión. Mi razón es que quiero un método para hacer lo que el nombre implica, sin dependencias ocultas. DrawLegend debería dibujar una leyenda, no quizás dibujar una leyenda.
Sin embargo, la respuesta de Steven Jeuris es mejor que las dos versiones de la pregunta.
fuente
En lugar de tener
Show___
propiedades individuales para cada parte, probablemente definiría un campo de bits. Eso lo simplificaría un poco a:Aparte de eso, me inclinaría por verificar la visibilidad en el método externo, no en el interno. Es, después de todo,
DrawLegend
y noDrawLegendIfShown
. Pero depende del resto de la clase. Si hay otros lugares que llaman a eseDrawLegend
método y también necesitan verificarShowLegend
, probablemente solo movería el chequeDrawLegend
.fuente
Iría con la primera opción, con el "si" fuera del método. Describe mejor la lógica que se sigue, además le da la opción de dibujar un eje, por ejemplo, en los casos en que desea dibujar uno independientemente de la configuración. Además, elimina la sobrecarga de una llamada de función adicional (suponiendo que no esté en línea), que puede acumularse si se busca velocidad (su ejemplo parece que puede estar dibujando un gráfico donde puede que no sea un factor, sino en una animación o juego podría ser).
fuente
En general, prefiero que los niveles más bajos de código asuman que se cumplen las condiciones previas y empiecen a hacer cualquier trabajo que se supone que deben hacer, con las comprobaciones realizadas más arriba en la pila de llamadas. Esto tiene el beneficio adicional de guardar ciclos al no hacer verificaciones redundantes, pero también significa que puede escribir un código agradable como este:
en lugar de:
Y, por supuesto, esto se vuelve aún más desagradable si aplica una salida única, tiene memoria que necesita liberar, etc.
fuente
Todo depende del contexto:
fuente