Pensamiento funcional: Acoplamiento y composición, Parte 1

Explorando las implicaciones de las abstracciones acopladas de forma nativa

El trabajar cada día en una abstracción en particular (como la orientación a objetos) hace que sea difícil de ver cuando la abstracción le conduce a una solución que no es la mejor alternativa. Este artículo es el primero de los dos que explora algunas de las implicaciones del pensamiento orientado a objetos para la reutilización de código, comparándolas con las más funcionales alternativas tales como la composición.

Neal Ford, Software Architect / Meme Wrangler, ThoughtWorks Inc.

Neal FordNeal Ford es arquitecto de software y Meme Wrangler en ThoughtWorks, una consultora global en TI. Él también diseña y desarrolla aplicaciones, material instructivo, artículos para revistas, courseware y presentaciones en video/DVD, y es autor o editor de libros que abarcan una variedad de tecnologías, incluyendo el más reciente The Productive Programmer. Él se enfoca en diseñar y crear aplicaciones corporativas a gran escala. También es un orador aclamado internacionalmente en conferencias de desarrolladores en todo el mundo. Visite su sitio Web.



05-03-2012

Sobre esta serie

Esta serie tiene como objetivo reorientar su perspectiva hacia una forma de pensar funcional, ayudándole a observar problemas comunes de nuevas formas y a encontrar formas para mejorar su codificación del día a día.Explorar los conceptos de programación funcional, la infraestructura que permite la programación funcional en el lenguaje Java, los lenguajes de programación funcionales que se ejecutan en la JVM, y algunas futuras direcciones de tendencia de diseño del lenguaje.Esta serie está articulada para desarrolladores que conozcan Java y cómo funcionan sus abstracciones, pero que tengan poca o ninguna experiencia usando un lenguaje funcional.

La programación orientada a objetos hace que el código sea comprensible mediante la encapsulación de las partes móviles. La programación funcional hace que el sea código entendible minimizando las partes que se mueven.
— Michael Feathers, autor de Working with Legacy Code, vía Twitter

El trabajar en una abstracción en particular todos los días hace que se filtre poco a poco en el cerebro, influyendo en la manera de resolver problemas.Uno de los objetivos de esta serie es ilustrar una manera funcional de ver los problemas típicos.Para esta y la próxima entrega, abordo la reutilización de código a través de la refactorización y el impacto que conlleva la abstracción.

Uno de los objetivos de la orientación a objetos es hacer una encapsulación y trabajar con el estado más fácilmente. Por lo tanto, sus abstracciones tienden hacia la utilización del Estado para resolver problemas comunes, lo que implica el uso de diferentes clases e interacciones. — Lo que la cita anterior de Michael Feathers llama "partes móviles". La programación funcional trata de reducir al mínimo las partes móviles componiendo partes juntas en lugar de acoplando estructuras juntas.Este es un concepto sutil que es difícil de ver por los desarrolladores, cuya experiencia principal es con lenguajes orientados a objetos.

La reutilización del código a través de la estructura

El estilo imperativo (y especialmente) la programación orientada a objetos utiliza la estructura y la mensajería como bloques de compilación.Para volver a utilizar el código orientado a objetos, se extrae el código de destino en otra clase, para después utilizar la herencia para acceder a él.

Duplicación de código inadvertida

Para ilustrar la reutilización del código y las implicaciones que conlleva, vuelvo a una versión del clasificador de número que utiliza las entregas anteriores para ilustrar la estructura y el estilo del código.El clasificador determina si un número entero positivo es abundante, perfecto, o deficiente.. Si la suma de factores del número es superior a dos veces el número, es abundante, y si la suma es igual a dos veces el número, es perfecto, de lo contrario (si la suma es inferior a dos veces el número), es deficiente.

También se puede escribir el código que utiliza los factores de un número entero positivo para determinar si es un número primo (que se define como un número entero mayor que 1 cuyos únicos factores son 1 y el mismo número). Ya que estos dos problemas se basan en factores de número, son buenos candidatos para la refactorización (sin doble sentido) y por lo tanto para ilustrar los estilos de la reutilización de código.

