Me preguntaba sobre un buen diseño de clase orientado a objetos. En particular, me cuesta decidir entre estas opciones:
- método estático vs instancia
- método sin parámetros o valor de retorno vs método con parámetros y valor de retorno
- superposición vs funcionalidad método distinto
- método privado vs público
Ejemplo 1:
Esta implementación utiliza métodos de instancia, sin valor de retorno o parámetros, sin funcionalidad superpuesta, y todos los métodos públicos
XmlReader reader = new XmlReader(url);
reader.openUrl();
reader.readXml();
Document result = reader.getDocument();
Ejemplo 2
Esta implementación utiliza métodos estáticos, con valores y parámetros de retorno, con funcionalidad superpuesta y métodos privados.
Document result = XmlReader.readXml(url);
En el ejemplo uno, todos los métodos son de instancia pública, lo que facilita su prueba unitaria. Aunque todos los métodos son distintos, readXml () depende de openUrl () en que openUrl () debe llamarse primero. Todos los datos se declaran en campos de instancia, por lo que no hay valores o parámetros de retorno en ningún método, excepto en el constructor y los accesores.
En el ejemplo dos, solo un método es público, el resto es estático privado, lo que dificulta la prueba de la unidad. Los métodos se superponen en que readXml () llama a openUrl (). No hay campos, todos los datos se pasan como parámetros en los métodos y el resultado se devuelve de inmediato.
¿Qué principios debo seguir para hacer una programación orientada a objetos adecuada?
fuente
Respuestas:
El ejemplo 2 es bastante malo para las pruebas ... y no quiero decir que no pueda probar las partes internas. Tampoco puede reemplazar su
XmlReader
objeto por un objeto simulado ya que no tiene ningún objeto.El ejemplo 1 es innecesariamente difícil de usar. Qué pasa
que no es más difícil de usar que tu método estático.
Cosas como abrir la URL, leer XML, convertir bytes a cadenas, analizar, cerrar sockets y lo que sea, no son interesantes. Crear un objeto y usarlo es importante.
Entonces, en mi humilde opinión, el diseño OO adecuado es hacer públicas solo las dos cosas (a menos que realmente necesite los pasos intermedios por alguna razón). La estática es malvada.
fuente
Document result = new XmlReader(url).getDocument();
¿Por qué? Para que pueda actualizarloDocument result = whateverReader.getDocument();
y me lo hayawhateverReader
entregado por otra cosa.Aquí está la cosa: no hay una respuesta correcta, y no hay una definición absoluta de "diseño orientado a objetos adecuado" (algunas personas le ofrecerán una, pero son ingenuos ... deles tiempo).
Todo se reduce a tus METAS.
Eres un artista y el papel está en blanco. Puede dibujar un retrato lateral en blanco y negro delicado y finamente dibujado a lápiz, o una pintura abstracta con enormes cortes de neones mixtos. O cualquier cosa en el medio.
Entonces, ¿qué SIENTE CORRECTO para el problema que está resolviendo? ¿Cuáles son las quejas de las personas que necesitan usar sus clases para trabajar con xml? ¿Qué tiene de difícil su trabajo? ¿Qué tipo de código están tratando de escribir que rodea las llamadas a su biblioteca, y cómo pueden ayudar a que fluya mejor para ellos?
¿Les gustaría más brevedad? ¿Les gustaría que fuera muy inteligente para determinar los valores predeterminados para los parámetros, de modo que no tengan que especificar mucho (o nada) y adivine correctamente? ¿Puedes automatizar las tareas de configuración y limpieza que requiere tu biblioteca para que les sea imposible olvidar esos pasos? ¿Qué más puedes hacer por ellos?
Demonios, lo que probablemente deba hacer es codificarlo de 4 a 5 formas diferentes, luego ponerse el sombrero de consumidor y escribir el código que usa los 5, y ver cuál se siente mejor. Si no puede hacer eso para toda su biblioteca, hágalo para un subconjunto. Y también necesita agregar algunas alternativas adicionales a su lista: ¿qué pasa con una interfaz fluida, o un enfoque más funcional, o parámetros con nombre, o algo basado en DynamicObject para que pueda inventar "pseudo-métodos" significativos que los ayuden? ¿fuera?
¿Por qué es jQuery el rey en este momento? Debido a que Resig y su equipo siguieron este proceso, hasta que encontraron un principio sintáctico que redujo increíblemente la cantidad de código JS que se necesita para trabajar con dom y eventos. Ese principio sintáctico no estaba claro para ellos ni para nadie más cuando comenzaron. Lo encontraron.
Como programador, esa es su mayor vocación. Andas a tientas en la oscuridad intentando cosas hasta que lo encuentras. Cuando lo hagas, lo sabrás. Y le dará a sus usuarios un gran salto de productividad. Y de eso se trata el diseño (en el ámbito del software).
fuente
La segunda opción es mejor, ya que es más simple de usar para las personas (incluso si solo eres tú), mucho más simple.
Para las pruebas unitarias, simplemente probaría la interfaz, no las internas, si realmente desea mover las internas de privadas a protegidas.
fuente
1. Estático vs. instancia
Creo que hay pautas muy claras sobre qué es un buen diseño OO y qué no lo es. El problema es que la blogósfera hace que sea difícil separar lo bueno de lo malo y lo feo. Puede encontrar algún tipo de referencia que respalde incluso la peor práctica que se le ocurra.
Y la peor práctica que se me ocurre es el estado global, incluidas las estadísticas que mencionaste y el Singleton favorito de todos. Algunos extractos del clásico artículo de Misko Hevery sobre el tema .
Esto se reduce a que no debe proporcionar referencias estáticas a nada que tenga algún tipo de estado almacenado. El único lugar donde uso estadísticas estáticas es para constantes enumeradas, y tengo dudas sobre eso.
2. Métodos con parámetros de entrada y valores de retorno vs. métodos sin ninguno
Lo que debe tener en cuenta es que los métodos que no tienen parámetros de entrada ni parámetros de salida están garantizados para operar en algún tipo de estado almacenado internamente (de lo contrario, ¿qué están haciendo?). Hay idiomas enteros que se basan en la idea de evitar el estado almacenado.
Cada vez que haya almacenado el estado, tiene la posibilidad de efectos secundarios, así que asegúrese de usarlo siempre con atención. Esto implica que debe preferir funciones con entradas y / o salidas definidas.
Y, de hecho, las funciones que tienen entradas y salidas definidas son mucho más fáciles de probar: no tiene que ejecutar una función aquí e ir a mirar allí para ver qué sucedió, y no tiene que establecer una propiedad en alguna parte más antes de ejecutar la función bajo prueba.
También puede usar este tipo de función de forma segura como estática. Sin embargo, no lo haría, porque si luego quisiera usar una implementación ligeramente diferente de esa función en algún lugar, en lugar de proporcionar una instancia diferente con la nueva implementación, no tengo forma de reemplazar la funcionalidad.
3. Superposición vs. Distinción
No entiendo la pregunta. ¿Cuál sería la ventaja en 2 métodos superpuestos?
4. Privado vs. Público
No exponga nada que no necesite exponer. Sin embargo, tampoco soy un gran fanático de lo privado. No soy un desarrollador de C #, sino un desarrollador de ActionScript. Pasé mucho tiempo en el código de Adobe Flex Framework, que fue escrito alrededor del año 2007. Y tomaron algunas decisiones realmente malas sobre qué hacer privado, lo que hace que sea una pesadilla tratar de extender sus clases.
Entonces, a menos que piense que es un mejor arquitecto que los desarrolladores de Adobe alrededor del año 2007 (según su pregunta, diría que tiene unos años más antes de tener la oportunidad de hacer esa afirmación), es probable que solo desee usar la protección predeterminada .
Hay algunos problemas con sus ejemplos de código que significan que no están bien diseñados, por lo que no es posible elegir A o B.
Por un lado, probablemente debería separar la creación de su objeto de su uso . Por lo tanto, generalmente no tendría su
new XMLReader()
derecho al lado de donde se usa.Además, como dice @djna, debe encapsular los métodos utilizados en los usos de su lector XML, de modo que su API (ejemplo de instancia) podría simplificarse para:
No sé cómo funciona C #, pero dado que he trabajado con una serie de tecnologías web, sospecho que no siempre podrás devolver un documento XML de inmediato (excepto tal vez como una promesa o un tipo futuro objeto), pero no puedo darle consejos sobre cómo manejar una carga asincrónica en C #.
Tenga en cuenta que con este enfoque, puede crear varias implementaciones que pueden tomar un parámetro que les dice dónde / qué leer y devolver un objeto XML, y cambiarlos según las necesidades de su proyecto. Por ejemplo, puede estar leyendo directamente desde una base de datos, desde una tienda local o, como en su ejemplo original, desde una URL. No puede hacer eso si usa un método estático.
fuente
Centrarse en la perspectiva del cliente.
Los métodos estáticos tienden a limitar la evolución futura, nuestro cliente está trabajando en términos de una interfaz proporcionada posiblemente por muchas implementaciones diferentes.
El principal problema con su idioma abierto / de lectura es que el cliente necesita saber el orden en el que llamar a los métodos, cuando solo quiere hacer un trabajo simple. Obvio aquí, pero en una clase más grande está lejos de ser obvio.
El método principal para probar es read (). Los métodos internos pueden hacerse visibles para los programas de prueba al no hacerlos públicos o privados y al poner las pruebas en el mismo paquete; las pruebas aún pueden mantenerse separadas del código publicado.
fuente
En la práctica, encontrará que los métodos estáticos generalmente se limitan a las clases de utilidad y no deberían saturar sus objetos de dominio, administradores, controladores o DAO. Los métodos estáticos son más útiles cuando todas las referencias necesarias pueden pasarse razonablemente como parámetros y ofrecer cierta funcionalidad que puede reutilizarse en muchas clases. Si se encuentra utilizando un método estático como solución alternativa para tener una referencia a un objeto de instancia, pregúntese por qué no solo tiene esa referencia.
Si no necesita parámetros en el método, no los agregue. Lo mismo ocurre con el valor de retorno. Tener esto en cuenta simplificará su código y garantizará que no esté codificando para una multitud de escenarios que nunca terminan sucediendo.
Es una buena idea tratar de evitar la superposición de funciones. A veces puede ser difícil, pero cuando es necesario un cambio en la lógica, es mucho más fácil cambiar un método que se reutiliza que cambiar un montón de métodos con una funcionalidad similar.
Comúnmente los captadores, colocadores y constructores deben ser públicos. Todo lo demás querrás intentar mantenerlo en privado a menos que haya un caso en el que otra clase necesite ejecutarlo. Mantener los métodos predeterminados en privado ayudará a mantener la Encapsulación . Lo mismo ocurre con los campos, acostumbrarse a privado como predeterminado
fuente
No responderé a su pregunta, pero creo que los términos que ha utilizado crean un problema. P.ej
Creo que necesita un XML, así que crearé un XML de objeto que se puede crear a partir de un tipo de texto (no sé C # ... en Java se llama String). P.ej
Puedes probarlo y está claro. Luego, si necesita una fábrica, puede agregar un método de fábrica (y puede ser estático como su segundo ejemplo). P.ej
fuente
Puedes seguir algunas reglas simples. Ayuda si comprende las razones de las reglas.
método estático vs instancia
Para los métodos no tiene que tomar esta decisión conscientemente. Si parece que su método no está utilizando ningún miembro de campo (su analizador favorito debería decírselo), debe agregar la palabra clave estática.
método sin parámetros o valor de retorno vs método con parámetros y valor de retorno
La segunda opción es mejor debido al alcance. Siempre debe mantener su alcance ajustado. Buscar cosas que necesita es malo, debe tener aportes, trabajar en ellos localmente y devolver un resultado. Su primera opción es mala por la misma razón por la que las variables globales generalmente son malas: son significativas solo para una parte de su código, pero son visibles (y, por lo tanto, ruidosas) en cualquier otro lugar y pueden manipularse desde cualquier lugar. Esto dificulta obtener una imagen completa de su lógica.
superposición de funcionalidad de método distinto
No creo que esto sea un problema. Los métodos que llaman a otros métodos están bien si esto divide las tareas en fragmentos más pequeños y claramente funcionales.
método privado vs público
Haga que todo sea privado a menos que lo necesite para ser público. El usuario de su clase puede prescindir del ruido, solo quiere ver lo que le importa.
fuente