Avançar para a área de conteúdo

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Todas as informações enviadas são seguras.

  • Fechar [x]

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Todas as informações enviadas são seguras.

  • Fechar [x]

Pensamento Funcional: Acoplamento e composição, Parte 1

Explorando as implicações de abstrações acopladas nativamente

Neal Ford, Software Architect / Meme Wrangler, ThoughtWorks Inc.
Neal Ford
Neal Ford é um arquiteto de software e Meme Wrangler, na ThoughtWorks, uma consultoria global de TI. Projeta e desenvolve aplicativos, materiais de instrução, artigos para revistas, treinamentos e apresentações em vídeo/DVD, e é autor ou editor de livros que abordam uma variedade de tecnologias, inclusive The Productive Programmer Seu enfoque é o projeto e construção de aplicativos corporativos de grande porte. Também é orador internacionalmente aclamado nas conferências de desenvolvedores ao redor do mundo. Conheça seu Web site.

Resumo:  Trabalhar todos os dias em uma abstração particular (como orientação a objeto) torna difícil ver quando aquela abstração o leva a uma solução que não é a melhor alternativa. Esse artigo é o primeiro de dois artigos que exploram algumas das implicações do pensamento orientado a objeto para reutilização de código, comparando-os a alternativas mais funcionais, como composição.

Visualizar mais conteúdo nesta série

Data:  14/Set/2011
Nível:  Intermediário Também disponível em :   Inglês
Atividade:  931 visualizações
Comentários:  


Sobre esta série

Esta série visa reorientar sua perspectiva para uma mentalidade funcional, ajudando a encarar problemas comuns de maneiras novas e encontrar maneiras de melhorar sua codificação diária. Ela explora os conceitos de programação funcional, estruturas que permitem programação funcional dentro da linguagem Java, linguagens de programação funcional executadas em JVM e algumas indicações de tendência futura do design da linguagem. A série é voltada para desenvolvedores que conhecem Java e como suas abstrações funcionam, mas têm pouca ou nenhuma experiência na utilização de uma linguagem funcional.

A programação orientada a objetos torna o código mais compreensível por meio da contenção de partes móveis. A programação funcional torna o código mais compreensível por meio da minimização de partes móveis.
— Michael Feathers, autor de Working with Legacy Code, no Twitter

Trabalhar em uma abstração particular todos os dias faz com que ela se infiltre gradualmente em seu cérebro, influenciando a forma como você soluciona problemas. Um dos objetivos dessa série é ilustrar uma maneira funcional de olhar para problemas típicos. Nesse artigo e no próximo, abordarei a reutilização de código por meio de refatoração e do impacto da abstração do operador.

Um dos objetivos da orientação a objetos é facilitar o encapsulamento e o trabalho com estado. Portanto, suas abstrações tendem a usar o estado para solucionar problemas comuns, implicando o uso de várias classes e interações — o que a citação de Michael Feathers acima chama de "peças móveis". A programação funcional tenta minimizar peças móveis compondo as peças juntas, em vez de acoplar as estruturas. Esse é um conceito sutil difícil de ver por desenvolvedores cuja experiência principal é com linguagens orientadas a objeto.

Reutilização de código via estrutura

O estilo de programação orientado a objeto imperativo (e especialmente ele) usa estrutura e sistemas de mensagens como blocos de construção. Para reutilizar código orientado a objeto, você extrai o código de destino em outra classe e, a seguir, usa herança para acessá-lo.

Duplicação inadvertida de código

Para ilustrar a reutilização de código e suas implicações, voltarei a uma versão do classificador de números que artigos anteriores usam para ilustrar a estrutura e o estilo de código. O classificador determina se um inteiro positivo é abundante, perfeitoou deficiente. Se a soma dos fatores do número for maior do que duas vezes o número, ele é abundante; se a soma for igual a duas vezes o número, ele é perfeito; caso contrário (se a soma for menor do que duas vezes o número), ele é deficiente.

Também é possível escrever código que usa os fatores de um inteiro positivo para determinar se ele é um número primo (definido como um inteiro maior do que 1 cujos fatores são 1 e o número em si). Como ambos os problemas baseiam-se nos fatores de número, eles são bons candidatos para refatoramento e, portanto, para ilustrar estilos de reutilização de código.

