Arquitectura evolutiva y diseño emergente: Método compuesto y SLAP

¿Cómo encuentra usted un diseño oculto en bases de códigos envejecidas? Este artículo trata sobre dos importantes patrones para estructura de código: método compuesto y nivel de abstracción individual. Aplicar estos principios a su código le permite encontrar activos reutilizables que permanecían ocultos anteriormente, con el beneficio adicional de dejar a su código abstracto existente en infraestructuras recogidas.

Neal Ford, Application Architect, ThoughtWorks

Neal Ford is an application architect at ThoughtWorks, a revolutionary IT professional services company that leverages gifted people from around the world to create more value from software. He is also the designer and developer of applications, instructional materials, magazine articles, courseware, video/DVD presentations, and author of the books Developing with Delphi: Object-Oriented Techniques,JBuilder 3 Unleashed, and Art of Java Web Development. His primary consulting focus is the building of large-scale enterprise applications. He is also an internationally acclaimed speaker, having spoken at numerous developers conferences worldwide. He welcomes feedback and can be reached at: nford@thoughtworks.com.



21-04-2009

En los dos episodios previos de esta serie, hablé sobre cómo el uso del desarrollo orientado con pruebas (TDD) le ayudará incrementalmente a descubrir el diseño. Esto funciona muy bien si usted inicia un proyecto de campo verde. ¿Pero qué pasa con los casos más comunes en los que usted cuenta con grandes cantidades de código que no es el mejor del mundo? ¿Cómo encuentra los activos y diseños reutilizables ocultos en una base de código envejecida?

Acerca de esta serie

Esta serie apunta a proporcionar una perspectiva fresca sobre los conceptos que se tratan frecuentemente pero que son elusivos, de arquitectura y diseño de software. Por medio de ejemplos concretos, Neal Ford le proporciona unas bases sólidas en las prácticas ágiles de arquitectura evolutiva y diseño emergente. Al diferir importantes decisiones sobre arquitectura y diseño hasta el último momento responsable, usted puede prevenir con que complejidad innecesaria menoscabe sus proyectos de software.

Este artículo habla sobre dos décadas de patrones viejos que le ayudan a refabricar su código para encontrar activos reutilizables: método compuesto y el principio de nivel de abstracción individual (SLAP). Los elementos de buen diseño ya aparecen en su código; usted sólo necesita herramientas para ayudarle a exponer los activos ocultos que usted ya ha creado.

Método compuesto

Uno de los desafortunados efectos colaterales del paso del cambio tecnológico es que como desarrolladores, frecuentemente ignoramos las tradiciones del software. Tendemos a pensar que cualquier cosa con más de algunos años de antigüedad debe considerarse anticuada y sin esperanza. Esto, por supuesto, no es cierto: muchos libros forman una tradición importante para los desarrolladores. Un o de estos clásicos (actualmente casi ignorado) es Patrones de Mejores Prácticas Smalltalk por Kent Beck (vea Recursos). Como desarrollador Java, usted puede preguntarse, "¿Cómo puede un libro de hace 13 años tener algo que ver conmigo?" Resulta que los Smalltalkers fueron los primeros desarrolladores en programar un lenguaje orientado a los objetos, y desarrollaron muchas buenas ideas. Una de ellas es el método compuesto.

El patrón del método compuesto define tres declaraciones clave:

  • Divida sus programas en métodos que lleven a cabo una tarea identificable.
  • Mantenga todas las operaciones en un método en el mismo nivel de abstracción.
  • Esto naturalmente llevará a programas con muchos métodos pequeños, cada uno con algunas líneas de longitud.

Traté el método compuesto en "Diseño orientado con pruebas, Parte 1," dentro del contexto de escribir unidades de prueba antes de que escriba el código verdadero. La adherencia rigurosa a TDD crea métodos de forma natural, que se adhieren al método compuesto. ¿Pero qué pasa con el código pre-existente? Ahora es el momento de investigar usando el método compuesto para revelar el diseño oculto.

Patrones Idiomáticos

