[Pesquisar este blog]

domingo, 19 de novembro de 2017

Java 9::Primeiras Impressões

A versão 9 da plataforma Java está disponível faz alguns meses, com adição de algumas novas características e a, tradicional, evolução de sua API, com mudanças importantes, mas nada revolucionário em relação ao que já existe. Agora é a hora de verificarmos o quanto as novidades são úteis no dia-a-dia.

A lista de features esperada na versão 9 incluiu elementos que podem ser organizados em três categorias, conforme seu escopo, ou seja, aqueles voltados para a linguagem de programação, para o Java Development Kit (JDK) e para a plataforma em si.

Este post contém comentário gerais sobre os novos elementos plataforma 9, alguns dos quais são tratados com mais detalhes nos seguintes posts complementares:

Modificações na Linguagem de Programação

As modificações inclusas linguagem de programação, na verdade, concentram-se no aperfeiçoamento das interfaces, com a adição de métodos privados.

Métodos privados em interfaces [post]

Recordando rapidamente, o Java 8 introduziu a possibilidade incluir métodos públicos default e públicos estáticos em interfaces. Estes novos tipos de métodos exigem implementação na própria interface, diferentemente dos demais métodos públicos abstratos que devem ser implementados nas classes que realizam a interface. Na prática, com isso, permite que interfaces existentes sejam atualizadas, recebendo novas operações, sem provocar a propagação da mudança, pois as classes que já as implementam não sofrem qualquer exigência adicional. Detalhes e exemplos do uso dos métodos default e static em interfaces estão disponíveis no post Interfaces::criação,uso e atualização no Java 8
.
Na versão 9 foram introduzidos os métodos privados nas interfaces, declarados com o especificador de visibilidade private e, opcionalmente, com o modificador static. Como outros membros privados, só podem ser acessados por aqueles presentes em seu tipo.

Um exemplo esquemático de um método privado estático seria:

public interface J9News {
private static boolean validate() { ... }
}

O uso de métodos privados, estáticos ou não, numa interface permite que o programador defina operações auxiliares para os demais métodos default e estáticos públicos, sem expor tal operação. O post Java 9::MétodosPrivados em Interfaces dá mais detalhes sobre esta nova característica.

Modificações no JDK

Nos próximos dias, serão comentadas algumas das novas características do Java Develpment Kit, tais como:
  • Melhorias na Stream API
  • Melhorias em Optional<T>
  • Extensões do Javadoc [JEP 221, 224, 225, 261]
  • Processador Nashorn [JEP 236]
  • Catálogos XML [JEP 268]
  • Novo cliente para HTTP 2 [JEP 110]
  • Imagens multirresolução [JEP 251]
  • I/O para imagens TIFF [JEP 262]
  • Suporte para Unicode 7.0 e 8.0 [JEP 227, 267]

Melhorias na Stream API [post]

O Java 9 traz alguns complementos úteis para esta API, como novas fontes de dados para as streams, além de algumas novas características. Além da possibilidade de obter um stream sequencia a partir de qualquer uma das coleções, existia no Java 8 um conjunto limitado de possibilidades obtenção destas streams  a partir de elementos de outros tipos externos às coleções, como por exemplo, java.io.BufferedReader.lines().

O Java 9 adiciona algumas novas fontes úteis a esse conjunto, por meio destes novos métodos:
  • java.util.Scanner.tokens()
  • java.util.regex.Matcher.results()
  • java.util.Optional.stream()
Além disso, foram adicionados quatro novos métodos na interface java.util.stream.Stream<T> que são:
  • Stream<T> takeWhile(Predicate)
  • Stream<T> dropWhile(Predicate)
  • Stream<T> ofNullable(T)
  • Stream<T> iterate(T, Predicate< T>, UnaryOperator<T>)
Embora pareça pouco, tais métodos são bastante úteis e flexíveis, como discutido no post Java 9::Melhorias na Stream API.

Melhorias em Optional<T> [post]

Para auxiliar no tratamento da exceção NullPointerException, foi introduzido no Java 8 a classe java.util.Optional<T>. Objetos do tipo Optional são como contêineres (i.e., containers) que podem armazenar um valor de qualquer tipo T ou apenas null. A classe Optional também provê alguns métodos úteis que podem eliminar a verificação explícita da presença de null.

Na versão 9 do Java foram introduzidos métodos novos:
  • public void ifPresentOrElse(Consumer<T> action, Runnable emptyAction);
  • public Optional<T> or(Supplier<Optional<T>> supplier);
O primeiro método, ifPresentOrElse() verifica se um valor está presente na instância de Optional<T>, conduzindo a ação indicada com o valor, ou realizando outra para a situação de vazio (conteúdo null).

Outro método interessante é or(), cujo comportamento é o seguinte: se o valor está presente, retorna um Optional que encapsula tal valor, senão, retorna um outro Optional produzido pela função de geração tomada como argumento.