El Listado 1 muestra el clasificador de números escrito en un estilo imperativo:

Listado 1. Clasificador de números imperativo
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import static java.lang.Math.sqrt;

public class ClassifierAlpha {
    private int number;

    public ClassifierAlpha(int number) {
        this.number = number;
    }

    public boolean isFactor(int potential_factor) {
        return number % potential_factor == 0;
    }

    public Set<Integer> factors() {
        HashSet<Integer> factors = new HashSet<Integer>();
        for (int i = 1; i <= sqrt(number); i++)
            if (isFactor(i)) {
                factors.add(i);
                factors.add(number / i);

            }
        return factors;
    }

    static public int sum(Set<Integer> factors) {
        Iterator it = factors.iterator();
        int sum = 0;
        while (it.hasNext())
            sum += (Integer) it.next();
        return sum;
    }

    public boolean isPerfect() {
        return sum(factors()) - number == number;
    }

    public boolean isAbundant() {
        return sum(factors()) - number > number;
    }

    public boolean isDeficient() {
        return sum(factors()) - number < number;
    }

}

Ya comenté la derivación de este código en la primera entrega, por lo que no la repetiré ahora.El propósito es ilustrar la reutilización del código.Esto me lleva al código del listado 2, el cual prueba los números primos:

Listado 2. Prueba de números primos, escritos imperiosamente
import java.util.HashSet;
import java.util.Set;

import static java.lang.Math.sqrt;

public class PrimeAlpha {
    private int number;

    public PrimeAlpha(int number) {
        this.number = number;
    }

    public boolean isPrime() {
        Set<Integer> primeSet = new HashSet<Integer>() {{
            add(1); add(number);}};
        return number > 1 &&
                factors().equals(primeSet);
    }

    public boolean isFactor(int potential_factor) {
        return number % potential_factor == 0;
    }

    public Set<Integer> factors() {
        HashSet<Integer> factors = new HashSet<Integer>();
        for (int i = 1; i <= sqrt(number); i++)
            if (isFactor(i)) {
                factors.add(i);
                factors.add(number / i);
            }
        return factors;
    }
}

Aparecen algunos artículos de la nota en El Listado 2.. El primero es el algo extraño código de inicialización en el isPrime() . Este es un ejemplo de un inicio. (Para más información sobre la inicialización de instancias — un técnica de Java que es secundaria en la programación funciones — ver "Arquitectura evolutiva y diseño emergente: Aprovechamiento del código reutilizable, Parte 2). .")

El resto de elementos de interés en El Listado 2 son los isFactor() y los métodos de factores() . Observe que son idénticos a sus duplicados en la clase ClassifierAlpha (en El Listado 1.). Este es el resultado natural de implementar dos soluciones independientemente y darse cuenta de que usted tiene prácticamente la misma funcionalidad.

Refactorización para eliminar la duplicación

La solución a este tipo de duplicación es refactorizar el código en una sola clase de Factores , la cual aparece en el Listado 3:

Listado 3. El código común de factorización refactorizada
import java.util.Set;
import static java.lang.Math.sqrt;
import java.util.HashSet;

public class FactorsBeta {
    protected int number;

    public FactorsBeta(int number) {
        this.number = number;
    }

    public boolean isFactor(int potential_factor) {
        return number % potential_factor == 0;
    }

    public Set<Integer> getFactors() {
        HashSet<Integer> factors = new HashSet<Integer>();
        for (int i = 1; i <= sqrt(number); i++)
            if (isFactor(i)) {
                factors.add(i);
                factors.add(number / i);
            }
        return factors;
    }
}

El código del Listado 3 es el resultado de utilizar la refactorización de Extracto de Superclase . Tenga en cuenta que debido a que los métodos extraídos utilizan una variable de asociado al número , se arrastra dentro de una superclase.Mientras se lleva a cabo esta refactorización, el IDE me preguntó cómo me gustaría manipular el acceso (par de objeto de usuario, ámbito protegido, y así sucesivamente).Elegí el ámbito protegido, el cual añade un número a la clase y crea un constructor para establecer su valor.