Usted probablemente esté familiarizado con el movimiento formal de Patrones de Diseño, popularizado por el libro seminal Pandilla de Cuatro Patrones de Diseño (vea Recursos). Este describe los patrones genéricos que aplican a todos los proyectos. No obstante, cada solución contiene patrones idiomáticos, que no son lo suficientemente formales como para engalanarse en un libro, pero que sin embargo son influyentes. Los patrones idiomáticos representan modismos comunes de diseño en su código. El verdadero truco en el diseño emergente es descubrir esos patrones. Ellos varían desde patrones netamente técnicos (por ejemplo, la forma en que las transacciones se manejan en este proyecto) hasta patrones del dominio de los problemas (tales como "verificar siempre el crédito del cliente antes de proceder con el envío").

Re-fabricación hacia el método compuesto

Considere el método simple en el Listado 1, diseñado para usar JDBC de nivel para conectarse a una base de datos, reúna objetos Parte , y póngalos en una Lista:

Listado 1. Método simple de recolección Partes
public void populate() throws Exception  {
    Connection c = null;
    try {
        Class.forName(DRIVER_CLASS);
        c = DriverManager.getConnection(DB_URL, USER, PASSWORD);
        Statement stmt = c.createStatement();
        ResultSet rs = stmt.executeQuery(SQL_SELECT_PARTS);
        while (rs.next()) {
            Part p = new Part();
            p.setName(rs.getString("name"));
            p.setBrand(rs.getString("brand"));
            p.setRetailPrice(rs.getDouble("retail_price"));
            partList.add(p);
        }    
    } finally {
        c.close();
    }
}

Olio se define como una colección miscelánea de cosas, y se usa coloquialmente como otra palabra para "sobras," (aparece bastante en crucigramas.) Los métodos Olio son métodos gigantes con una gran recopilación de cosas misceláneas en ellos, que saltan por todo el domino del problema. Los métodos en su base de código que alcancen la marca de 300 líneas son, por definición, métodos olio. ¿Cómo puede un método como tal tener la posibilidad de ser cohesivo si es así de extenso? Es uno de los principales inhibidores para re-fabricación, pruebas y diseño emergente.

El Listado 1 no incluye nada particularmente complejo. Obviamente tampoco contiene código reutilizable. También es bastante corto, pero debe ser re-fabricado. El método compuesto dice que cada método debe hacer una y sólo una cosa, y este método viola esa regla. Tengo un conocimiento heurístico en proyectos Java de que cualquier método de más de 10 líneas de extensión pide una re-fabricación porque probablemente hace más de una cosa. Así que re-fabricaré este método, con el método compuesto en mente, para ver si puedo aislar las partes atómicas. La versión re-fabricada aparece en el Listado 2:

Listado 2. Re-fabricado populate() method
public void populate() throws Exception {
    Connection c = null;
    try {
        c = getDatabaseConnection();
        ResultSet rs = createResultSet(c);
        while (rs.next())
            addPartToListFromResultSet(rs);
    } finally {
        c.close();
    }
}

private ResultSet createResultSet(Connection c)
        throws SQLException {
    return c.createStatement().
            executeQuery(SQL_SELECT_PARTS);
}

private Connection getDatabaseConnection()
        throws ClassNotFoundException, SQLException {
    Connection c;
    Class.forName(DRIVER_CLASS);
    c = DriverManager.getConnection(DB_URL,
            "webuser", "webpass");
    return c;
}

private void addPartToListFromResultSet(ResultSet rs)
        throws SQLException {
    Part p = new Part();
    p.setName(rs.getString("name"));
    p.setBrand(rs.getString("brand"));
    p.setRetailPrice(rs.getDouble("retail_price"));
    partList.add(p);
}

El método populate() es ahora mucho más corto y se lee como un bosquejo de las tareas que necesita ejecutar, con la implementación de tareas que reside en los métodos privados. Una vez que he retirado todas las partes atómicas, puedo ver con cuáles activos cuento realmente. Note que el método get Database Connection() no tiene nada que ver con parts — es funcionalidad genérica para conectarse a una base de datos. Esto sugiere que este método no debe estar en esta clase, así que voy a re-fabricarlo hacia una clase BoundaryBase , la cual actúa como una pariente de la clase PartDb .