As melhorias em Optional<T> são detalhadas no post Java 9::Melhorias no Optional.

Modificações na Plataforma

A versão 9 também incorporou várias modificações em nível de plataforma, dentre elas:
  • Console Java (jshell) [JEP 222]
  • Sistema de modularização (conhecido como Jigsaw) [JSR 376]
  • JARs multi-release [JEP 238]
  • jlink [JEP 282]
  • Registro unificado [JEP 158]
  • Microbenchmark Suite [JEP 230]
  • Atualização da Process e da Concurrency API [JEP 102 & 266]
  • Coletor de lixo G1 como default [JEP 248]

jshell [post]

O jshell é uma nova e interessante ferramenta de linha de comando que corresponde a um console Java. Diferentemente dos IDEs Java, onde devemos construir classes e programas completos que devem ser compilados para poderem ser executados; no jshell é possível avaliar expressões, efetuar declarações e executar diretivas do Java, sem a necessidade de construir um projeto, um programa ou mesmo um método para seu teste.

O fato de constituir um ambiente interativo torna o jshell muito interessante para que iniciantes estudem o Java experimentando suas construções. Ao mesmo tempo é útil para programadores mais experientes, pois é um ambiente de simulação rápido e direto. Mais detalhes sobre o jshell estão no post Java 9::O Console jshell.

Jigsaw [post]

A característica mais esperada da versão 9 do Java é o resultado do projeto Jigsaw, traz um novo sistema de modularidade para plataforma Java. A modularidade é um princípio importante no projeto de software que enfatiza a criação de conjuntos de classes ou componentes que possam ser reutilizados em diferentes contextos. Até a versão 8, os arquivos JAR (Java Archives) eram a unidade básica da modularidade no Java. Os JARs são arquivos compactados contendo uma estrutura de subdiretórios, arquivos de classe Java e outros arquivos usados como recursos da aplicação.

Embora os arquivos JAR sejam capazes de agrupar classes relacionadas, sua organização possui algumas limitações, como o encapsulamento fraco e a existência de dependências explícitas entre seus elementos, além de problemas associados a presença de JARs de versões diferentes no mesmo classpath.

Por conta disso foi criado um novo artefato, o module, que estabelece uma nova unidade de modularização, resolvendo grande parte dos problemas exibidos pelos packages, e ainda mantendo compatibilidade com aplicações de versões anteriores da plataforma. O Jigsaw é apresentado e comentado no post Java 9: Jigsaw.

jlink

O jlink é uma nova ferramenta de linha de comando que faz parte do novo sistema de modularização do Java. Seu propósito é empacotar os diversos módulos de uma aplicação, facilitando sua utilização e, principalmente, sua distribuição. A ferramenta jlink é abordada juntamente com os posts sobre o Jigsaw.

Mais à frente, comentaremos sobre outras das interessantes características adicionadas na plataforma nesta versão 9.

Considerações Finais

Depois de dissecarmos muitas das novas características da versão 9 do Java, fica claro o seu amadurecimento. É claro que poucos desenvolvedores utilizarão à fundo todas estas adições, concentrando-se nos elementos mais próximos à suas áreas de atuação. Além disso, veremos críticas sobre esta ou aquela característica, assim como elogios sobre outras, mas seria possível garantir a satisfação de todos em relação a tudo.

O que vale mesmo, é que o Java, a cada versão, mostra seu valor!

sexta-feira, 10 de novembro de 2017

Computadores, Comunicação e Colaboração

É sempre importante refletir sobre a contribuição da tecnologia na educação. De fato, acho incontestável que os computadores e as redes que permitem a integração destas máquinas, são importantíssimos para prover melhor comunicação e colaboração, dois elementos fundamentais de qualquer plataforma ou sistema de ensino.

Tomando como foco alguns objetivos educacionais específicos, tais como a aprendizagem ativa (active learning) e os diferentes estilos de aprendizagem (learning styles), poderíamos tecer uma série de considerações importantes para a criação de ambientes educacionais.

O aprendizado é mais efetivo quando o aluno participa ativamente do processo de construção de seu próprio conhecimento, ou, sobre outra ótica, quando se vê no contexto apresentado ou quando se coloca mais imerso dentro das situações de ensino propostas. Também é necessário ressaltar que as pessoas não são iguais, ou seja, exibem diferentes características, o que as torna mais abertas a certos tipos de experiências do que outros, aprendendo mais facilmente sobre condições específicas. Tais características poderiam ser agrupadas sob a denominação de estilos de aprendizagem.

