php - net - mvc pdf
Separación de intereses; MVC; ¿por qué? (8)
Actualmente estoy leyendo OO antes de embarcarme en mi próximo gran proyecto. Para darle algunos antecedentes rápidos, soy un desarrollador de PHP, trabajando en aplicaciones web.
Un área que me interesa particularmente es la interfaz de usuario; específicamente cómo construir esto y conectarlo a mi "modelo" de OO.
He estado leyendo un poco sobre esta área. Uno de mis favoritos es este: crear interfaces de usuario para sistemas orientados a objetos
"Todos los objetos deben proporcionar su propia IU"
Al pensar en mi problema, puedo ver que funciona bien. Construyo mi objeto "usuario" para representar a alguien que ha iniciado sesión en mi sitio web, por ejemplo. Uno de mis métodos es "display_yourself" o similar. Puedo usar esto a lo largo de mi código. Quizás para comenzar con esto solo será su nombre. Más tarde, si necesito ajustarme para mostrar su nombre + pequeño avatar, puedo actualizar este único método y listo, mi aplicación se actualiza. O si necesito hacer que su nombre sea un enlace a su perfil, hey-presto puedo actualizar fácilmente desde un solo lugar.
En términos de un sistema OO; Creo que este enfoque funciona bien. Al buscar en otros hilos de StackOverflow, encontré esto en "Separation of Concerns" (Separación de preocupaciones): Soc
"En ciencias de la computación, la separación de preocupaciones (SoC) es el proceso de dividir un programa de computadora en características distintas que se superponen en funcionalidad lo menos posible. Una preocupación es cualquier interés o enfoque en un programa. Por lo general, las preocupaciones son sinónimo de características o comportamientos. El progreso hacia SoC se logra tradicionalmente a través de la modularidad y la encapsulación, con la ayuda del ocultamiento de la información ".
En mi opinión, he logrado esto. Mi objeto de usuario oculta toda su información. No tengo ningún lugar en mi código donde digo $ user-> get_user_name () antes de mostrarlo.
Sin embargo, esto parece ir en contra de lo que otras personas parecen pensar como "mejores prácticas".
Para citar la respuesta "seleccionada" (verde) de la misma pregunta:
"La separación de las preocupaciones es mantener separado el código para cada una de estas preocupaciones. Cambiar la interfaz no debería requerir cambiar el código de lógica de negocios, y viceversa. El patrón de diseño Model-View-Controller (MVC) es un excelente ejemplo de separación de estas preocupaciones para una mejor mantenibilidad del software ".
¿Por qué esto mejora la capacidad de mantenimiento del software? Seguramente con MVC, mi vista tiene que saber mucho sobre el modelo. Lea el artículo de JavaWorld para una discusión detallada sobre este punto: Creación de interfaces de usuario para sistemas orientados a objetos
De todos modos ... llegar al punto real, finalmente!
1. ¿Alguien puede recomendar cualquier libro que discuta esto en detalle? No quiero un libro de MVC; No estoy vendido en MVC. Quiero un libro que analice OO / UI, los problemas potenciales, las posibles soluciones, etc. (tal vez incluyendo MVC) Heurística de diseño orientado a objetos de Arthur Riel
toca en él (¡y es también un libro excelente!), pero quiero algo que entre en detalles.
2. ¿Puede alguien presentar un argumento tan bien explicado como el artículo de Java Holmen de Allen Holub que explica por qué MVC es una buena idea?
Muchas gracias a todos los que pueden ayudarme a llegar a una conclusión sobre esto.
¿Alguien puede presentar un argumento [...] que explique por qué MVC es una buena idea?
Te mantiene sano al ayudarte a recordar lo que hace tu código porque están aislados el uno del otro.
El problema con la idea de que todos sus objetos saben cómo mostrarse es que cada objeto solo se puede mostrar de una manera. Qué sucede si desea proporcionar una vista detallada de un usuario y una vista de resumen. ¿Qué sucede si desea mostrar una vista que combine varios objetos (usuarios y sus direcciones asociadas, por ejemplo)? Si separa sus objetos comerciales (usuarios) de las cosas que saben cómo mostrarlos, entonces no tiene más código para escribir, simplemente lo separa en diferentes lugares.
Esto hace que el software sea más fácil de mantener, porque si un objeto de usuario se comporta de manera incorrecta, usted sabe que es el usuario, si no se muestra correctamente, usted sabe que es la vista. En la situación en la que necesita proporcionar una nueva interfaz a su aplicación (digamos que decide darle una nueva apariencia a los navegadores móviles), entonces no necesita cambiar su objeto de usuario, agrega un nuevo objeto que sabe cómo para renderizar el objeto del usuario para un navegador móvil.
Los principios SÓLIDOS proporcionan un buen razonamiento para esto, here hay una mirada relativamente concisa de estos. Me temo que no tengo un libro a mano que lo resuma bien, pero la experiencia me ha enseñado que es más fácil escribir un código nuevo que actualizar el código anterior, y por lo tanto diseños que favorecen las pequeñas clases modulares que se conectan a lograr lo que se necesita, mientras que es más difícil de diseñar al frente, son mucho más fáciles de mantener en el largo plazo. Es genial poder escribir un nuevo procesador para un objeto de usuario, sin tener que profundizar en las partes internas de ese objeto.
Este es el enfoque que tomo al crear sitios web en PHP usando un patrón MVC / separación de preocupaciones:
El marco que uso tiene tres piezas básicas:
- Modelos - Clases de PHP. Les agrego métodos para buscar y guardar datos. Cada modelo representa un tipo distinto de entidad en el sistema: usuarios, páginas, publicaciones de blog
- Vistas - Plantillas Smarty. Aquí es donde vive el html.
- Controladores - Clases de PHP. Estos son los cerebros de la aplicación. Normalmente las URL en el sitio invocan los métodos de la clase. example.com/user/show/1 invocaría el método $ user_controller-> show (1). El controlador extrae los datos del modelo y los muestra en la vista.
Cada una de estas piezas tiene un trabajo específico o "preocupación". El trabajo del modelo es proporcionar una interfaz limpia para los datos. Normalmente, los datos del sitio se almacenan en una base de datos SQL. Agrego métodos al modelo para extraer datos y guardar datos en.
El trabajo de la vista es mostrar datos. Todas las marcas de HTML van en la vista. La lógica para manejar la distribución de cebras para una tabla de datos va en la vista. El código para manejar el formato en el que se debe mostrar una fecha entra en la vista. Me gusta usar plantillas Smarty para vistas porque ofrece algunas características interesantes para manejar cosas como esa.
El trabajo del controlador es actuar como intermediario entre el usuario, el modelo y la vista.
Veamos un ejemplo de cómo se combinan y dónde radican los beneficios:
Imagine un sitio de blog simple. La pieza principal de datos es una publicación. Además, imagine que el sitio realiza un seguimiento de la cantidad de veces que se ve una publicación. Crearemos una tabla SQL para eso:
posts
id date_created title body hits
Ahora suponga que le gustaría mostrar las 5 publicaciones más populares. Esto es lo que puede ver en una aplicación que no sea MVC:
$sql = "SELECT * FROM posts ORDER BY hits DESC LIMIT 5";
$result = mysql_query($sql);
while ($row = mysql_fetch_assoc($result)) {
echo "<a href="post.php?id=$row[''id'']">$row[''title'']</a><br />";
}
Este fragmento es bastante directo y funciona bien si:
- Es el único lugar donde quieres mostrar las publicaciones más populares
- Nunca quieres cambiar la forma en que se ve
- Nunca decides cambiar lo que es una "publicación popular"
Imagine que desea mostrar las 10 publicaciones más populares en la página de inicio y las 5 más populares en una barra lateral en las subpáginas. Ahora necesita duplicar el código anterior o colocarlo en un archivo de inclusión con lógica para verificar dónde se muestra.
¿Qué sucede si desea actualizar el marcado de la página de inicio para agregar una clase de "nueva publicación" a las publicaciones que se crearon hoy?
Supongamos que decides que una publicación es popular porque tiene muchos comentarios, no hits. La base de datos cambiará para reflejar esto. Ahora, cada lugar en su aplicación que muestra publicaciones populares debe actualizarse para reflejar la nueva lógica.
Estás empezando a ver una bola de nieve de forma compleja. Es fácil ver cómo las cosas pueden volverse cada vez más difíciles de mantener en el transcurso de un proyecto. Además, considere la complejidad cuando varios desarrolladores trabajan en un proyecto. ¿Debería el diseñador consultar con el desarrollador de la base de datos al agregar una clase al resultado?
Adoptar un enfoque MVC e imponer una separación de preocupaciones dentro de su aplicación puede mitigar estos problemas. Idealmente, queremos separarlo en tres áreas:
- lógica de datos
- lógica de aplicación
- y mostrar lógica
Veamos cómo hacer esto:
Comenzaremos con el modelo . Tendremos una clase $post_model
y le daremos un método llamado get_popular()
. Este método devolverá una matriz de publicaciones. Además, le daremos un parámetro para especificar la cantidad de publicaciones que debe devolver:
post_model.php
class post_model {
public function get_popular($number) {
$sql = "SELECT * FROM posts ORDER BY hits DESC LIMIT $number";
$result = mysql_query($sql);
while($row = mysql_fetch_assoc($result)) {
$array[] = $row;
}
return $array;
}
}
Ahora para la página de inicio tenemos un controlador , lo llamaremos "hogar". Imaginemos que tenemos un esquema de enrutamiento url que invoca a nuestro controlador cuando se solicita la página de inicio. Su trabajo es obtener las publicaciones populares y darles la vista correcta:
home_controller.php
class home_controller {
$post_model = new post_model();
$popular_posts = $post_model->get_popular(10);
// This is the smarty syntax for assigning data and displaying
// a template. The important concept is that we are handing over the
// array of popular posts to a template file which will use them
// to generate an html page
$smarty->assign(''posts'', $popular_posts);
$smarty->view(''homepage.tpl'');
}
Ahora veamos cómo se vería la vista :
homepage.tpl
{include file="header.tpl"}
// This loops through the posts we assigned in the controller
{foreach from=''posts'' item=''post''}
<a href="post.php?id={$post.id}">{$post.title}</a>
{/foreach}
{include file="footer.tpl"}
Ahora tenemos las piezas básicas de nuestra aplicación y podemos ver la separación de las preocupaciones.
El modelo está interesado en obtener los datos. Conoce la base de datos, conoce las consultas SQL y las sentencias LIMIT. Sabe que debería devolver una buena matriz.
El controlador conoce la solicitud del usuario, que está mirando la página de inicio. Sabe que la página de inicio debe mostrar 10 publicaciones populares. Obtiene los datos del modelo y los da a la vista.
La vista sabe que una serie de publicaciones debe mostrarse como una serie de etiquetas achor con etiquetas de salto después de ellas. Sabe que una publicación tiene un título y una identificación. Sabe que el título de una publicación debe usarse para el texto de anclaje y que la identificación de la publicación debe usarse en la href. La vista también sabe que debe haber un encabezado y un pie de página en la página.
También es importante mencionar lo que cada pieza no sabe.
El modelo no sabe que las publicaciones populares se muestran en la página principal.
El controlador y la vista no saben que las publicaciones se almacenan en una base de datos SQL.
El controlador y el modelo no saben que cada enlace a una publicación en la página de inicio debe tener una etiqueta de interrupción después.
Por lo tanto, en este estado hemos establecido una clara separación de preocupaciones entre la lógica de datos (el modelo), la lógica de la aplicación (el controlador) y la lógica de la pantalla (la vista). ¿Y ahora que? Tomamos un pequeño fragmento de PHP simple y lo dividimos en tres archivos confusos. ¿Qué nos da esto?
Veamos cómo tener una separación de preocupaciones puede ayudarnos con los problemas mencionados anteriormente. Para reiterar, queremos:
- Mostrar publicaciones populares en una barra lateral en las subpáginas
- Resalta publicaciones nuevas con una clase de CSS adicional
- Cambiar la definición subyacente de una "publicación popular"
Para mostrar las publicaciones populares en una barra lateral, agregaremos dos archivos a nuestra subpágina:
Un controlador de subpágina ...
subpage_controller.php
class subpage_controller {
$post_model = new post_model();
$popular_posts = $post_model->get_popular(5);
$smarty->assign(''posts'', $popular_posts);
$smarty->view(''subpage.tpl'');
}
... y una plantilla de subpágina:
subpage.tpl
{include file="header.tpl"}
<div id="sidebar">
{foreach from=''posts'' item=''post''}
<a href="post.php?id={$post.id}">{$post.title}</a>
{/foreach}
</div>
{include file="footer.tpl"}
El nuevo controlador de subpáginas sabe que la subpágina solo debe mostrar 5 publicaciones populares. La vista de subpágina sabe que las subpáginas deben poner la lista de publicaciones dentro de una div de barra lateral.
Ahora, en la página de inicio queremos destacar nuevas publicaciones. Podemos lograr esto modificando la página de inicio.tpl.
{include file="header.tpl"}
{foreach from=''posts'' item=''post''}
{if $post.date_created == $smarty.now}
<a class="new-post" href="post.php?id={$post.id}">{$post.title}</a>
{else}
<a href="post.php?id={$post.id}">{$post.title}</a>
{/if}
{/foreach}
{include file="footer.tpl"}
Aquí la vista maneja toda la nueva lógica para mostrar publicaciones populares. El controlador y el modelo no necesitaban saber nada sobre ese cambio. Es puramente lógica de visualización. La lista de subpáginas continúa apareciendo como lo hacía antes.
Finalmente, nos gustaría cambiar lo que es una publicación popular. En lugar de basarse en el número de visitas que obtuvo una página, nos gustaría que se basara en la cantidad de comentarios que obtuvo una publicación. Podemos aplicar ese cambio al modelo:
post_model.php
class post_model {
public function get_popular($number) {
$sql = "SELECT * , COUNT(comments.id) as comment_count
FROM posts
INNER JOIN comments ON comments.post_id = posts.id
ORDER BY comment_count DESC
LIMIT $number";
$result = mysql_query($sql);
while($row = mysql_fetch_assoc($result)) {
$array[] = $row;
}
return $array;
}
}
Hemos aumentado la complejidad de la lógica de "publicación popular". Sin embargo, una vez que hemos hecho este cambio en el modelo , en un solo lugar, la nueva lógica se aplica en todas partes. La página principal y la subpágina, sin otras modificaciones, ahora mostrarán publicaciones populares basadas en comentarios. Nuestro diseñador no necesitó involucrarse en esto. El marcado no se ve afectado.
Es de esperar que esto brinde un ejemplo convincente de cómo separar las preocupaciones de la lógica de datos, la lógica de la aplicación y la lógica de visualización puede facilitar el desarrollo de su aplicación. Los cambios en un área tienden a tener un menor impacto en otras áreas.
Seguir esta convención no es una bala mágica que automáticamente hará que tu código sea perfecto. Y sin duda se encontrará con problemas en los que es mucho menos claro dónde debería estar la separación. Al final, se trata de administrar la complejidad dentro de la aplicación.
Deberías pensar mucho sobre cómo construyes tus modelos. ¿Qué tipo de interfaces proporcionarán (ver la respuesta de Gregory con respecto a los contratos)? ¿Con qué formato de datos el controlador y la vista esperan trabajar? Pensar en estas cosas antes de tiempo facilitará las cosas más adelante.
Además, al iniciar un proyecto puede haber cierta sobrecarga para que todas estas piezas funcionen juntas muy bien. Hay muchos marcos que proporcionan los bloques de construcción para modelos, controladores, motores de plantillas, enrutamiento url y más. Vea muchas otras publicaciones sobre SO para sugerencias sobre frameworks PHP MVC. Estos frameworks lo pondrán en funcionamiento pero usted, como desarrollador, está a cargo de administrar la complejidad y aplicar una separación de preocupaciones.
También notaré que los fragmentos de código anteriores son solo ejemplos simplificados. Ellos (lo más probable) tienen errores. Sin embargo, son muy similares en estructura al código que uso en mis propios proyectos.
Esto es un error en la forma en que a menudo se enseña OOP, usando ejemplos como rectangle.draw () y dinosaur.show () que no tienen ningún sentido.
Casi estás respondiendo tu propia pregunta cuando hablas de tener una clase de usuario que se muestra a sí misma.
"Más tarde, si tengo que ajustarme para mostrar su nombre + pequeño avatar, puedo actualizar este único método y hey-presto, mi aplicación se actualiza".
Piensa en esa pequeña pieza por momento. Ahora eche un vistazo a y observe todos los lugares donde aparece su nombre de usuario. ¿Se ve igual en cada caso? No, en la parte superior, acaba de recibir un sobre junto a su nombre de usuario seguido de su reputación y distintivos. En un hilo de preguntas, tienes tu avatar seguido de tu nombre de usuario con tu reputación y las insignias debajo de él. ¿Crees que hay un objeto de usuario con métodos como getUserNameWithAvatarInFrontOfItAndReputationAndBadgesUnderneath ()? Nah.
Un objeto se refiere a los datos que representa y a los métodos que actúan sobre esos datos. Su objeto de usuario probablemente tendrá los miembros firstName y lastName, y los getters necesarios para recuperar esas piezas. También podría tener un método de conveniencia como toString () (en términos de Java) que devolvería el nombre del usuario en un formato común, como el primer nombre seguido de un espacio y luego el apellido. Aparte de eso, el objeto de usuario no debería hacer mucho más. Depende del cliente decidir qué quiere hacer con el objeto.
Tome el ejemplo que nos ha dado con el objeto de usuario y luego piense cómo haría lo siguiente si creara una "IU" en él:
- Cree una exportación de CSV que muestre todos los usuarios, ordenados por apellido. Por ejemplo, Apellido, Nombre.
- Proporcione tanto una GUI pesada como una interfaz basada en web para trabajar con el objeto del usuario.
- Muestre un avatar junto al nombre de usuario en un solo lugar, pero solo muestre el nombre de usuario en otro.
- Proporcione una lista RSS de usuarios.
- Muestre el nombre de usuario en negrita en un lugar, en cursiva en otro y como hipervínculo en otro lugar.
- Muestre la inicial del segundo nombre del usuario donde corresponda.
Si piensa en estos requisitos, todos se reducen a proporcionar un objeto de usuario que solo se preocupa por los datos que le preocupan. No debe tratar de ser todo para todos, solo debe proporcionar un medio para obtener los datos del usuario. Depende de cada una de las muchas vistas que creará para decidir cómo desea mostrar los datos del usuario.
Su idea de actualizar el código en un lugar para actualizar sus puntos de vista en muchos lugares es buena. Esto todavía es posible sin tener que hacer demasiadas cosas a un nivel demasiado bajo. Sin duda podría crear clases similares a widgets que encapsularían sus diversas vistas comunes de "cosas", y las usarían a lo largo de su código de vista.
Mi 2c .. otra cosa que podrías hacer además de lo que se dijo es usar Decoradores de tus objetos de Usuario. De esta forma, podría decorar al usuario de manera diferente según el contexto. Así que terminarías con WebUser.class, CVSUser.class, RSSUser.class, etc.
Realmente no lo hago de esta manera, y podría ser complicado, pero ayuda a evitar que el código del cliente tenga que sacar mucha información de su Usuario. Puede ser algo interesante para mirar ;-)
No conozco ningún buen libro sobre el tema MVC, pero sí de mi propia experiencia. En el desarrollo web, por ejemplo, muchas veces trabajas con diseñadores y, a veces, dbas. Separar la lógica de la presentación le permite trabajar mejor con personas con diferentes conjuntos de habilidades porque el diseñador no necesita mucho sobre codificación y viceversa. Además, para el concepto de DRY, puede hacer que su código sea menos repetitivo y más fácil de mantener. Su código será más reutilizable y hará que su trabajo sea mucho más fácil. También te hará un mejor desarrollador porque estarás más organizado y pensarás en la programación de una manera diferente. Entonces, incluso si tiene que trabajar en algo que no es MVC, puede tener un enfoque diferente para la arquitectura del proyecto porque comprende los conceptos de MVC.
Supongo que la compensación con muchos frameworks MVC para sitios grandes es que puede no ser lo suficientemente rápido para manejar la carga.
No estoy seguro de poder llevarte al agua que quieres beber, pero creo que puedo responderte algunas de tus preocupaciones.
En primer lugar, en MVC, el modelo y la vista tienen cierta interacción, pero la vista está realmente acoplada al contrato y no a la implementación. Puede cambiar a otros modelos que cumplan con el mismo contrato y aún así puedan usar la vista. Y, si lo piensas, tiene sentido. Un usuario tiene un nombre y apellido. Probablemente también tenga un nombre de inicio de sesión y una contraseña, aunque puede o no vincular esto con el "contrato" de lo que es un usuario. El punto es que, una vez que determina qué es un usuario, es poco probable que cambie mucho. Puede agregarle algo, pero es poco probable que se lo quite a menudo.
En la vista, tiene indicadores para el modelo que se adhiere a ese contrato, pero puedo usar un objeto simple:
public class User
{
public string FirstName;
public string LastName;
}
Sí, me doy cuenta de que los campos públicos son malos. :-) También puedo usar una DataTable como modelo, siempre que exponga FirstName y LastName. Ese puede no ser el mejor ejemplo, pero el punto es que el modelo no está vinculado a la vista. La vista está vinculada a un contrato y el modelo particular se adhiere a ese contrato.
No he oído hablar de que cada objeto deba tener su propia interfaz de usuario. Hay esencialmente dos tipos de objetos: estado y comportamiento. He visto ejemplos que tienen tanto el estado como el comportamiento, pero generalmente están en sistemas que no son muy comprobables, que no me gustan. En última instancia, todos los objetos de estado deberían estar expuestos a alguna forma de IU para evitar obligar a las personas de TI a manejar todas las actualizaciones directamente en un almacén de datos, pero ¿tienen su propia IU? Tendría que ver eso escrito en una explicación para tratar de entender qué está haciendo el usuario.
En cuanto a SoC, la razón para empaquetar cosas distintamente es la capacidad de cambiar capas / niveles sin reescribir todo el sistema. En general, la aplicación está realmente ubicada en el nivel comercial, por lo que esa parte no se puede cambiar fácilmente. La información y la IU deberían ser bastante fáciles de cambiar en un sistema bien diseñado.
En cuanto a los libros sobre la comprensión de OOP, me gustan los libros sobre patrones, ya que son formas más prácticas de entender los conceptos. Puede encontrar el material de imprimación en la web. Si quieres un libro de patrones agnósticos del idioma y piensas un poco geek, el libro de Gang of Four es un buen lugar para comenzar. Para tipos más creativos, diría Heads Up Design Patterns.
- Considere la cantidad de código que entraría en esa clase única, si desea exponer la misma información no solo como Html en la interfaz de usuario, sino como parte de un RSS, un JSON, un servicio de descanso con XML, [inserte algo más] .
- Es una abstracción con goteras, lo que significa que intenta darle la sensación de que será la única pieza que alguna vez conocerá esos datos, pero eso no puede ser del todo cierto. Digamos que desea proporcionar un servicio que se integrará con varios terceros externos. Te será muy difícil forzarlos a usar tu lenguaje específico para integrarte con tu servicio (ya que es la clase la única pieza que puede usar los datos que está usando), o si, por otro lado, expones algunos de sus datos no está ocultando los datos de esos sistemas de terceros.
Actualización 1: Eché un vistazo general a todo el artículo, y como es un artículo antiguo (99), realmente no se trata de MVC tal como lo conocemos hoy en día, sino que está orientado a objetos, y no tiene argumentos que estén en contra del SRP.
Podría perfectamente estar en línea con lo que dijo, y manejar el escenario anterior que mencioné con clases específicas responsables de traducir el contrato público del objeto a los diferentes formatos: la principal preocupación era que no teníamos un lugar claro para manejar los cambios. y también que no queríamos que la información se repitiera por todas partes. Por lo tanto, en el caso html, podría tener perfectamente un control que represente la información, o una clase que lo transforme en html o [inserte el mecanismo de reutilización aquí].
Por cierto, tuve un flash con el bit RMI. De todos modos, en ese ejemplo puedes ver que está vinculado a un mecanismo de comunicación. Dicho esto, cada llamada al método se maneja de forma remota. Creo que también estaba realmente preocupado por los desarrolladores que tenían código y que en lugar de obtener un solo objeto y operar con la información devuelta, tenían muchas llamadas pequeñas para obtener toneladas de información.
PD. Te sugiero que leas información sobre DDD y Solid, que como dije para el SRP, no diría que es del tipo de cosas que compilaba el autor.