¿Hay algún otro método en el Listado 2 lo suficientemente genérico como para ser generalizado en una clase de pariente? El método create ResultSet() suena bastante genérico, pero tiene un enlace hacia parts, denominada la constante SQL_SELECT_PARTS. Si puedo definir una forma para forzar a la clase hija(PartDb) para que le diga a la clase pariente el valor de su cadena SQL, también podría retirar este método. Exactamente este es el propósito del método abstracto. Entonces arrastro create ResultSet() dentro de la claseBoundaryBase junto con un método abstracto de compañía llamado método get Sql For Entity() como se muestra en el Listado 3:

Listing 3. La Clase BoundaryBase hasta el momento
abstract public class BoundaryBase {
    private static final String DRIVER_CLASS =
            "com.mysql.jdbc.Driver";
    private static final String DB_URL =
            "jdbc:mysql://localhost/orderentry";

    protected Connection getDatabaseConnection() throws ClassNotFoundException,
            SQLException {
        Connection c;
        Class.forName(DRIVER_CLASS);
        c = DriverManager.getConnection(DB_URL, "webuser", "webpass");
        return c;
    }                               

    abstract protected String getSqlForEntity();

    protected ResultSet createResultSet(Connection c) throws SQLException {
        Statement stmt = c.createStatement();
        return stmt.executeQuery(getSqlForEntity());
    }

Eso fue divertido. ¿Puedo arrastrar más métodos desde la clase hija hasta la clase pariente genérica? Si usted observa el método Listado 2populate() mismo, este se amarra a la clase PartDb que son los métodos get Database Connection(), create Result Set() y add Part To List From ResultSet(). Los primeros dos métodos ya se movieron a la clase pariente. Si resumo el método add Part To List From Result Set() (junto con un nombre genérico más apropiado), puedo arrastrar el métodopopulate() completo hacia el pariente, lo cual he efectuado en el Listado 4:

Listado 4. La clase BoundaryBase
abstract public class BoundaryBase {
    private static final String DRIVER_CLASS =
            "com.mysql.jdbc.Driver";
    private static final String DB_URL =
            "jdbc:mysql://localhost/orderentry";

    protected Connection getDatabaseConnection() throws ClassNotFoundException,
            SQLException {
        Connection c;
        Class.forName(DRIVER_CLASS);
        c = DriverManager.getConnection(DB_URL, "webuser", "webpass");
        return c;
    }                               

    abstract protected String getSqlForEntity();

    protected ResultSet createResultSet(Connection c) throws SQLException {
        Statement stmt = c.createStatement();
        return stmt.executeQuery(getSqlForEntity());
    }

    abstract protected void addEntityToListFromResultSet(ResultSet rs)
            throws SQLException;

    public void populate() throws Exception {
        Connection c = null;
        try {
            c = getDatabaseConnection();
            ResultSet rs = createResultSet(c);
            while (rs.next())
                addEntityToListFromResultSet(rs);
        } finally {
            c.close();
        }
    }

}

Una vez que he arrastrado todos estos métodos hasta la clase pariente, la clase PartDb ha sido simplificada significativamente, como se muestra en el Listado 5:

Listado 5. La clase PartDb simplificada y re-fabricada
public class PartDb extends BoundaryBase {
    private static final int DEFAULT_INITIAL_LIST_SIZE = 40;
    private static final String SQL_SELECT_PARTS =
            "select name, brand, retail_price from parts";
    private static final Part[] TEMPLATE = new Part[0];
    private ArrayList partList;

    public PartDb() {
        partList = new ArrayList(DEFAULT_INITIAL_LIST_SIZE);
    }

    public Part[] getParts() {
        return (Part[]) partList.toArray(TEMPLATE);
    }

    protected String getSqlForEntity() {
        return SQL_SELECT_PARTS;
    }