Num primeiro momento, a tecnologia provê meios de oferecer novas alternativas para as questões do quando e do onde, eventualmente, do como também; assim oferece suporte para comunicação se dê de maneira síncrona e assíncrona e, portanto, incluindo as situações de contato presencial e distante. Existem ótimas soluções para comunicação síncrona e assíncrona a distância. Sendo assim, tais meios oferecidos pela tecnologia e, mais especificamente, da comunicação mediada por computadores (Computer Mediated Communication - CMC), desde que adequadamente utilizados, se constituem alternativas flexíveis para promover a participação ativa do educando. Além das restrições de custo e infraestrutura, os métodos educacionais empregados são determinantes, tanto no favorecimento do aprendizado ativo, como no melhor atendimento dos estilos de aprendizagem existentes.

Torna-se evidente que os professores deverão estar mais amplamente capacitados, pois além do indispensável conhecimento teórico-técnico de suas disciplinas, também é necessário o domínio de métodos pedagógicos e ferramentas educacionais que favoreçam a aprendizagem.

Nas diversas situações possíveis da CMC, para que o aprendizado seja ativo e os diferentes estilos de aprendizagem cobertos, os papéis de professor e de aluno devem ser revistos: o professor, numa postura diferente da tradicional, deve se esforçar para oferecer o maior número possível de estímulos diferentes que possibilitem aos alunos a construção do conhecimento; o aluno, cuja atitude requerida também é outra, torna-se corresponsável pela construção do próprio conhecimento, exigindo que a aprendizagem se calce na sua participação ativa e crítica. Aqui não se deseja, tão pouco se considera adequada, a transmissão e reprodução pura e simples de conhecimentos prontos.



Ao tomar o estudante como foco, de maneira que suas características próprias sejam respeitadas, a CMC possibilita a formação de grupos, num contexto mais adaptado aos seus participantes, que propicia um ambiente mais rico no qual seja favorecida a troca de conhecimentos e de experiências, assim como a colaboração propriamente dita, realimentando o processo. A CMC pode contribuir oferendo novas formas para suportar o surgimento e a interação destes grupos, que tendenciosamente criação suas próprias identidades.

Tais formas de comunicação oferecidas nos levam a crer que o estudante seja motivado a adotar uma postura mais ativa, independente e responsável, possibilitando ganhos relacionados às habilidades de comunicação oral e escrita, capacidade de pesquisa, interação social e comportamento ético; tudo isso respeitando seu estilo individual. Por outro lado, em virtude dessas diferenças pessoais, devemos considerar que o excesso de liberdade e flexibilidade podem conduzir a posturas desleixadas, inconsequentes, irresponsáveis ou, quando possível, anônimas. A possibilidade de interdependência com seu grupo também exibe pontos favoráveis e desfavoráveis análogos.

Disto temos que o papel do professor é fundamental não como apresentador, expositor ou avaliador tradicionais, mas como aquele que abre mão do centro das atenções da sala, deixando o protagonismo clássico, para assumir múltiplos papeis: de guia, de instrutor, de facilitador, de orientador, de observador, de conselheiro e de mediador. Todos estes papeis de parceria com seus alunos, que exigem participação ativa deste professor, no oferecimento de retorno e de suporte aos alunos.

Obviamente, em função dos objetivos relacionados ao conteúdo ao desenvolvimento de certas habilidades ou comportamentos, o professor deve dirigir aos alunos na direção correta sem, necessariamente, determinar ou impor um único caminho. Com isto abrem-se as possibilidades reais do aprendizado ativo e da adaptação efetiva dos diferentes estilos de aprendizagem.

Tecnologia e pedagogia passam a ter um relacionamento interessante e dependente. A tecnologia estende as possibilidades da comunicação, isto é, alternativas para tornar a informação mais persistente, mais facilmente disponível. Isto implica em permitir paradas, individualmente estabelecidas, para reflexão, revisão, ampliação e retomada de conteúdos, ou mesmo de descanso. Também favorece o maior envolvimento de pessoas tímidas, admite o anonimato, estabelecendo um novo patamar e riqueza para aprendizagem e participação.

Ao mesmo tempo, diversos problemas podem ocorrer, dentre os quais se destacam: as questões da variabilidade da participação opcional ou voluntária; a maior dificuldade do professor na identificação de alunos com problemas de aprendizagem, que ficam ocultos pela menor participação; a maior dificuldade na determinação do escopo, da profundidade, do valor relativo, da duração e da frequência das avaliações; a redução o inexistência de contato social real; das dificuldades do preparo de materiais; e do atendimento das expectativas.

Muitos destes problemas podem ser solucionados ou, ao menos mitigados, pela aplicação consistente de metodologias de ensino consolidadas; pela determinação clara das responsabilidades; pela especificação precisa das tarefas e de seus objetivos; pelo fornecimento rápido, consistente e constante de retorno e de suporte

Para Saber Mais


  • WikiPedia
    Computer Mediated Communication;
  • Oxford Jounals
    Computer-mediated Communication;
  • Study.com
    Computer-Mediated Communication: Definition, Types & Advantages
  • AECT
    Computer Mediated Communication.

