El nuevo bombo en la programación tiene que ver con los paradigmas de programación funcional. Los lenguajes funcionales se utilizan cada vez más en aplicaciones mayores y mejores. Scala, Haskel, etc. son prósperos y otros lenguajes más conservadores, como Java, comenzaron a adoptar algunos de los paradigmas de programación funcional (ver cierres en Java7 y evaluación perezosa para listas en Java8). Sin embargo, lo que solo pocas personas saben es que PHP es bastante versátil cuando se trata de programación funcional. Todos los principales conceptos de programación funcional se pueden expresar en PHP. Entonces, si eres nuevo en la programación funcional, prepárate para hacer volar tu mente, y si ya estás familiarizado con la programación funcional, prepárate para divertirte con este tutorial.
Sin paradigmas de programación, podríamos hacer lo que queramos de la manera que queramos. Si bien esto llevaría a una flexibilidad extrema, también conduciría a arquitecturas imposibles y un código muy exagerado. Por lo tanto, los paradigmas de programación se inventaron para ayudarnos a los programadores a pensar de manera específica sobre un programa específico y, de esta manera, limitar nuestra capacidad de expresar nuestra solución..
Cada paradigma de programación nos quita la libertad:
En programación funcional, no tienes datos representados por variables..
En programacion funcional todo es una funcion. Y me refiero a todo. Por ejemplo, un conjunto, como en matemáticas, se puede representar como varias funciones. Una matriz o lista es también una función o un grupo de funciones..
En la programación orientada a objetos todo es un objeto. Y un objeto es una colección de datos y métodos que realizan acciones en esos datos. Los objetos tienen un estado, un estado volátil, mutable..
En programación funcional, no tiene datos representados por variables. No hay contenedores de datos. Los datos no se asignan a una variable. Algunos valores pueden ser definidos y asignados. Sin embargo, en la mayoría de los casos son funciones asignadas a "variables". He puesto "variables" entre comillas porque en la programación funcional están inmutable. Aunque la mayoría de los lenguajes de programación funcionales no imponen la inmutabilidad, de la misma manera que la mayoría de los lenguajes orientados a objetos no aplican los objetos, si cambia el valor después de una asignación, ya no está haciendo la programación funcional pura..
Debido a que no tiene valores asignados a las variables, en la programación funcional tiene sin estado.
Por no tener estado ni asignaciones, en programación funcional. Las funciones no tienen efectos secundarios.. Y por las tres razones anteriores., las funciones son siempre predecibles. Básicamente, esto significa que si llama una función con los mismos parámetros una y otra vez y otra vez ... siempre tendrá el mismo resultado. Esta es una gran ventaja sobre la programación orientada a objetos y reduce en gran medida la complejidad de las aplicaciones multihilo y masivamente multihilo..
Pero, si queremos expresar todo en funciones, necesitamos poder asignarles parámetros o devolverlos desde otras funciones. Así, la programación funcional requiere el soporte para funciones de orden superior. Básicamente, esto significa que una función puede asignarse a una "variable", enviarse como un parámetro a otra función y devolverse como resultado de una función.
Finalmente, porque no tenemos valores en las variables, mientras que para los bucles son inusuales en la programación funcional y se reemplazan con recursión.
Basta de hablar y filosofía para una lección. Vamos a codificar!
Configure un proyecto PHP en su IDE favorito o editor de código. Crea en ello un "Pruebas"
carpeta. Crea dos archivos: FunSets.php
en la carpeta del proyecto, y FunSetsTest.php
en la carpeta Pruebas. Crearemos una aplicación, con pruebas, que representará el concepto de conjuntos..
En matemáticas, un conjunto es una colección de objetos distintos, considerados como un objeto por derecho propio. (wikipedia)
Eso significa básicamente que los conjuntos son un montón de cosas en un solo lugar. Estos conjuntos pueden ser y se caracterizan por operaciones matemáticas: uniones, intersecciones, diferencias, etc. Y por propiedades procesables como: contiene.
Así que vamos a codificar! Pero espera. ¿Cómo? Bueno, para respetar los conceptos de programación funcional tendremos que aplicar las siguientes restricciones a nuestro código:
No se aplican limitaciones a las pruebas. Debido a la naturaleza de PHPUnit, usaremos el código PHP orientado a objetos clásico allí. Además, para acomodar mejor nuestras pruebas, agruparemos todo nuestro código de producción en una sola clase.
Si usted es un programador experimentado, pero no está familiarizado con la programación funcional, Ahora es el momento de dejar de pensar como lo hace habitualmente. y prepárate para salir de tu zona de confort. Olvide todas sus formas anteriores de razonar acerca de un problema e imagine todas en funciones.
La función definitoria de un conjunto es su método "contiene"..
la función contiene ($ set, $ elem) return $ set ($ elem);
OK ... Esto no es tan obvio, así que veamos cómo lo usaríamos.
$ set = function ($ element) return true;; contiene ($ set, 100);
Bueno, esto lo explica un poco mejor. La función "contiene"
tiene dos parámetros:
conjunto de $
- representa un conjunto definido como una función.$ elem
- representa un elemento definido como un valor.En este contexto, todo eso "contiene"
Lo que hay que hacer es aplicar la función en conjunto de $
con el parametro $ elem
. Vamos a envolver todo en una prueba.
la clase FunSetsTest extiende PHPUnit_Framework_TestCase private $ funSets; función protegida setUp () $ this-> funSets = new FunSets (); function testContainsIsImplemented () // Caracterizamos un conjunto por su función que contiene. Es la función básica de un conjunto. $ set = function ($ element) return true;; $ this-> assertTrue ($ this-> funSets-> contiene ($ set, 100));
Y envolver nuestro código de producción en el interior. FunSets.php
en una clase:
La clase FunSets public function contiene ($ set, $ elem) return $ set ($ elem);
Puede ejecutar esta prueba y pasará. El conjunto que definimos para esta prueba es solo una función que siempre devuelve verdadero. Es un "verdadero conjunto".
Si el capítulo anterior fue un poco confuso o parecía inútil en lógica, este lo aclarará un poco. Queremos definir un conjunto con un solo elemento, un conjunto de singleton. Recuerde, esto tiene que ser una función, y querremos usarla como en la prueba a continuación..
function testSingletonSetContainsSingleElement () // Un conjunto de singleton se caracteriza por una función a la que se pasa que contiene y devolverá verdadero para el elemento simple // pasado como su parámetro. En otras palabras, un singleton es un conjunto con un solo elemento. $ singleton = $ this-> funSets-> singletonSet (1); $ this-> assertTrue ($ this-> funSets-> contiene ($ singleton, 1));
Necesitamos definir una función llamada "singeltonSet"
con un parámetro que representa un elemento del conjunto. En la prueba, ese es el número uno (1). Entonces, esperamos nuestra contiene
Método, cuando se llama con una función singleton, para devolver cierto
si el enviado en el parámetro es igual a uno. El código que hace que la prueba pase es el siguiente:
función pública singletonSet ($ elem) return function ($ otherElem) use ($ elem) return $ elem == $ otherElem; ;
¡Guauu! Eso es una locura Entonces, la función "singletonSet"
obtiene como parámetro un elemento como $ elem
. Luego devuelve otra función que tiene un parámetro. $ otherElem
y esta segunda funcion comparara $ elem
a $ otherElem
.
¿Entonces, cómo funciona esto? Primero, esta línea:
$ singleton = $ this-> funSets-> singletonSet (1);
se transforma en lo que "singletonSet (1)"
devoluciones:
$ singleton = function ($ otherElem) return 1 == $ otherElem; ;
Entonces "contiene ($ singleton, 1)"
se llama. Que, a su vez, llama a lo que está en $ singleton
. Entonces el código se convierte en:
$ singleton (1)
Que realmente ejecuta el código en él con $ otherElem
teniendo el valor uno.
devuelve 1 == 1
Lo que por supuesto es cierto y nuestra prueba pasa..
¿Ya estás sonriendo? ¿Sientes que tu cerebro comienza a hervir? Ciertamente lo hice cuando escribí este ejemplo por primera vez en Scala y lo hice de nuevo cuando escribí este ejemplo por primera vez en PHP. Creo que esto es extraordinario. Logramos definir un conjunto, con un elemento, con la capacidad de verificar que contiene el valor que le pasamos. Hicimos todo esto sin una sola asignación de valor. No tenemos ninguna variable que contenga el valor uno o que tenga un estado de uno. Sin estado, sin asignación, sin mutabilidad, sin bucles. Estamos en el camino correcto aquí.
Ahora que podemos crear un conjunto con un solo valor, debemos poder crear un conjunto con varios valores. La forma obvia de hacerlo es definir la operación de unión en nuestros conjuntos. La unión de dos conjuntos singleton representará otra unión con ambos valores. Quiero que se tome un minuto y piense en la solución antes de desplazarse al código, tal vez tome un pico en las siguientes pruebas.
function testUnionContainsAllElements () // Una unión se caracteriza por una función que obtiene 2 conjuntos como parámetros y contiene todos los conjuntos proporcionados // Solo podemos crear singletons en este punto, así que creamos 2 singletons y los unimos $ s1 = $ this -> funSets-> singletonSet (1); $ s2 = $ this-> funSets-> singletonSet (2); $ union = $ this-> funSets-> union ($ s1, $ s2); // Ahora, verifique que tanto 1 como 2 sean parte de la unión $ this-> assertTrue ($ this-> funSets-> contiene ($ union, 1)); $ this-> assertTrue ($ this-> funSets-> contiene ($ union, 2)); // ... y que no contiene 3 $ this-> assertFalse ($ this-> funSets-> contiene ($ union, 3));
Queremos una función llamada "Unión"
Eso consigue dos parámetros, ambos conjuntos. Recuerda, los sets son solo funciones para nosotros, así que nuestros "Unión"
La función obtendrá dos funciones como parámetros. Entonces, queremos poder consultar con "contiene"
Si la unión contiene un elemento o no. Entonces nuestro "Unión"
La función debe devolver otra función que "contiene"
puedo usar.
función pública union ($ s1, $ s2) return function ($ otherElem) use ($ s1, $ s2) return $ this-> contiene ($ s1, $ otherElem) || $ this-> contiene ($ s2, $ otherElem); ;
Esto está funcionando bastante bien. Y es perfectamente válido incluso cuando se llama a su sindicato con otro sindicato más un singleton. Llama contiene
Dentro de sí mismo para cada parámetro. Si es un sindicato, se repetirá. Es así de simple.
Podemos aplicar la misma lógica de línea con cambios menores para obtener las siguientes dos funciones más importantes que caracterizan un conjunto: intersección: contiene solo los elementos comunes entre dos conjuntos, y la diferencia contiene solo aquellos elementos del primer conjunto que no forman parte del segundo set.
función pública intersect ($ s1, $ s2) return function ($ otherElem) use ($ s1, $ s2) return $ this-> contiene ($ s1, $ otherElem) && $ this-> contiene ($ s2, $ otherElem); ; función pública diff ($ s1, $ s2) return function ($ otherElem) use ($ s1, $ s2) return $ this-> contiene ($ s1, $ otherElem) &&! $ this-> contiene ($ s2 , $ otherElem); ;
No te inundaré con el código de prueba para estos dos métodos. Las pruebas están escritas y puede verificarlas si busca en el código adjunto..
Bueno, esto es un poco más complicado, no podremos resolverlo con una sola línea de código. Un filtro es una función que utiliza dos parámetros: un conjunto y una función de filtrado. Aplica la función de filtrado a un conjunto y devuelve otro conjunto que contiene solo los elementos que satisfacen la función de filtrado. Para entenderlo mejor, aquí está la prueba para ello..
function testFilterContainsOnlyElementsThatMatchConditionFunction () $ u12 = $ this-> createUnionWithElements (1, 2); $ u123 = $ this-> funSets-> union ($ u12, $ this-> funSets-> singletonSet (3)); // Regla de filtrado, busque elementos mayores que 1 (lo que significa 2 y 3) $ condition = function ($ elem) return $ elem> 1;; // Conjunto filtrado $ filtersSet = $ this-> funSets-> filter ($ u123, $ condition); // Verificar que el conjunto filtrado no contiene 1 $ this-> assertFalse ($ this-> funSets-> contiene ($ filterSet, 1), "No debe contener 1"); // Comprueba que contiene 2 y 3 $ this-> assertTrue ($ this-> funSets-> contiene ($ filterSet, 2), "Debe contener 2"); $ this-> assertTrue ($ this-> funSets-> contiene ($ filterSet, 3), "Debe contener 3"); la función privada createUnionWithElements ($ elem1, $ elem2) $ s1 = $ this-> funSets-> singletonSet ($ elem1); $ s2 = $ this-> funSets-> singletonSet ($ elem2); devuelve $ this-> funSets-> union ($ s1, $ s2);
Creamos un conjunto con tres elementos: 1, 2, 3. Y lo colocamos en la variable $ u123
por lo que es fácilmente identificable en nuestras pruebas. Luego definimos una función que queremos aplicar a la prueba y la colocamos en $ condición
. Finalmente, llamamos filtro a nuestra $ u123
colocado con $ condición
y colocar el conjunto resultante en $ filterSet
. Luego hacemos afirmaciones con "contiene"
Para determinar si el conjunto se ve como queremos. Nuestra función de condición es simple, devolverá verdadero si el elemento es mayor que uno. Por lo tanto, nuestro conjunto final debe contener solo los valores dos y tres, y esto es lo que verificamos en nuestras afirmaciones..
filtro de función pública ($ set, $ condition) return function ($ otherElem) use ($ set, $ condition) si ($ condition ($ otherElem)) devuelve $ this-> contiene ($ set, $ otherElem); falso retorno; ;
Y aquí tienes. Implementamos el filtrado con solo tres líneas de código. Más precisamente, si la condición se aplica al elemento provisto, ejecutamos un contenido en el conjunto para ese elemento. Si no, acabamos de volver falso
. Eso es.
El siguiente paso es crear varias funciones de bucle. El primero, "para todos()", tomará un conjunto de $
y un $ condición
y volver cierto
Si $ condición
Se aplica a todos los elementos de la conjunto de $
. Esto lleva a la siguiente prueba:
function testForAllCorrectlyTellsIfAllElementsSatisfyCondition () $ u123 = $ this-> createUnionWith123 (); $ higherThanZero = function ($ elem) return $ elem> 0; ; $ higherThanOne = function ($ elem) return $ elem> 1; ; $ higherThanTwo = function ($ elem) return $ elem> 2; ; $ this-> assertTrue ($ this-> funSets-> forall ($ u123, $ higherThanZero)); $ this-> assertFalse ($ this-> funSets-> forall ($ u123, $ higherThanOne)); $ this-> assertFalse ($ this-> funSets-> forall ($ u123, $ higherThanTwo));
Extrajimos el $ u123
Creación de la prueba anterior en un método privado. Luego definimos tres condiciones diferentes: más alto que cero, más alto que uno y más alto que dos. Como nuestro conjunto contiene los números uno, dos y tres, solo la condición superior a cero debe devolver verdadero, el resto debe ser falso. De hecho, podemos hacer que la prueba pase con la ayuda de otro método recursivo que se utiliza para iterar sobre todos los elementos..
$ límite privado = 1000; función privada forallIterator ($ currentValue, $ set, $ condition) si ($ currentValue> $ this-> bound) devuelve true; elseif ($ this-> contiene ($ set, $ currentValue)) return $ condition ($ currentValue) && $ this-> forallIterator ($ currentValue + 1, $ set, $ condition); de lo contrario, devuelva $ this-> forallIterator ($ currentValue + 1, $ set, $ condition); public function forall ($ set, $ condition) return $ this-> forallIterator (- $ this-> bound, $ set, $ condition);
Comenzamos por definir algunos límites para nuestro conjunto. Los valores deben estar entre -1000 y +1000. Esta es una limitación razonable que imponemos para mantener este ejemplo lo suficientemente simple. La función "para todos"
Llamaré al método privado "forallIterator"
con los parámetros necesarios para decidir recursivamente si todos los elementos respetan la condición. En esta función, primero probamos si estamos fuera de los límites. Si es así, devuelva verdadero. Luego verifique si nuestro conjunto contiene el valor actual y devuelva el valor actual aplicado a la condición junto con un "AND" lógico con una llamada recursiva a nosotros mismos con el siguiente valor. De lo contrario, solo llámenos con el siguiente valor y devuelva el resultado.
Esto funciona bien, podemos implementarlo de la misma manera que "existe ()"
. Esto devuelve cierto
Si alguno de los elementos satisface la condición..
la función privada existeIterator ($ currentValue, $ set, $ condition) si ($ currentValue> $ this-> bound) devuelve false; elseif ($ this-> contiene ($ set, $ currentValue)) return $ condition ($ currentValue) || $ this-> existirIterador ($ currentValue + 1, $ set, $ condición); de lo contrario, devuelva $ this-> existenciaIterador ($ currentValue + 1, $ set, $ condición); existe una función pública ($ set, $ condition) return $ this-> existenciaIterador (- $ this-> bound, $ set, $ condition);
La única diferencia es que volvemos. falso
cuando estamos fuera de los límites y usamos "OR" en lugar de "AND" en el segundo si.
Ahora, "mapa()"
Será diferente, más sencillo y más corto..
mapa de funciones públicas ($ set, $ acción) return function ($ currentElem) use ($ set, $ action) return $ this-> existe ($ set, function ($ elem) use ($ currentElem, $ action) devuelve $ currentElem == $ action ($ elem);); ;
Mapear significa que aplicamos una acción a todos los elementos de un conjunto. Para el mapa, no necesitamos un iterador auxiliar, podemos reutilizarlo "existe ()"
y devolver aquellos elementos de "existe" que satisfacen el resultado de $ acción
aplicado a $ elemento
. Esto puede no ser obvio en el primer sitio, así que veamos qué está pasando.
1, 2
y la acción $ elemento * 2 (doble)
para asignar.existe
con el set 1, 2
y la función de condición $ currentElement
es igual a $ elem * 2
.existe ()
iteraremos sobre todos los elementos entre -1000 y +1000, nuestros límites. Cuando encuentra un elemento, el doble de lo que proviene. "contiene"
(El valor de $ currentElement
) volverá cierto
.cierto
para la llamada a contiene con el valor dos, cuando el valor de la corriente multiplicado por dos, resulta en dos. Entonces, para el primer elemento del conjunto, uno, devolverá verdadero en dos. Para el segundo elemento, dos, en valor cuatro. Bueno, la programación funcional es divertida pero está lejos de ser ideal en PHP. Por lo tanto, no te recomiendo escribir aplicaciones completas de esta manera. Sin embargo, ahora que ha aprendido lo que PHP puede hacer funcionalmente, puede aplicar partes de este conocimiento en sus proyectos diarios. Aquí hay un ejemplo de módulo de autenticación. los AuthPlugin
La clase proporciona un método que recibe un usuario y una contraseña y hace su magia para autenticar al usuario y establecer sus permisos..
clase AuthPlugin private $ permissions = array (); función de autenticación ($ nombre de usuario, $ contraseña) $ this-> verifyUser ($ nombre de usuario, $ contraseña); $ adminModules = new AdminModules (); $ this-> permisos [] = $ adminModules-> allowRead ($ username); $ this-> permisos [] = $ adminModules-> allowWrite ($ username); $ this-> permisos [] = $ adminModules-> allowExecute ($ username); private function verifyUser ($ nombre de usuario, $ contraseña) // ... DO USER / PASS CHECKING // ... LOAD DETALLES DEL USUARIO, ETC.
Ahora, esto puede sonar bien, pero tiene un gran problema. 80% de los "autenticar()"
método utiliza información de la "AdminModules"
. Esto crea una dependencia muy fuerte..
Sería mucho más razonable tomar las tres llamadas y crear un método único en AdminModules
.
Así, moviendo la generación en AdminModules
Logramos reducir tres dependencias a solo una. La interfaz pública de AdminModules
También se redujo de tres a un solo método. Sin embargo, todavía no estamos allí.. AuthPlugin
todavía depende directamente de AdminModules
.
Si desea que nuestro complemento de autenticación pueda ser utilizado por cualquier módulo, necesitamos definir una interfaz común para estos módulos. Inyectemos la dependencia e introduzcamos una interfaz..
clase AuthPlugin private $ permissions = array (); privado $ appModule; function __construct (ApplicationModule $ appModule) $ this-> appModule = $ appModule; Autenticación de la función ($ nombre de usuario, $ contraseña) $ this-> verifyUser ($ nombre de usuario, $ contraseña); $ this-> permissions = array_merge ($ this-> permissions, $ this-> appModule-> getPermissions ($ username)); private function verifyUser ($ nombre de usuario, $ contraseña) // ... DO USER / PASS CHECKING // ... LOAD DETALLES DEL USUARIO, ETC.
AuthPlugin
tiene un constructor Obtiene un parámetro de tipo. Modelo de Aplicación
, una interfaz, y llamadas "getPermissions ()"
en este objeto inyectado.
interface ApplicationModule public function getPermissions ($ username);
Modelo de Aplicación
define un solo método público, "getPermissions ()"
, con un nombre de usuario como parámetro.
La clase AdminModules implementa ApplicationModule // […]
Finalmente, AdminModules
necesita implementar el Modelo de Aplicación
interfaz.
Ahora, esto es mucho mejor. Nuestro AuthPlugin
depende solo de una interfaz. AdminModules
depende de la misma interfaz, por lo que la AuthPlugin
se hizo módulo agnóstico. Podemos crear cualquier número de módulos, todos implementando Modelo de Aplicación
, y AuthPlugin
Podremos trabajar con todos ellos..
Otra forma de revertir la dependencia, y hacer Módulo de administración
, o cualquier otro módulo, para utilizar el AuthPlugin
Es inyectar en estos módulos una dependencia para AuthPlugin
. AuthPlugin
proporcionará una manera de configurar la función de autenticación y cada aplicación enviará en su propio "obtener permiso()"
función.
clase AdminModules private $ authPlugin; function __construct (Authentitcation $ authPlugin) $ this-> authPlugin = $ authPlugin; private function allowRead ($ username) return "yes"; private function allowWrite ($ nombre de usuario) devolver "no"; función privada allowExecute ($ username) return $ username == "joe"? "si no"; private function authenticate () $ this-> authPlugin-> setPermissions (function ($ username) $ permissions = array (); $ permissions [] = $ this-> allowRead ($ username); $ permissions [] = $ this-> allowWrite ($ username); $ permissions [] = $ this-> allowExecute ($ username); devuelva $ permissions;); $ this-> authPlugin-> authenticate ();
Empezamos con Módulo de administración
. Ya no se implementa nada. Sin embargo, utiliza un objeto inyectado que tiene que implementar la autenticación. En Módulo de administración
habrá un "autenticar()"
método que llamará "establecer permisos()"
en AuthPlugin
Y pasar en la función que necesita ser utilizada..
autenticación de interfaz función setPermissions ($ permissionGrantingFunction); función authenticate ();
La interfaz de autenticación simplemente define los dos métodos..
la clase AuthPlugin implementa la autenticación private $ permissions = array (); privado $ appModule; privado $ permissionsFunction; function __construct (ApplicationModule $ appModule) $ this-> appModule = $ appModule; Autenticación de la función ($ nombre de usuario, $ contraseña) $ this-> verifyUser ($ nombre de usuario, $ contraseña); $ this-> permissions = $ this-> permissionsFunction ($ username); private function verifyUser ($ nombre de usuario, $ contraseña) // ... DO USER / PASS CHECKING // ... LOAD DETALLES DEL USUARIO, ETC. función pública setPermissions ($ permissionGrantingFunction) $ this-> permissionsFunction = $ permissionGrantingFunction;
Finalmente, AuthPlugin
implementa la autenticación y establece la función de entrada en un atributo de clase privada. Entonces, "autenticación()"
se convierte en un método tonto. Simplemente llama a la función y luego establece el valor de retorno. Es completamente desacoplado de lo que viene en.
Si miramos el esquema, hay dos cambios importantes:
Módulo de administración
, AuthPlugin
es el que implementa la interfaz.AuthPlugin
devolvere la llamada" Módulo de administración
, o cualquier otro módulo enviado en la función de permisos.No hay una respuesta correcta a esta pregunta. Yo diría que si el proceso de determinación de los permisos depende bastante del módulo de la aplicación, entonces el enfoque orientado a objetos es más apropiado. Sin embargo, si cree que cada módulo de aplicación debería poder proporcionar una función de autenticación, y su AuthPlugin
es solo un esqueleto que proporciona la funcionalidad de autenticación, pero sin saber nada acerca de los usuarios y los procedimientos, puede utilizar el enfoque funcional..
El enfoque funcional hace que su AuthPlugin
Muy abstracto y puedes confiar en ello. Sin embargo, si planea permitir su AuthPlugin
Para hacer más y saber más sobre los usuarios y su sistema, se volverá demasiado concreto y no querrá depender de ello. En ese caso, elija el modo orientado a objetos y deje que el concreto AuthPlugin
Depende de los módulos de aplicación más abstractos..