    protected void addEntityToListFromResultSet(ResultSet rs) 
            throws SQLException {
        Part p = new Part();
        p.setName(rs.getString("name"));
        p.setBrand(rs.getString("brand"));
        p.setRetailPrice(rs.getDouble("retail_price"));
        partList.add(p);
    }
}

¿Qué he logrado procediendo con este ejercicio de re-fabricación? Primero, ahora tengo dos clases que están mucho más enfocadas en sus trabajos particulares que antes. Todos los métodos en ambas clases son concisos, lo cual se les hace fáciles de entender. Segundo, note que la clase PartDb pertenece a parts y nada más. Todo el código de conectividad genérico, en texto estándar se ha movido hacia la clase pariente. Tercero, todos estos métodos se pueden probar ahora: cada método (excepto populate()) hace sólo una cosa. El método populate() es el método real de flujo de trabajo de estas clases. Este usa todos los otros métodos (privados) para efectuar el trabajo y se lee como un bosquejo de los pasos efectuados. Cuarto, ahora que tengo pequeños bloques de construcción, la reutilización del método es más fácil porque ahora puedo mezclarlos y hacer que combinen. Las oportunidades de usar un método grande como el método populate() original son pocas: es muy poco probable que necesite hacer estas cosas, exactamente en ese orden, en una clase subsecuente. Tener métodos atómicos le permite mezclar y combinar funcionalidades.

Las mejores infraestructuras tienden a ser aquellas extraídas del código en funcionamiento diseñadas preventivamente. Quien se siente a diseñar una infraestructura debe anticipar todas las formas en que los desarrolladores querrán usarla. La infraestructura termina incluyendo muchos recursos, con una alta probabilidad de que cualquier usuario no las use todas. Pero usted aún debe tener en cuenta los recursos no utilizados de su infraestructura seleccionada, porque estos suman a la complejidad accidental de sus aplicaciones. Las infraestructuras preventivas tienden a ser baldes gigantes de recursos, con otros recursos (no anticipados) dejados por fuera. JavaServer Faces (JSF) es un ejemplo clásico de una infraestructura preventiva. Uno de sus atributos más interesantes es la capacidad para conectarse a diferentes conductos de interpretación. Aunque si bien este recurso se usa raramente, todos los usuarios de JSF deben entender su impacto en el ciclo de vida de una solicitud JSF.

Las infraestructuras que crecen desde aplicaciones en funcionamiento tienden a ofrecer un conjunto de recursos más práctico, porque solucionan problemas reales que alguien ya ha enfrentado al escribir una aplicación. Las infraestructuras extraídas tienden a tener menos atributos extraños. Compara una infraestructura preventiva como JSF con una infraestructura extraída como Ruby on Rails, que ha crecido a partir del uso real.

El beneficio verdaderamente importante de este ejercicio es la habilidad de recolectar código reutilizable. Cuando usted ve el código en El Listado 1, usted no ve activos reutilizables; usted sólo ve una pila de código. Al arrastrar el método olio aparte, descubrí activos reutilizables. Pero estas ventajas van más allá de la reutilización. También he creado las bases para una infraestructura simple para manejar la persistencia en mi aplicación. Cuando llegue el momento de crear otra clase de límite simple para recolectar alguna entidad de una base de datos, yo ya tengo el código para ayudarme a hacer eso. Esta es la esencia de extraer infraestructuras más que de construirlas en una torre de marfil.

La recolección de activos reutilizables permite al diseño general de su aplicación comenzar a brillar a través de la pila de código que hace a la aplicación. Una de las metas del diseño emergente es encontrar patrones de uso idiomáticos dentro de su aplicación. La combinación de BoundaryBase y PartDb forma un patrón utilizable que aparece una y otra vez en esta aplicación. Cuando tenga todas las partes móviles en pequeñas piezas, es más fácil ver cómo encajan juntas.


SLAP

La segunda parte de la definición del método compuesto establece que usted debe "mantener todas las operaciones en un método al mismo nivel de abstracción." Un ejemplo de la aplicación de este principio le ayudará a comprender lo que significa y qué tipo de impacto puede tener en el diseño.

Considere el código en el Listado 6, tomado de una pequeña aplicación de comercio electrónico. El método addOrder() toma varios parámetros y pone la información de la orden en la base de datos.

Listado 6. El método addOrder() de un sitio de comercio electrónico
public void addOrder(ShoppingCart cart, String userName,
                     Order order) throws SQLException {
    Connection c = null;
    PreparedStatement ps = null;
    Statement s = null;
    ResultSet rs = null;
    boolean transactionState = false;
    try {
        s = c.createStatement();
        transactionState = c.getAutoCommit();
        int userKey = getUserKey(userName, c, ps, rs);
        c.setAutoCommit(false);
        addSingleOrder(order, c, ps, userKey);
        int orderKey = getOrderKey(s, rs);
        addLineItems(cart, c, orderKey);
        c.commit();
        order.setOrderKeyFrom(orderKey);
    } catch (SQLException sqlx) {
        s = c.createStatement();
        c.rollback();
        throw sqlx;
    } finally {
        try {
            c.setAutoCommit(transactionState);
            dbPool.release(c);
            if (s != null)
                s.close();
            if (ps != null)
                ps.close();
            if (rs != null)
                rs.close();
        } catch (SQLException ignored) {
        }
    }
}

El método addOrder() tiene mucho contenido desordenado. Aún así, en lo que estoy particularmente interesado es en el flujo de trabajo cerca del comienzo del bloque try . Note estas dos líneas consecutivas:

c.setAutoCommit(false);
addSingleOrder(order, c, ps, userKey);

Estas dos líneas de código ilustran una violación del principio SLAP. La primera (y los métodos arriba de la misma) tratan con estos detalles de bajo-nivel de configuración de la infraestructura de base de datos. La segunda es un método de orden superior, uno que un analista de negocios podría entender. Las dos líneas vienen de dos mundos diferentes. Es difícil leer el código cuando se tiene que saltar mentalmente entre niveles de abstracción, que es lo que el principio SLAP trata de evitar. Un corolario al problema de dificultad de lectura descansa en la dificultad para comprender el diseño subyacente de lo que hace el código, haciendo más difícil aislar los patrones idiomáticos para esta aplicación en particular.

Para mejorar el código en el Listado 6, Voy a re-fabricarlo con SLAP en mi mente. Después de un par de rondas de re-fabricación con el método de extracción, Quedo con el código en el Listado 7:

Listado 7. Abstracción mejorada del método addOrder()
public void addOrderFrom(ShoppingCart cart, String userName,
                     Order order) throws SQLException {
    setupDataInfrastructure();
    try {
        add(order, userKeyBasedOn(userName));
        addLineItemsFrom(cart, order.getOrderKey());
        completeTransaction();
    } catch (SQLException sqlx) {
        rollbackTransaction();
        throw sqlx;
    } finally {
        cleanUp();
    }
}

private void setupDataInfrastructure() throws SQLException {
    _db = new HashMap();
    Connection c = dbPool.getConnection();
    _db.put("connection", c);
    _db.put("transaction state",
            Boolean.valueOf(setupTransactionStateFor(c)));
}

private void cleanUp() throws SQLException {
    Connection connection = (Connection) _db.get("connection");
    boolean transactionState = ((Boolean)
            _db.get("transation state")).booleanValue();
    Statement s = (Statement) _db.get("statement");
    PreparedStatement ps = (PreparedStatement)
            _db.get("prepared statement");
    ResultSet rs = (ResultSet) _db.get("result set");
    connection.setAutoCommit(transactionState);
    dbPool.release(connection);
    if (s != null)
        s.close();
    if (ps != null)
        ps.close();
    if (rs != null)
        rs.close();
}

private void rollbackTransaction()
        throws SQLException {
    ((Connection) _db.get("connection")).rollback();
}

private void completeTransaction()
        throws SQLException {
    ((Connection) _db.get("connection")).commit();
}

private boolean setupTransactionStateFor(Connection c)
        throws SQLException {
    boolean transactionState = c.getAutoCommit();
    c.setAutoCommit(false);
    return transactionState;
}

Ahora el método se puede leer mucho mejor. Su cuerpo principal se adhiere a la meta del método compuesto: se lee como un bosquejo de los pasos que efectúa. Los métodos están a un nivel tan alto que ahora usted casi puede mostrárselo a una persona sin conocimiento técnico para describirle lo que hace este método. Si observa de cerca al método complete Transaction() , notará que es una línea de código. ¿No podría yo poner esa única línea de código de nuevo en el método addOrder() ? No sin dañar la facilidad de lectura y el nivel de abstracción del código. Saltar desde un flujo de trabajo de negocios de nivel superior hasta los detalles esenciales de las transacciones viola el principio SLAP. Tener un método complete Transaction() abstrae mi código hacia lo conceptual y lo aleja de los detalles concretos. Si en el futuro cambio la forma en la que accedo a bases de datos, puedo cambiar los contenidos del método complete Transaction() sin tocar el código de llamada.

El principio SLAP apunta a hacer su código más fácil de leer y de comprender. Pero también ayuda a descubrir los patrones idiomáticos que existen en su código. Note que tal patrón emerge en la forma en que las actualizaciones están protegidas con bloques de transacción. Usted puede re-fabricar aún más el método addOrder() hacia la combinación de los métodos en el Listado 8:

Listado 8. Patrón de acceso transaccional
public void wrapInTransaction(Command c) throws SQLException {
    setupDataInfrastructure();
    try {
        c.execute();
        completeTransaction();
    } catch (RuntimeException ex) {
        rollbackTransaction();
        throw ex;
    } finally {
        cleanUp();
    }
}

public void addOrderFrom(final ShoppingCart cart, final String userName,
                         final Order order) throws SQLException {
    wrapInTransaction(new Command() {
        public void execute() throws SQLException{
            add(order, userKeyBasedOn(userName));
            addLineItemsFrom(cart, order.getOrderKey());
        }
    });                
}

He agregado un método wrap In Transaction() que implementa este patrón común en mi aplicación, usando una versión en la línea del Patrón de Diseño de Comando, desde la Pandilla de Cuatro (vea Recursos). El modelo wrap In Transaction() efectúa toda la "carpintería" necesaria para asegurar que mi código funcione correctamente. He quedado con un feo código de texto estándar debido al envolvimiento anónimo de clase interna que es el propósito real de este método las dos líneas de código que aparece en el cuerpo del método addOrderFrom(). Este bloque de protección de recursos aparecerá una y otra vez en su código, así que es un candidato probable para ascender dentro de la jerarquía.

La razón por la cual implementé el código wrap In Transaction() usando una clase interna anónima destaca un punto importante sobre la expresividad de la sintaxis de lenguaje. Si usted escribe este código en Groovy, puede usar los bloques de cierre nativos para crear una versión mejorada de lo mismo, como se muestra en el Listado 9:

Listado 9. Envolviendo el acceso transaccional usando cierres Groovy
public class OrderDbClosure {
   def wrapInTransaction(command) {
     setupDataInfrastructure()
     try {
       command()
       completeTransaction()
     } catch (RuntimeException ex) {
       rollbackTransaction()
       throw ex
     } finally {
       cleanUp()
     }
   }
   