terça-feira, 5 de setembro de 2017

Business Process Modeling Notation- Uma breve introdução

O BPMN ou Business Process Modeling Notation é uma Notação para modelagem de processos de negócios é criada para que as organizações pudessem padronizar a modelagem e a representação de processos de negócios.
Dentre seus objetivos temos permitir que as organizações, de qualquer tipo, modelem e documentem seus processos de negócio, de maneira que as pessoas, de diferentes níveis hierárquicos, possam compreender tais processos com clareza e, principalmente, os papéis que desempenham nestes processos.

Este é o assunto de uma palestra que ministrei na Semana de Tecnologia 2017 da FATEC Jundiaí, cujos slides compartilho abaixo.

terça-feira, 23 de maio de 2017

Os Princípios SOLID::DIP (parte VI)

Com o objetivo orientar nas atividades de projeto e desenvolvimento de sistemas que possam ter o maior ciclo de vida possível, ou seja, sistemas concebidos de maneira a exibir tanto funcionamento adequado, como facilidades para sua própria manutenção, Robert Martin enunciou cinco princípios cujo acrônimo SOLID é bastante conhecido.
Estes cinco princípios são:
Single Responsability Principle
Open-Closed Principle
Liskov`s Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle

Este post finaliza esta pequena série de artigos, abordando o DIP ou Princípio da inversão de dependência.

DIP::Dependency Inversion Principle

Dependa de abstrações. Não dependa de elementos concretos
Para Martin (1996), a aplicação rigorosa dos princípios Open-Closed (OCP) e da substituição de Liskov (LSP) podem ser generalizadas num outro princípio que denominou Princípio da Inversão de Dependência (Dependency Inversion Principle) ou apenas DIP.

O Princípio da Inversão de Dependência determina que um módulo de alto nível não deve depender de outros módulos de baixo nível. Aqui a ideia de nível se refere à abstração contida nestes módulos. Desta forma, um módulo com certo nível de abstração não deveria depender de outros, menos abstratos que ele próprio, ou seja, as abstrações nele contidas não devem depender de detalhes ou especificidades da implementação de outros módulos. Resumidamente, abstrações não devem depender de detalhes; enquanto os detalhes devem depender de abstrações.

Isso requer que, no projeto e implementação de qualquer módulo ou componente de software sejam utilizadas abstrações do próprio nível, assim sendo:
  • Interfaces devem depender de outras interfaces;
  • Classes concretas não devem ser adicionadas às assinaturas das interfaces;
  • Enquanto as interfaces podem (e devem) ser usadas na assinatura de métodos de quaisquer classes.

A questão aqui é que trabalhar com classes concretas leva, invariavelmente, a um acoplamento maior do que aquele obtido com o uso das interfaces que estariam presentes nestas mesmas classes. A programação dirigida a interfaces deseja, portanto: reduzir o acoplamento, exatamente como prescrito pelo OCP e pelo LSP; e tornar o código mais reusável, tanto por meio de sua extensão, como pela substituição de seus elementos.

Considere as duas interfaces Reader e Writer, dadas a seguir, que são abstrações de operações possíveis envolvendo a leitura e escrita de caracteres em algum dispositivo de entrada e saída (E/S) não específico.

public interface Reader {
  char getchar();
}
public interface Writer {
  void putchar(char c);
}

Considere também as realizações esquemáticas (implementações) das interfaces Reader e Writer, que poderiam constituir formas concretas das operações de leitura e escrita de caracteres determinadas por tais interfaces, mas agora em dispositivos de E/S específicos, como o teclado para entrada (leitura) e a impressora para saída (escrita).

public class Keyboard implements Reader {
  public char getchar()
  { /* código efetivo */ }
}

public class Printer implements Writer {
  public void putchar(char c)
  { /* código efetivo */ }
}

Qualquer programa que utilize instâncias diretamente obtidas das classes Keyboard e Printer acabam por estar acoplados ao código efetivo dos métodos getchar() e putchar() implementados nestas classes. Caso se deseje utilizar operações de leitura ou escrita diferentes, ou associadas a outros dispositivos periféricos, a substituição destas classes seria mais complexa, assim como sua modificação poderia criar problemas em outras partes do código que dependem de sua funcionalidade. O exemplo que segue, a classe ConcreteCharCopier, exibe tais fragilidades:

public class ConcreteCharCopier {
  public void copy(Keyboard reader, Printer writer)
  {
    int c;
    while ((c = reader.getchar()) != EOF) {
      writer.putchar();
    }
  }
}

Esta classe poderia ser usada assim:

Keyboard keyboard = new Keyboard(); // uma implementação de Reader
Printer printer = new Printer(); // uma implementação de Writer
// classe definida por meio de elementos concretos
ConcreteCharCopier ccc = new ConcreteCharCopier();
// uso da operação de cópia
ccc.copy(keyboard, printer);

Se for requerida uma mudança onde a leitura dos caracteres não é mais obtida do teclado, não é possível substituir o objeto correspondente ao parâmetro de tipo Keyboard, por outro de tipo diferente, gerando um impasse: se método copy(Keyboard, Printer) for alterado para que a entrada seja outra, por exemplo copy(Disk, Printer), todos os locais onde a versão original foi utilizada deverão ser alterados, o que provavelmente não será adequado. Outra possibilidade é a adição de uma segunda versão desta operação por meio da sobrecarga, mas a solução é temporária, pois implica que novas operações deverão ser adicionadas para cada novo dispositivo existente. Modificar a classe Keyboard para efetuar a leitura de outro periférico (o que é, por si só, uma heresia) leva ao mesmo impasse inicial.

Todos os problemas desta modificação estão associados ao simples fato de que a operação de cópia desejada é uma abstração que ficou dependente de elementos concretos.

Quando um programa depende das abstrações contidas em interfaces, tais como as ilustradas em Reader e Writer, passa a não depender de como tais abstrações são implementadas, ou seja, passa abstrair detalhes das implementações dos serviços dos quais depende.

Uma implementação adequada da operação de cópia poderia ser como na classe AbstractCharCopier, que segue:

public class AbstractCharCopier {
  public void copy(Reader reader, Writer writer)
  {
    int c;
    while ((c = reader.getchar()) != EOF) {
      writer.putchar();
    }
  }
}

A pequena mudança efetuada na maneira com que os objetos que implementam as funcionalidades de leitura e escrita de caracteres são passados para operação, faz toda a diferença. Agora a operação de cópia tem assinatura copy(Reader, Writer), de maneira que quaisquer dispositivos de entrada e de saída possam ser combinados e utilizados, sem requerer modificações neste método. Basta que os objetos supridos implementem as interfaces Reader e Writer, como no trecho que segue.

Keyboard keyboard = new Keyboard(); // uma implementação de Reader
Disk disk = new Disk(); // uma nova implementação de Reader
Printer printer = new Printer(); // uma implementação de Writer
// classe definida por meio de abstrações
AbstractCharCopier acc = new AbstractCharCopier();
// uso da operação de cópia
acc.copy(keyboard, printer);
acc.copy(disk, printer);

Note os princípios da substituição de Liskov (LSP) e do aberto-fechado (OCP) em ação: a substituição de um tipo (Keyboard) por outro (Disk) não provoca qualquer efeito colateral indesejado, pois ambas as classes implementam a mesma interface (que se comporta como um supertipo aqui). De modo análogo, os tipos existentes podem ser estendidos para suprir novas necessidades, sem a necessidade de alteração dos tipos existentes (no caso, Keyboard, Printer e, até mesmo, as interfaces Reader e Writer).

A ideia central deste princípio é isolar as classes por meio de uma ou mais camadas de interfaces, as quais definem as abstrações das quais tais classes dependem, separando-as dos detalhes de suas implementações, que podem então mudar livremente.

A figura abaixo ilustra o mecanismo preconizado por este princípio.


Isso reduz o acoplamento e facilita mudanças no projeto.

Considerações finais

Construir código modular, apesar de ser um bom e importante começo, não significa, necessariamente, ter um bom projeto.

Projetar bem, para o funcionamento do sistema e sua futura manutenção, inclui gerenciar todas as dependências dos componentes deste sistema, pois dependências não controladas podem comprometer, em definitivo, um projeto de software, no momento que alterações se fazem necessárias para correção de bugs ou atendimento de mudanças na especificação do sistema.

Os princípios SOLID são diretrizes valiosas para programadores e projetistas de software, pois:
- O código se torna mais e melhor testável (Test Driven Design – TDD, que não envolve apenas o teste do software, mas seu projeto);
- Devem ser seguidos sempre que possível (bom senso nunca é demais), trazendo os vários e substanciais ganhos comentados ao longo desta série de artigos.

Finalmente, o aprendizado contínuo e consistente é a única maneira para que, de fato, se possa buscar a excelência.

Para Saber Mais

  • LARMAN, Craig. Utilizando UML e padrões: uma introdução à análise e ao projeto orientados à objetos e ao Processo Unificado. Porto Alegre: Bookman, 2007.
  • MARTIN, R. C.; et. al. Clean Code: a handbook of agile software craftsmanship. Boston: Pearson Education, 2009.
  • MARTIN, R. C. The Clean Coder. Upper Sadle River: Prentice-Hall, 2011.
  • MARTIN, R. C. Principles of OOD. Disponível em http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod, recuperado em 17/02/2017.
  • MARTIN, R. C. Get a SOLID start. Disponível em http://objectmentor.com, recuperado em 17/02/2017.
  • PAGE-JONES, Meilir. Fundamentos do Desenho Orientado a Objetos. São Paulo: Makron Books, 2001.
  • SOMMERVILLE, I. Software Engineering. 9th. Ed. Boston: Addison-Wesley, 2011.

segunda-feira, 15 de maio de 2017

Os Princípios SOLID::ISP (parte V)

Parte I | Parte II | Parte III | Parte IV  | Parte V | Parte VI
Os princípios de projeto e programação orientada a objetos enunciados por Martin em 1995, depois denominados como SOLID, são um importante guia para orientação tanto a construção de sistemas fáceis de manter, como para reorganizar (refactor) código existente, além de serem consistentes com para desenvolvimento de software.


Os cinco princípios que formam o acrônimo SOLID:
  • Single Responsability Principle
  • Open-Closed Principle
  • Liskov's Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

Nos post anteriores foram tratados os princípios da responsabilidade única (SRP), do aberto-fechado (OCP) e da substituição de Liskov (LSP). Neste artigo falaremos sobre o Princípio da Segregação de Interfaces (ISP).

ISP::Interface Segregation Principle

Não dependa daquilo que você não necessita!
O Princípio da Segregação da Interface ou Interface Segregation Principle, aqui referido apenas como ISP, afirma que é preferível dispor de muitas interfaces de programação de cliente específicas do que uma única interface de programação de cliente de propósito geral.

Entende-se como interface de programação de cliente o conjunto de operações que um interface especifica, as quais deverão ser implementadas (ou realizadas) por uma ou mais classes. Abaixo temos uma interface Java denominada IVeiculo, dotada de três operações, todas implicitamente públicas e abstratas.

public interface IVeiculo {
    int getNumeroMaximoOcupantes();
    int getNumeroAtualOcupantes();
    void setNumeroOcupantes(int ocupantes);
}

Para que esta interface seja útil, a mesma deverá ser implementada por uma classe, isto é, um classe pode optar por realizar esta interface, substituindo todos os métodos presentes na interface, como no outro exemplo que segue:

public classe MeuVeiculo implements IVeiculo {
    public static final int MAX_OCUP = 5;
    public int nOcupantes = 0;
    public int getNumeroMaximoOcupantes() {
        return MAX_OCUP;
    }
    public int getNumeroAtualOcupantes() {
        return nOcupantes;
    }
    public void setNumeroOcupantes(int ocupantes) {
        if (ocupantes<0 || ocupantes>MAX_OCUP) {
            throw new RuntimeException("número ocupantes inválido");
        }
        nOcupantes = ocupantes;
    }
}

Neste caso, a classe concreta MeuVeiculo só necessitou implementar três métodos para realizar aqueles especificados pela interface IVeiculo, “contratada” na sua declaração. Espera-se que todos estes métodos sejam, de fato, úteis nesta implementação.

É este o foco do ISP: os clientes (as classes concretas que implementam interfaces) não devem ser forçados a depender de funcionalidades das quais não necessitam, ou seja, uma classe que realiza uma interface não deveria implementar métodos sem necessidade. Assim, ao invés de classes e interfaces complexas, e com numerosas operações, este princípio direciona para a construção de múltiplos elementos menores e mais coesos.

Esta pode ser a situação de uma interface dotada de muitas operações, que, em geral, criará a necessidade da construção de classes clientes dotadas das implementações de tais métodos. 

Classes complexas, com muitas operações, são inevitáveis em algumas situações, mas é igualmente comum que seus clientes utilizem apenas um subconjunto de suas funcionalidades. Mesmo que indiretamente, tais clientes dependem de funcionalidades que eles não utilizam, assim fica a questão: o que acontece se tais classes complexas mudam?

Considere um caixa eletrônico bancário (Automatic Teller Machine - ATM), em cuja tela são exibidas múltiplas mensagens conforme as operações executadas. A não aplicação de SRP, OCP e LSP podem levar a criação de uma interface como a que segue, com muitas operações!

public interface ATMMessenger {
    void askForCard();
    void tellInvalidCard();
    void askForPin();
    void tellInvalidPin();
    :
    void askForWithdrawAmount();
    void tellInsuficientBalance();
    void tellBalance();
    :
    void tellAmountDeposited();
}

Ao acrescentar uma nova operação nesta interface, muitas classes cliente deverão ser modificadas e recompiladas. Mas isto seria necessário?

A separação de uma interface complexa (de propósito geral) em muitas interfaces específicas simplifica o problema. Esta separação é o que se denomina segregação no ISP. Como no exemplo que segue:

public interface LoginMessenger {
    void askForCard();
    void tellInvalidCard();
    void askForPin();
    void tellInvalidPin();
}
public interface WithDrawMessenger {
    void askForWithdrawAmount();
    void askForFeeConfirmation();
    void tellInsuficientBalance();
    void tellBalance();
}

Nesta nova situação, de interfaces com maior foco e menos operações, a adição de uma operação nova reduz o escopo de alterações às classes que efetivamente implementam tais funcionalidades. Ou seja, somente aquelas que dependem das funcionalidades da interface modificada.

Observe abaixo a comparação entre as implementações hipotéticas de uma única e grande interface geral em relação à realização de interfaces específicas, ainda considerando o exemplo do caixa eletrônico bancário.


Conclusões

O esforço da divisão de uma interface complexa, com muitos métodos, em várias interfaces menores, com grupos melhor correlacionados de operações, simplifica tanto a construção do código como sua manutenção.

Na construção torna-se possível selecionar e implementar apenas as interfaces necessárias, reduzindo-se o esforço de codificação. Se todas as interfaces são necessárias, a divisão não causa qualquer problema (não requer mais código ou qualquer manobra especial). Na manutenção, a alteração de interfaces menores causa menor impacto no código, o que é um grande benefício.

Fica claro que é melhor não depender das coisas das quais não se necessita.

Assim, a garantia do ISP leva a:
  • Baixo acoplamento e alta coesão;
  • Promoção do projeto ortogonal (ortogonalidade do projeto) onde:
    • Módulos centrados em sua necessidade (SRP) e
    • Dependências reduzidas entre eles (LSP e ISP).


Para Saber Mais

  • LARMAN, Craig. Utilizando UML e padrões: uma introdução à análise e ao projeto orientados à objetos e ao Processo Unificado. Porto Alegre: Bookman, 2007.
  • MARTIN, R. C.; et. al. Clean Code: a handbook of agile software craftsmanship. Boston: Pearson Education, 2009.
  • MARTIN, R. C. The Clean Coder. Upper Sadle River: Prentice-Hall, 2011.
  • MARTIN, R. C. Principles of OOD. Disponível em http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod, recuperado em 17/02/2017.
  • MARTIN, R. C. Get a SOLID start. Disponível em http://objectmentor.com, recuperado em 17/02/2017.
  • PAGE-JONES, Meilir. Fundamentos do Desenho Orientado a Objetos. São Paulo: Makron Books, 2001.
  • SOMMERVILLE, I. Software Engineering. 9th. Ed. Boston: Addison-Wesley, 2011.

domingo, 7 de maio de 2017

Os Princípios SOLID::LSP (parte IV)

Parte I | Parte II | Parte III | Parte IV | Parte V | Parte VI
Os princípios SOLID, organizados por Robert C. Martin, constituem um conjunto consistente de diretrizes para o projeto e programação orientada a objetos que pretendem auxiliar os projetistas de software, além dos programadores, na construção de sistemas estruturados para que sejam mais fáceis de manter e de ampliar com o passar do tempo.
O desenvolvimento de software é uma atividade inerentemente complexa, assim os custos relacionados a construção de sistemas grandes são usualmente altos. Torna-se imperativo, então, projetar sistemas que, além de agregar valor, possam ser utilizados por muito tempo, como única forma de compensar o investimento em sua construção. Assim, o projeto de um software deve se preocupar não apenas com as funcionalidades requeridas, mas também na sua manutenção e ampliação, evitando que seu envelhecimento dificulte ou impeça sua utilização.

Os princípios enunciados por Martin contém recomendações importantes que devem ser consideradas na construção; mas também são um guia para refatoração do código, além de se enquadrarem nas estratégias ágeis de construção de software.

Como visto, são cinco os princípios que formam o acrônimo SOLID:
·      Single Responsability Principle
·      Open-Closed Principle
·      Liskov's Substitution Principle
·      Interface Segregation Principle
·      Dependency Inversion Principle

Neste post será abordado o LSP ou Princípio da Substituição de Liskov.

LSP::Liskov's Substitution Principle

Os tipos derivados devem ser completamente substituíveis por seus tipos base.
O Liskov's Substitution Principle ou Princípio da Substituição de Liskov ou apenas LSP foi proposto por Barbara Liskov (e outros) em 1981, preocupando-se com as questões de abstração e manutenabilidade do software. Este princípio é formalmente enunciado como:
"Se para cada objeto o1 do tipo S existe um objeto o2 do tipo T tal que para todos os programas P definidos em termos de T, o comportamento de P é inalterado quando o1 é substituído por o2, então S é um subtipo de T."
Esta definição é mais simples do que parece. Considere um programa que utiliza um objeto o1 de um tipo determinado, por exemplo da classe S. Também considere um outro objeto o2, mas da classe T, sendo que S é uma subclasse de T (portanto T é um supertipo de S). A figura abaixo ilustra esta situação. O LSP significa que o programa deve funcionar corretamente quando o objeto o1 é substituído pelo objeto o2, pois este objeto o2 é um supertipo de o1.
Quando se afirma que o programa continua a funcionar corretamente, ou seja, que seu comportamento não se altera, não está se afirmando que os resultados produzidos por um objeto de um tipo sejam iguais aos produzidos por outros de suas subclasses, mas espera-se que as mesmas operações estejam disponíveis e que os resultados sejam do mesmo tipo.

Por isso o LSP também é conhecido como princípio da conformidade de tipo:
"Se S é um real subtipo de T, então S deve se conformar a T. Em outras palavras, um objeto de tipo S pode ser provido em qualquer contexto no qual um objeto do tipo T seja esperado, e a exatidão ainda será preservada quando qualquer operação de acesso do objeto for executada." (PAGE-JONES, 2001, pg.286)
Assim, num projeto orientado a objeto adequado e consistente, o tipo de cada classe deverá se conformar com o tipo de sua superclasse, de maneira que hierarquias de classes devem seguir este princípio, permitindo que o polimorfismo seja usado com comodidade e precisão. Assim sendo, as dependências do cliente em relação a classe podem ser substituídas por suas subclasses, sem que o cliente saiba da mudança.

O projeto de hierarquias de classes onde é garantido que as subclasses operem da mesma maneira que suas superclasses, isto é, funcionalidade diferente, mas dentro do comportamento esperado, é conhecida também como projeto por contrato (design by contract). Neste sentido a relação típica é um/is a que identifica as relações de herança deve ser repensada como é substituível por/is substitute for.

Um Exemplo

Considere como pode ser modelada a relação existente entre quadrados e retângulos; tal como círculos e elipses. A geometria euclidiana afirma que um todo quadrado é um retângulo; assim como os círculos são elipses. Do ponto de vista da programação orientada a objetos, como a pergunta "é do tipo?" é respondida satisfatoriamente, identificam-se relações típicas de herança entre retângulos (supertipo) e quadrado (subtipo), assim como elipses (supertipo) e círculos (subtipo), como ilustra a figura que segue.
Aqui existe uma situação que merece atenção. Se uma instância de Retangulo for substituída por uma instância de Quadrado, o programa poderá falhar se for esperado que a altura e largura variem livremente, pois no caso do Quadrado, existe uma invariante que altura=largura. A substituição inversa (um objeto Quadrado substituído por outro Retangulo) pode causar problemas pela mesma razão, se esperado que as dimensões sejam mantidas iguais. Nesta situação temos uma violação do LSP, pois o código se comporta diferentemente do que foi estabelecido pela geometria.

Tornar as classes imutáveis, de maneira que novos objetos consistentes sejam "fabricados" por demanda, é uma solução possível, como ilustrado pelas interfaces seguem.

public interface Retangulo {
    double getLargura();
    double getAltura();
    Retangulo comAltura(double a);
    Retangulo comLargura(double l);
}
public interface Quadrado extends Retangulo {
    Quadrado comTamanho(double t);
}

O mesmo problema não ocorreria com uma implementação típica de Elipse e Circulo, pois o comportamento do código permitiria respeitar as definições geométricas.

O exemplo enfatiza que um violação do LSP causa, geralmente, comportamento indefinido, principalmente quando a substituição é dinâmica. A dificuldade reside no fato de funcionar durante o desenvolvimento, mas provocar bugs aleatórios durante a produção.

Conclusões

O Princípio da Substituição de Liskov destaca que a validade de um modelo não é intrínseca e que depende de sua inserção e coerência com o contexto do cliente. Além disso, o comportamento de objetos de uma hierarquia deve ser o mesmo: um quadrado é um retângulo geometricamente, mas não se comporta como um! Já círculos e elipses exibem o mesmo comportamento! Então, concluímos que na orientação a objetos, a relação de herança pertence ao comportamento público dos objetos.

O cumprimento deste princípio requer atenção no projeto de hierarquias de classes usadas como substituições dinâmicas. Seu atendimento ajudar evitar: 
  • Comportamento indefinido decorrente de substituições entre supertipos e subtipos;
  • Esforço desproporcional para detecção e correção dos problemas decorrentes;
  • Problemas de fragilidade e rigidez!

Para Saber Mais


  • LARMAN, Craig. Utilizando UML e padrões: uma introdução à análise e ao projeto orientados à objetos e ao Processo Unificado. Porto Alegre: Bookman, 2007.
  • MARTIN, R. C.; et. al. Clean Code: a handbook of agile software craftsmanship. Boston: Pearson Education, 2009.
  • MARTIN, R. C. The Clean Coder. Upper Sadle River: Prentice-Hall, 2011.
  • MARTIN, R. C. Principles of OOD. Disponível em http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod, recuperado em 17/02/2017.
  • MARTIN, R. C. Get a SOLID start. Disponível em http://objectmentor.com, recuperado em 17/02/2017.
  • PAGE-JONES, Meilir. Fundamentos do Desenho Orientado a Objetos. São Paulo: Makron Books, 2001.
  • SOMMERVILLE, I. Software Engineering. 9th. Ed. Boston: Addison-Wesley, 2011.