A Listagem 1 mostra o classificador de número escrito em um estilo imperativo:


Listagem 1. Classificador 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;
    }

}

Discuto a derivação desse código no primeiro artigo, portanto não o repetirei agora. Sua finalidade aqui é ilustrar a reutilização de código. Isso me leva ao código na Listagem 2, que testa números primos:


Listagem 2. Teste de números primos, escrito imperativamente

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;
    }
}

Alguns itens de destaque aparecem na Listagem 2. O primeiro é a inicialização do código ligeiramente estranha no método isPrime() . Esse é um exemplo de um inicializador de instância. (Para saber mais sobre inicialização de instância — uma técnica do Java que é incidental à programação funcional — , consulte "Arquitetura evolucionária e projeto emergente: Aproveitando código reutilizável, Parte 2.")

Os outros itens de interesse na Listagem 2 são o método isFactor() e o método factors() . Note que eles são idênticos às suas contrapartidas na classe ClassifierAlpha (na Listagem 1). Esse é o resultado natural de implementar duas soluções de forma independente e descobrir que você tem virtualmente a mesma funcionalidade.

Refatoração para eliminar duplicação

A solução para esse tipo de duplicação é refatorar o código em uma única classe Factors , que aparece na Listagem 3:


Listagem 3. Código de fatoração comum refatorado

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;
    }
}

O código da Listagem 3 é o resultado de usar a refatoração de Extract Superclass . Note que, como ambos os métodos extraídos usam a variável de membro number , ela é levada para a superclasse. Enquanto realizada esse refatoramento, o IDE perguntou se eu gostaria de manipular o acesso (par de acessador, escopo protegido e assim por diante). Escolhi escopo protegido, que adiciona number na classe e cria um construtor para definir seu valor.

Depois de isolar e remover o código duplicado, o classificador de números e o testador de números primos ficam muito mais simples. A Listagem 4 mostra o classificador de números refatorado:


Listagem 4. Classificador de números refatorado e simplificado
  
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;
    }

}

A Listagem 5 mostra o testador de número primo refatorado:


Listagem 5. Testador de número primo refatorado e simplificado

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);
    }
}

Independentemente da opção de acesso que você escolher para o membro number ao refatorar, é preciso lidar com uma rede de classes ao pensar sobre esse problema. Frequentemente, isso é bom, pois permite que você isole partes do problema, mas também tem consequências descendentes ao fazer alterações na classe pai.

Esse é um exemplo de reutilização de código via acoplamento: vincular dois elementos (nesse caso, classes) por meio do estado compartilhado do campo number e do método getFactors() da superclasse. Em outras palavras, isso funciona usando as regras de acoplamento integradas na linguagem. A orientação a objeto define estilos de interação acoplada (como você acessa variáveis de membro por meio de herança, por exemplo), portando há regras predefinidas sobre como as coisas são acopladas — o que é bom, pois é possível argumentar sobre o comportamento de forma consistente. Não me entenda errado — não estou sugerindo que usar herança seja uma má ideia. Em vez disso, estou sugerindo que ela é usada em excesso em linguagens orientadas a objetos, em vez de abstrações alternativas que têm características melhores.


Reutilização de código via composição

No segundo artigo dessa série, apresentei uma versão funcional do classificador de números em Java, mostrado na Listagem 6:


Listagem 6. Versão mais funcional do classificador 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;
    }
}

Também tenho uma versão funcional (usando funções puras e nenhum estado compartilhado) do testador de número primo, cujo método isPrime() aparece na Listagem 7. O resto de seu código é idêntico aos métodos de mesmo nome na Listagem 6.


Listagem 7. Versão funcional do testador de número primo

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

Como fiz com as versões imperativas, extraí o código duplicado em sua própria classe Factors , alterando o nome do método factors para of para facilitar a leitura, como mostra a Listagem 8:


Listagem 8. A classe Factors funcional refatorada

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;
    }
}

Como o estado na versão funcional é passado como parâmetro, nenhum estado compartilhado vem com a extração. Depois de extrair essa classe, posso refatorar o classificador e o testador de número primo funcionais para usá-la. A Listagem 9 mostra o classificador de números refatorado:


