[Pesquisar este blog]

segunda-feira, 20 de novembro de 2017

Java 9::O Console jshell

Estreia no Java uma nova interessante ferramenta de linha de comando denominada jshell, o console Java. Por meio dela é 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.

Este post é parte da série Java 9::Primeiras Impressões, que comenta as principais características da nova versão do Java.

REPL

A abreviatura REPL significa Read-Evaluate-Print-Loop, uma referência a consoles web e plug-ins de diversas linguagens, onde o usuário fornece uma expressão ou diretiva da linguagem ao console (leitura), a qual é processada imediatamente (avaliação), tendo seus resultados exibidos (impressão), retornando à situação inicial (loop) onde uma outra expressão ou diretiva pode ser interativamente fornecida.

Conforme as características próprias de cada um dos consoles que existem, os resultados de avaliações anteriores podem ou não estar disponíveis, possibilitando a acumulação de efeitos e o uso de tais ambientes tanto para experimentação de coisas simples, como para simulação de construções mais complexas, tudo de maneira bastante direta. Esta é a grande vantagem do uso dos consoles REPL.

Como veremos, o jshell permite tanto executar expressões simples, como definir variáveis, instanciar objetos e executar trechos relativamente sofisticados de código, tornando-se igualmente apropriado para o estudante e para o programador mais experiente.

Acionando o jshell

Em um prompt de comandos, console ou terminal, dependendo do seu sistema operacional, garanta que o path está corretamente ajustado. No MS Windows usualmente basta executar:

C:\Users\Jandl>path=C:\Program Files\Java\jdk-9\bin;%path%

No meu caso, o JDK da versão 9 está instalado em C:\Program Files\Java\jdk-9\. O jshell, e as demais ferramentas de linha de comando do JDK estão no subdiretório bin. Após o ajuste do path, basta acionar o comando jshell, como mostra a figura que segue.
Prompt de comando e o acionamento do jshell

Experimente algo simples, como somar dois valores inteiros, pressionando ENTER ao final:

Observe que a expressão “1 + 2” foi avaliada, de modo que o resultado foi armazenado na variável temporária denominada $1, a qual foi exibida. Fornecendo apenas o nome de tal variável, obteríamos o seu conteúdo.

Os ponto-e-vírgula, exigidos no código Java, podem ser omitidos, visto que o jshell, frequentemente, é capaz de adicioná-los.

Testando diretivas e declarações

Diretivas simples, de repetição ou decisão, também podem ser executadas, bastando digitar o código que se deseja avaliar, pressionado ENTER para seu processamento:

Se desejado, variáveis de qualquer tipo válido podem ser declaradas:

O comando /vars permite visualizar todas as variáveis válidas previamente definidas, assim como seus tipos e conteúdos:

Eventualmente, o fragmento de código que se deseja testar fica melhor organizado se escrito em várias linhas. Para isto basta digitar uma linha e, com SHIFT+ENTER, continuar na linha seguinte, como na figura abaixo:

Histórico de comandos: com as setas UP e DOWN do teclado é possível navegar pelas lista de fragmentos anteriormente processados pelo jshell. Após escolher o fragmento desejado, basta um ENTER para executá-lo.

Caso você deseje modificar um comando anterior, ou corrigir uma linha com código errado, basta usar as setas UP e DOWN do teclado para selecionar tal linha, usando as setas LEFT e RIGHT, HOME e END para navegar na linha. Se for um trecho de várias linhas, é necessário repetir todas as linhas corretas, mais as corrigidas, na sequência desejada.

O comando /list lista todos os fragmentos de código válido que foram avaliados.

Use o comando /<id> para executar o fragmento identificado pelo id correspondente ou /! para repetir a execução do último fragmento processado. Lembre-se apenas que os vários fragmentos utilizam as variáveis definidas de maneira global, ou seja, todos os fragmentos têm acesso à todas as variáveis, propagando seus efeitos por meio delas.

Características avançadas

Também é possível instanciar objetos, declarar e inicializar arrays:

Como esperado, objetos de qualquer tipo, assim como arrays, podem ser usados em fragmentos de código, como este que segue, que utiliza o objeto StringBuilder e o array de String declarados acima:

Com o jshell é possível declarar-se métodos para uso independente ou em outros fragmentos.

Até mesmo novos tipos (classes, interfaces ou enumerações) podem ser definidos.

Desta maneira, objetos dos tipos existentes ou definidos no ambiente jshell podem ser instanciados e utilizados.




Comandos do jshell

O jshell suporta a execução de vários comandos, todos precedidos por /, que podem facilitar sua utilização. A tabela que segue mostra os principais comandos em suas formas mais simples:

Comando
Efeito
/list
Lista os fragmentos válidos fornecidos.
/edit <id>
Edita o fragmento identificado por <id>.
/drop <id>
Remove o fragmento identificado por <id>.
/save <fileName>
Salva os fragmentos de código no arquivo indicado.
/open <fileName>
Abre o arquivo contendo fragmentos de código.
/vars
Lista as variáveis declaradas e seus valores.
/methods
Lista os métodos declarados e suas assinaturas.
/types
Lista os tipos declarados.
/history
Lista o histórico do código digitado.
/<id>
Executa o fragmento identificado por <id>.
/!
Reexecuta o último fragmento válido.
/help
Exibe informação sobre o jshell e seus comandos
/exit
Finaliza o jshell.

Considerações Finais

O jshell é, realmente, uma ótima ferramenta, pois alia a simplicidade e conveniência de uso com muita flexibilidade. Como permite testar declarações, diretivas e expressões, incluindo a definição de métodos independentes e de tipos completos, é muito útil para que deseja estudar Java, pois permite praticar a construção de código limitando-se ao essencial. Mesmo programadores mais experientes podem se beneficiar do seu uso para simular e testar fragmentos de código de maneira rápida.

É isso!
/exit

Para saber mais

domingo, 19 de novembro de 2017

Java 9::Métodos Privados em Interfaces

Uma das novidades da versão 9 do Java são os métodos com visibilidade privada nas interfaces. Este novo elemento, acrescido aos métodos default e estáticos incluídos na versão 8, vem para completar as alternativas construtivas nas interfaces.

Este post é parte da série Java 9::Primeiras Impressões, que comenta as principais características da nova versão do Java.

Interfaces

Uma interface é a definição de conjunto de elementos visíveis, ou seja, públicos, dos objetos de uma classe. Quando a implementação de uma classe contém todas as operações de conjunto particular, dizemos que tal classe realiza tal interface, ou seja, esta classe atende as especificações ditadas pela interface implementada.

Uma interface Java é algo simples como:

package jandl.j9;
public interface Adder {
void add(Number value);
Number getTotal();
void setTotal(Number initValue);
void reset();
}

Implicitamente, todas as operações de uma interface são, por padrão, públicas e abstratas, ou seja, todas poderiam receber o especificador de acesso public e o modificador abstract em suas declarações.

Desta maneira, uma interface é como um contrato, que dita quais operações devem estar disponíveis para obter-se um conjunto específico de funcionalidade (e de interoperabilidade, portanto). Isto é particularmente importante, pois a questão aqui é como os objetos podem ser utilizados, e não como foram implementados.

A classe AdderImpl que segue é uma implementação possível da interface Adder.

package jandl.j9;
public class AdderImpl implements Adder {
private double total;
public AdderImpl() { reset(); }
public AdderImpl(Number value) { setTotal(value); }
@Override
public void add(Number value) { total = total + ((Number) value).doubleValue(); }
@Override
public Number getTotal() { return new Double(total); }
@Override
public void setTotal(Number initValue) { total = ((Number) initValue).doubleValue(); }
@Override
public void reset() { total = 0; }
}

Desta maneira, classes pertencentes a diferentes hierarquias de objetos, mas que implementam uma interface comum, podem, polimorficamente, serem tratadas como de um mesmo tipo que corresponde a tal interface.

A modelagem de sistemas por meio da definição de interfaces é uma prática reconhecidamente boa, também conhecida como programação por contratos.

Assim, o uso de interfaces é particularmente importante no projeto de sistemas, o que também é enfatizado pelos princípios SOLID (discutidos numa série de posts iniciados por Os Princípios SOLID – Parte I).

Evolução de interfaces

Apesar das muitas conveniências no projeto e uso de interfaces, surgem situações onde uma interface necessita ser alterada. Aí o cenário deixa de positivo. Modificar uma interface, no sentido de alterar a assinatura de um dos métodos presentes ou acrescentar novas operações cria a exigência de modificar ou completar todas as classes que realizam tal interface, numa constrangedora propagação das alterações realizadas.

A partir do Java 8 se tornou possível realizar alterações numa interface existente, mantendo a compatibilidade com suas versões anteriores, isto é, sem propagar as alterações, simplificando muito o trabalho de manutenção do código. Para isto foram introduzidos os métodos default e estáticos às interfaces.

Os métodos default são aqueles declarados com o especificador de visibilidade public e com o novo modificador default. Além disso, tais métodos devem ser implementados na própria interface, pois não são abstratos, o que evita a propagação da modificação.

A interface Adder poderia receber um novo método, como segue, sem que as classes que a realizam sejam afetadas.

package jandl.j9;
public interface Adder {
:
// método default, cuja implementação é adicionada na interface modificada
default void add(Adder adder) {  add(adder.getTotal()); }
}