   def addOrderFrom(cart, userName, order) {
     wrapInTransaction {
       add order, userKeyBasedOn(userName)
       addLineItemsFrom cart, order.getOrderKey()
     }
   }
}

La sintaxis y recursos de lenguje avanzado de Groovy (ver Recursos) hacen un código más legible, especialmente cuando se combina con las técnicas complementarias de método compuesto y SLAP.


Conclusión

En esta edición, he observado dos patrones importantes de diseño y facilidad de lectura de código. El primer paso para atacar un diseño pobre para código existente es moldearlo en algo con lo que pueda trabajar. Un método de 300 líneas no sirve desde un punto de vista del diseño o la reutilización porque usted no podrá enfocarse en las partes importantes que lo constituyen. Al re-fabricarlo en piezas atómicas usted puede ver los activos con los que cuenta. En cuanto pueda verlos claramente, podrá recolectar las partes reutilizables y aplicar principios de diseño idiomático.

El la próxima edición trataré la re-fabricación hacia el diseño, construyendo sobre los conceptos del método compuesto y del principio SLAP. Allí, trataré cómo descubrir diseño que ya esté merodeando en su base de código.

Recursos

Aprender

Comentar

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=tecnologia Java
ArticleID=391048
ArticleTitle=Arquitectura evolutiva y diseño emergente: Método compuesto y SLAP
publish-date=04212009