Una vez que hemos aislado y eliminado el código duplicado, tanto el número clasificador como el probador de números primos son mucho más sencillos.El Listado 4 muestra el clasificador de número refactorizado:

Listado 4. Clasificador de números simplificados refactorizados.
import java.util.Iterator;
import java.util.Set;

public class ClassifierBeta extends FactorsBeta {

    public ClassifierBeta(int number) {
        super(number);
    }

    public int sum() {
        Iterator it = getFactors().iterator();
        int sum = 0;
        while (it.hasNext())
            sum += (Integer) it.next();
        return sum;
    }

    public boolean isPerfect() {
        return sum() - number == number;
    }

    public boolean isAbundant() {
        return sum() - number > number;
    }

    public boolean isDeficient() {
        return sum() - number < number;
    }

}

El Listado 5 muestra el probador de números primos refactorizados:

Listado 5 probador de números primos refactorizado.
import java.util.HashSet;
import java.util.Set;

public class PrimeBeta extends FactorsBeta {
    public PrimeBeta(int number) {
        super(number);
    }

    public boolean isPrime() {
        Set<Integer> primeSet = new HashSet<Integer>() {{
            add(1); add(number);}};
        return getFactors().equals(primeSet);
    }
}

Independientemente de la opción que elija para acceder al número asociado durante la refactorización, debe tratar con una red de clases cuando piense en este problema.A menudo esto es bueno porque permite aislar partes del problema, pero también tiene consecuencias secundarias, cuando se realizan cambios en la clase padre.

Este es un ejemplo de reutilización de código a través del acoplamiento: enlazando dos elementos (en este caso, las clases) a través del estado compartido del campo número y el método getFactors() de la superclase.En otras palabras, esto funciona utilizando las reglas de acoplamiento incorporadas en el lenguaje.La orientación a objetos define estilos de interacción acoplados (como el acceso a las variables del asociado a través de la herencia, por ejemplo), por lo que usted tiene reglas predefinidas sobre cómo se acoplan las cosas, — lo cual es bueno porque se puede razonar sobre el comportamiento de una manera coherente.No me malinterprete, — no estoy sugiriendo que el uso de la herencia es una mala idea.Más bien, estoy sugiriendo que se utiliza en excedo en lenguajes orientados a objetos en lugar de abstracciones alternativas que tienen mejores características.


Reutilización del código a través de la composición

En la segunda entrega de esta serie, he presentado una versión funcional del clasificador de números en Java, que se muestra en el Listado 6:

Listado 6. Una versión más funcional del clasificador de números
public class FClassifier {

    static public boolean isFactor(int number, int potential_factor) {
        return number % potential_factor == 0;
    }

    static public Set<Integer> factors(int number) {
        HashSet<Integer> factors = new HashSet<Integer>();
        for (int i = 1; i <= sqrt(number); i++)
            if (isFactor(number, i)) {
                factors.add(i);
                factors.add(number / i);
            }
        return factors;
    }

    public static int sumOfFactors(int number) {
        Iterator<Integer> it = factors(number).iterator();
        int sum = 0;
        while (it.hasNext())
            sum += it.next();
        return sum;
    }

    public static boolean isPerfect(int number) {
        return sumOfFactors(number) - number == number;
    }

    public static boolean isAbundant(int number) {
        return sumOfFactors(number) - number > number;
    }

    public static boolean isDeficient(int number) {
        return sumOfFactors(number) - number < number;
    }
}

También tengo una versión funcional (utilizando funciones puras y no el estado compartido) del probador de números primos, cuyo método isPrime() aparece en el Listado 7. El resto del código es idéntico al de los métodos con el mismo nombre en El Listado 6.

Listado 7. Versión funcional del probador de números primos
public static boolean isPrime(int number) {
    Set<Integer> factors = factors(number);
    return number > 1 &&
            factors.size() == 2 &&
            factors.contains(1) &&
            factors.contains(number);
}