De forma análoga, os métodos estáticos são aqueles declarados com o especificador de visibilidade public e com o conhecido modificador static, o que também exige sua própria interface, pois, como qualquer elemento estático, pertencem a seu tipo e não a suas instâncias. Da mesma forma sua adição não propaga qualquer efeito nas classes que já realizam a interface modificada.

package jandl.j9;
public interface Adder {
:
// método estático, cuja implementação é adicionada na interface modificada
static void add(Adder adder, List<Number> list) {
    list.stream().forEach((value)->{ adder.add(value); });
}
}

As duas alternativas, dos métodos default e dos métodos estáticos, possibilitam a evolução de interfaces existentes, sem propagação de alterações e com garantia da compatibilidade binária com suas versões antigas.

Métodos privados em interfaces

A adição da possibilidade de declarar métodos privados em interfaces, embora estranha à primeira vista, é um complemento para auxiliar o programador no uso dos métodos default e métodos estáticos nas interfaces.

Os novos métodos privados são declarados com o especificador de visibilidade private e, opcionalmente, com o modificar static. Como qualquer membro privado, só podem ser acessados pelos demais membros do seu tipo.

Sendo assim, o uso de métodos privados, estáticos ou não, numa interface tem como objetivo permitir que o programador defina operações auxiliares para os demais métodos default e estáticos públicos, sem expor tal operação.

Na nova implementação da interface Adder, um método privado estático add(Adder, Number[]) provê o serviço de adição para três outros métodos da interface.

package jandl.j9;
import java.util.List;

public interface Adder {
void add(Number value);
Number getTotal();
void setTotal(Number initValue);
void reset();
default void add(Adder adder) { add(adder.getTotal()); }
// métodos default e estáticos que utilizam método privado desta interface
default void add(Number[] array) { add(this, array); }
default void add(List<Number> list) { add(this, list.toArray(new Number[0])); }
static void add(Adder adder, List<Number> list) {
   add(adder, list.toArray(new Number[0]));
}
// método estático privado que prove serviço para os demais
private static void add(Adder adder, Number[] array) {
   for(int i=0; i<array.length;i++) { adder.add(array[i]);
}
}
}

Considerações Finais

As interfaces na versão 9 Java suportam, então, uma variada combinação de especificadores e modificadores, como sumarizado na tabela que segue:

Especificador
Modificador
Situação
Nenhum
Nenhum
Declaração válida de método, implicitamente, público e abstrato.
Nenhum
abstract
Declaração válida de método abstrato, implicitamente público.
Nenhum
default
Declaração válida de método default, implicitamente público.
Nenhum
static
Declaração válida de método estático, implicitamente público.
public
Nenhum
Declaração válida de método público, implicitamente abstrato.
public
abstract
Declaração válida explícita de método público e abstrato.
public
default
Declaração válida explícita de método público e default.
public
static
Declaração válida explícita de método público e estático.
private
Nenhum
Declaração válida explícita de método privado.
private
abstract
Declaração inválida. Erro de compilação.
private
default
Declaração inválida. Erro de compilação.
private
static
Declaração válida explícita de método privado e estático.

Os métodos privados não são herdados por subinterfaces ou implementações das classes.
Finalmente, apesar da utilidade exemplificada, de permitir modificações em interfaces existentes sem a propagação de efeitos colaterais indesejados, cabe destacar que, se uma interface existente deve ser modificada, isto indica duas situações: alterações nas regras de negócio da aplicação ou identificação de falhas no projeto do software. Enquanto a primeira razão pode ser imprevisível e inevitável; a segunda reforça o cuidado necessário com o projeto de qualquer software.

A implementação de métodos (default ou estáticos, públicos ou privados) é, do ponto de vista da Orientação a Objetos, uma violação dos seus propósitos. Se é necessária uma implementação parcial de uma interface, é mais adequada a construção de uma classe abstrata contendo a codificação destas operações, mantendo a interface como uma classe abstrata pura (sem implementação de qualquer operação).

Reforço que isto não é um defeito do Java, mas, acredito, alternativas possíveis na plataforma para solução de problemas envolvendo interfaces.

Assim, muito cuidado no projeto de suas interfaces, que devem conter apenas as operações minimamente necessárias (tal como defendido pelo princípio de segregação das interfaces ou ISP dos princípios SOLID). Qualquer coisa diferente disso, pode não ser adequada, mesmo com todas as possibilidades ofertadas pela plataforma Java.

Para saber mais




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.

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.

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:
  • 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 multiresolução [JEP 251]
  • I/O para imagens TIFF [JEP 262]
  • Suporte para Unicode 7.0 e 8.0 [JEP 227, 267]

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]


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.

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.