Listagem 9. Classificador de números refatorado
  
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;
    }
}

A Listagem 10 mostra o testador de número primo refatorado:


Listagem 10. Testador de número primo refatorado

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);
    }
}

Note que não usei quaisquer bibliotecas ou linguagens especiais para tornar a segunda versão mais funcional. Mas eu o fiz usando composição em vez de acoplamento para a reutilização de código. Tanto a Listagem 9 quanto a A Listagem 10 usa a classe Factors , mas seu uso é inteiramente contido dentro dos métodos individuais.

A distinção entre acoplamento e composição é sutil, mas importante. Em um exemplo simples como esse, é possível ver o esqueleto da estrutura do código aparecendo. No entanto, ao refatorar uma base de código grande, o acoplamento é exibido em todos os lugares, pois é um dos mecanismos de reutilização em linguagens orientadas a objeto. A dificuldade de entender estruturas exuberantemente acopladas prejudicou a reutilização em linguagens orientadas a objetos, limitando a reutilização efetiva a domínios técnicos bem definidos, como o mapeamento relacional de objetos e bibliotecas de widgets. O mesmo nível de reutilização nos ilude ao escrever código Java menos estruturado de forma óbvia (como o código que é escrito em aplicativos de negócios).

Você poderia ter tornado a versão imperativa melhor observando o que o IDE oferece durante a refatoração, recusando educadamente e, em vez disso, usando composição.


Conclusão

Pensar como um programador mais funcional significa pensar de forma diferente sobre todos os aspectos da codificação. A reutilização de código é uma meta óbvia do desenvolvimento e as abstrações imperativas tendem a solucionar esse problema de forma diferente da forma como os programadores funcionais o solucionam. Esse artigo comparou os dois estilos de reutilização de código: acoplamento via herança e composição via parâmetros. O próximo artigo continuará a explorar essa importante divisão.


Recursos

Aprender

Obter produtos e tecnologias

Discutir

  • Participe da comunidade do developerWorks. Entre em contato com outros usuários do developerWorks, enquanto explora os blogs, fóruns, grupos e wikis orientados ao desenvolvedor.

Sobre o autor

Neal Ford

Neal Ford é um arquiteto de software e Meme Wrangler, na ThoughtWorks, uma consultoria global de TI. Projeta e desenvolve aplicativos, materiais de instrução, artigos para revistas, treinamentos e apresentações em vídeo/DVD, e é autor ou editor de livros que abordam uma variedade de tecnologias, inclusive The Productive Programmer Seu enfoque é o projeto e construção de aplicativos corporativos de grande porte. Também é orador internacionalmente aclamado nas conferências de desenvolvedores ao redor do mundo. Conheça seu Web site.

Ajuda para Relatar Abuso

Relatar abuso

Obrigado. Esta entrada foi sinalizada para atenção do moderador.


Ajuda para Relatar Abuso

Relatar abuso

Falha no envio do Relatório de abuso. Tente novamente mais tarde.


developerWorks: Registre-se


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Selecione seu nome de exibição

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

(Deve possuir de 3 a 31 caracteres.)


Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Classificar este artigo

Comentários

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java
ArticleID=757690
ArticleTitle=Pensamento Funcional: Acoplamento e composição, Parte 1
publish-date=09142011
author1-email=nford@thoughtworks.com
author1-email-cc=jaloi@us.ibm.com

Conheça a IBM da sua cidade

Virtual Branch Office Brasil

A IBM está mais perto do que você imagina!


Tags

Help
Use o campo de pesquisa para encontrar todos os tipos de conteúdo no My developerWorks com essa tag.

Use a barra de rolagem para ver mais ou menos tags.

Tags populares mostra as principais tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Minhas tags mostra suas tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Use o campo de pesquisa para localizar todos os tipos de conteúdo no Meu developerWorks com essa tag. Tags populares mostra as tags principais para essa zona de conteúdo particular (por exemplo, tecnologia Java, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere). Minhas tags mostra as suas tags para essa zona de conteúdo em particular (por exemplo, tecnologia Java, Linux, WebSphere).