Al igual que hice con las versiones imperativas, extraje el código duplicado en su propia clase de Factores , cambiando el nombre del método de factores al de para legibilidad, como se muestra en el Listado 8:

Listado 8. La clase funcional refactorizada de Factores
import java.util.HashSet;
import java.util.Set;
import static java.lang.Math.sqrt;

public class Factors {
    static public boolean isFactor(int number, int potential_factor) {
        return number % potential_factor == 0;
    }

    static public Set<Integer> of(int number) {
        HashSet<Integer> factors = new HashSet<Integer>();
        for (int i = 1; i <= sqrt(number); i++)
            if (isFactor(number, i)) {
                factors.add(i);
                factors.add(number / i);
            }
        return factors;
    }
}

Porque todo el estado en la versión funcional se pasa como parámetros, el estado compartido no viene junto con la extracción.Una vez extraída esta clase, se pueden refactorizar para su uso, tanto el clasificador funcional como el probador de números primos. Ellistado 9 muestra el clasificador de número refactorizado:

Listado 9. Clasificador de números refactorizados
public class FClassifier {

    public static int sumOfFactors(int number) {
        Iterator<Integer> it = Factors.of(number).iterator();
        int sum = 0;
        while (it.hasNext())
            sum += it.next();
        return sum;
    }

    public static boolean isPerfect(int number) {
        return sumOfFactors(number) - number == number;
    }

    public static boolean isAbundant(int number) {
        return sumOfFactors(number) - number > number;
    }

    public static boolean isDeficient(int number) {
        return sumOfFactors(number) - number < number;
    }
}

El Listado 10 muestra el probador de números primos refactorizados:

Listado 10. Probador de números primos refactorizados
import java.util.Set;

public class FPrime {

    public static boolean isPrime(int number) {
        Set<Integer> factors = Factors.of(number);
        return number > 1 &&
                factors.size() == 2 &&
                factors.contains(1) &&
                factors.contains(number);
    }
}

Tenga en cuenta que no utilizo ninguna bibliotecas o lenguaje especial para hacer que la segunda versión sea más funcional. En cambio, lo hice utilizando la composición en lugar el acoplamiento para la reutilización del código.Tanto el Listado 9 como el Listado 10 utilizan la clase deFactores , pero su uso está totalmente contenido dentro los métodos individuales.

La distinción entre el acoplamiento y la composición es sutil pero importante.En un simple ejemplo como este, se puede ver el esqueleto de la estructura del código que se muestra a través.Sin embargo, cuando usted termina de refactorizar una base de código de gran tamaño, aparece el acoplamiento en todas partes ya que es uno de los mecanismos de reutilización de lenguajes orientados a objetos.La dificultad de comprender estructuras exuberantemente acopladas ha dañado la reutilización en lenguajes orientados a objetos, limitando la reutilización efectiva a dominios técnicos bien definidos como la correlación relacional de objetos y las bibliotecas widget.El mismo nivel de reutilización nos ha eludido cuando escribimos el obviamente menos estructurado código Java (como el código que se escribe en las aplicaciones para empresas).

Se podría haber hecho la versión imperativa mejor al observar lo que ofrece el IDE durante la refactorización, declinando educadamente, y utilizando la composición es su lugar.


Conclusión

Pensando como un programador más funcional significa pensar de forma diferente sobre todos los aspectos de la codificación.La reutilización del código es un evidente objetivo de desarrollo, y las abstracciones imperativas tienden a resolver ese problema de forma diferente a la que los programadores funcionales lo hacen.Esta entrega compara los dos estilos de la reutilización del código: el acoplamiento a través de la herencia y la composición a través de los parámetros.En la próxima entrega continuaremos explorando esta importante división.

Recursos

Aprender

Obtener los productos y tecnologías

Comentar

  • Participe en la comunidad developerWorks. Conéctese con otros usuarios developerWorks mientras explora los blogs, foros, grupos y wikis dirigidos a desarrolladores.

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=799338
ArticleTitle=Pensamiento funcional: Acoplamiento y composición, Parte 1
publish-date=03052012