Diseño: ¿Método de objeto versus método de clase separada que toma Objeto como parámetro?

14

Por ejemplo, ¿es mejor hacer:

Pdf pdf = new Pdf();
pdf.Print();

o:

Pdf pdf = new Pdf();
PdfPrinter printer = new PdfPrinter();
printer.Print(pdf);

Otro ejemplo:

Country m = new Country("Mexico");
double ratio = m.GetDebtToGDPRatio();

o:

Country m = new Country("Mexico");
Country us = new Country("US");
DebtStatistics ds = new DebtStatistics();
double usRatio = ds.GetDebtToGDPRatio(us);
double mRatio = ds.GetDebtToGDPRatio(m);    

Mi preocupación en el último ejemplo es que hay estadísticas potencialmente infinitas (pero digamos que solo 10) es posible que desee saber sobre un país; ¿todos pertenecen al objeto del país?

p.ej

Country m = new Country("Mexico");
double ratio = m.GetGDPToMedianIncomeRatio();

Estas son proporciones simples, pero supongamos que las estadísticas son lo suficientemente complicadas como para justificar un método.

¿Dónde está esa línea entre las operaciones que son intrínsecas a un objeto frente a las operaciones que se pueden realizar en un objeto pero que no forman parte de él?

Usuario
fuente

Respuestas:

16

Tomando sus ejemplos en PDF como punto de partida, veamos esto.

http://en.wikipedia.org/wiki/Single_responILITY_principle

El Principio de responsabilidad única sugiere que un objeto debe tener un solo objetivo. Mantén esto en mente.

http://en.wikipedia.org/wiki/Separation_of_concerns

El principio de separación de preocupaciones nos dice que las clases no deberían tener funciones superpuestas.

Cuando observa estos dos, sugieren que la lógica debería ir en una clase solo si tiene sentido, solo si esa clase es responsable de hacerlo.

Ahora, en su ejemplo en PDF, la pregunta es, ¿quién es responsable de la impresión? ¿Qué tiene sentido?

Primer fragmento de código:

Pdf pdf = new Pdf();
pdf.Print();

Esto no está bien. Un documento PDF no se imprime solo. Se imprime con ... ta da! .. una impresora. Entonces su segundo fragmento de código es mucho mejor:

Pdf pdf = new Pdf();
PdfPrinter printer = new PdfPrinter();
printer.Print(pdf);

Esto tiene sentido. Una impresora PDF imprime un documento pdf. Mejor aún, una impresora no debería ser una impresora PDF o una impresora fotográfica. Debería ser solo una impresora capaz de imprimir lo que se le envíe de la mejor manera posible.

Pdf pdf = new Pdf();
Printer printer = new Printer();
printer.Print(pdf);

Entonces eso es simple. Ponga los métodos donde tengan sentido. Obviamente, no siempre es así de simple. Tome las estadísticas de su país, por ejemplo:

Country m = new Country("Mexico");
double ratio = m.GetDebtToGDPRatio();

Su preocupación es que puede haber un número n de estadísticas, y que no deberían estar en una clase de país. Eso es verdad. Sin embargo, si su modelo solo requiere estadísticas particulares, este ejemplo de modelado podría estar bien.

En este caso, se podría decir de forma bastante lógica que un país debería poder calcular sus propias estadísticas, específicas para su modelo y los requisitos disponibles.

Y ahí está la cosa: ¿cuáles son sus requisitos? Sus requisitos impulsarán la forma en que modela el mundo, el contexto, en el que estos requisitos deben cumplirse.

Si realmente tiene un número de estadísticas múltiple / variable, entonces su segundo ejemplo tiene más sentido:

Country m = new Country("Mexico");
DebtStatistics ds = new DebtStatistics();
double usRatio = ds.GetDebtToGDPRatio(m);

Mejor aún, tenga una superclase o interfaz abstracta llamada Estadísticas que tome un país como parámetro:

interface StatisticsCalculator // or a pure abstract class if doing C++
{
   double getStatistics(Country country); // or a pure virtual function if in C++
}

La clase DebtToGDPRatioStatisticsCalculator implementa StatisticsCalculator ....

clase InfantMortalityStatisticsCalculator implementa StatisticsCalculator ...

Y así sucesivamente y así sucesivamente. Lo que lleva a lo siguiente: generalización, delegación, abstracción. La recopilación estadística se delega a instancias específicas que generalizan una abstracción específica (una API de recopilación de estadísticas).

No sé si esto responde tu pregunta al 100%. Después de todo, no tenemos modelos infalibles basados ​​en leyes inviolables (como lo hacen las personas de EE.) Todo lo que puede hacer es poner las cosas donde tienen sentido. Y esa es una decisión de ingeniería que debes tomar. Lo mejor que puede hacer es familiarizarse realmente con los principios OO (y los buenos principios de modelado de software en general).

luis.espinal
fuente
1
+1 para la interfaz de StatisticsCalculator (y el uso posterior del patrón de estrategia). Y la respuesta bien pensada
edwardsmatt
3
tiempo insuficiente para deconstruir esto completamente en este momento, pero debe señalar que la clase Impresora se convertirá en una clase de Dios con el tiempo, estrechamente unida a todo tipo de clases de documentos. Pdf.Print sería preferible, pero todo depende de cómo defina la 'responsabilidad única' ;-)
Steven A. Lowe
@Steve: lo que está proponiendo es una idea horrible (hacer que Pdf implemente print ()). No refleja cómo se implementa la impresión en la vida real. Todos los sistemas operativos y las API de impresión que conozco proporcionan una abstracción Printer. Mire la lista de impresoras en su máquina XP / Vista (o en / var / spool o equivalente en * nix.) Cada aplicación serializa un objeto de documento en una de sus impresoras. No hay impresora de Word, impresora de texto o impresora de PDF. Solo hay impresoras específicas para el dispositivo de impresión y no específicas para el tipo de documento.
luis.espinal
2
+1 Me gusta. Estoy reflexionando sobre lo que dijiste. @ Steve y Luis: creo que la pieza que falta en el debate sobre los objetos de Dios es un objeto genérico de la impresora que debe aceptar algunos formatos estándar como ASCII o mapa de bits (aunque pdf probablemente también sea razonable) y debería ser responsabilidad de una tercera clase convertir un tipo de documento en particular (digamos un documento de ms word) a uno de estos formatos estándar.
Usuario
2
Me parece que quizás un PDF debería ser capaz de representarse en una interfaz Canvas o en un objeto Imagen que luego podría ser procesado por un objeto Impresora.
Winston Ewert
4

Creo que ninguno es definitivamente mejor que el otro. Usar pdf.Print () es más estricto, pero tener una clase PdfPrinter podría ser mejor si:

  • Necesita administrar instancias de impresoras
  • Hay una amplia gama de opciones y acciones que eliminarían la complejidad del pdf.Print (...) (por ejemplo, cancelación de impresión, formateo adicional, etc.)

De lo contrario, no me quedaría colgado.

Kevin Hsu
fuente
una buena respuesta práctica; el tiempo dirá cómo debe evolucionar esto
Steven A. Lowe
1
La sugerencia breve es mirar tanto la lógica como los datos cuando se aplica SRP, para decidir si lamentaremos no desacoplarlos antes. El problema con el almacenamiento de la configuración por impresora en la Pdfclase es que no se supone que se almacenen juntos; Pdfse almacena en un archivo, pero la configuración por impresora se debe almacenar con el perfil de usuario / máquina.
rwong