Alguns dias depois agradeci a Microsoft por me ajudar com as melhorias do Flash Player. Hoje li no site da Adobe que o Flash Player 11 tera suporte a H264 encode para transmiss?o de c?mera.
Isso mesmo, ap?s 9 anos da adi??a do codec Sorenson Spark, teremos no FP11 op??o de transmitir nossas c?meras com H264 codificado …
O sonho virou realidade
Definindo a Interface Orientation em aplicativos para IPad
Com o lançamento do iPad pela Apple, e a necessidade de adaptar os aplicativos existentes para o iPad, os desenvolvedores encontraram alguns desafios que antes não eram percebidos quando desenvolvendo para iPhone ou iPod Touch.
Todos os devices da apple possuem o acelerômetro e através dele nossa iteração com os dispositivos e aplicativos mudou completamente, saindo de algo “estático” para aplicativos que reagem conforme você posiciona o aparelho.
Essa funcionalidade já quase não é mais percebida nos aplicativos para iPhone, pois quase todos os aplicativos estão preparados para a visualização em Landscape e Portrait. Porém, devido ao tamanho da tela desse device, não existe grande diferença nessas visualizações.
Arrisco em dizer que grande parte das vezes em que rotacionamos o iPhone para Landscape é para ter uma maior área de visualização ou melhor acesso ao teclado para digitar textos mais longos.
Porém, com o iPad essa funcionalidade foi renovada. Alguns fatores como tamanho da tela influenciaram nessa mudança, afinal, com tanto espaço as informações podem ser melhores dispostas para o usuário.
Essa mudança pode ser percebida em grande parte dos aplicativos existentes no iPad, e sua disposição pode mudar da acordo com a necessidade.
Percebam, por exemplo, que para o aplicativo Settings não existe diferenciação entre Portrait e Landscape:
Já, outros aplicativos como Mail e YouTube possuem visualizações bem diferentes de acordo com a orienteção do device:
Mas, e quando estamos desenvolvendo aplicativos mais simples, que não irão possuir tantas formas de visualização, ou mesmo quando estamos aprendendo a trabalhar com esse controle de visualização, como podemos permitir ou bloquear que o aplicativo mude sua visualização?
Quando não estamos criando controles avançados de tratamento dessas visualizações, podemos ter alguns resultados indesejáveis quando o iPad é rotacionado, e toda a UI ser afetada, impactando a UX para o usuário.
Portanto, uma forma simples de permitir, por exemplo, que a aplicação não mude a orientação para Landscape é através do controller da view.
No controller podemos encontrar o método
|
1
|
- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) interfaceOrientation
|
Esse método sempre retorna YES, o que significa que ele sempre rotaciona a view de acordo com a orientação.
Podemos permitir quatro tipos de orientação:
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
Cada orientação segue o posicionamento do device:
Então, caso nossa view deva responder somente ao modo Portrait e LandscapeLeft, devemos definir o método como abaixo:
|
1
2 3 4 5 6 7 |
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation==UIInterfaceOrientationPortrait || interfaceOrientation==UIInterfaceOrientationLandscapeLeft); } |
Qualquer combinação é válida, e caso todas sejam permitidas, devemos manter o método inalterado, conforme abaixo:
|
1
2 3 4 5 |
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES; } |
Mais detalhes:
O screencast abaixo demostra a criação de um aplicativo simples, com uma View, e como tratar a InterfaceOrientation com mais detalhes. Ao final do Screencast você deverá conseguir alterar os modos de rotação da forma que desejar, e assim aplicar o conceito em outras aplicações.
O código fonte gerado através do screencast pode ser baixado através do link: OrientationTest(zip)
Deixe seu comentário ou dúvidas.
Artur Fabri
@arturfabri
Generics, como Scala trata o Problema de Generalização de Classes
Generics em Java = Type Parameters em Scala
A motivação para o uso de generics em Java é bem conhecida, e Scala resolve o mesmo problema, dando um nome diferente para a solução: Type Parameters. Podemos chamar também de Generic Types, o significado é o mesmo.
Criando uma lista de tipo específico
Vamos implementar uma lista ligada para valores String. Tente fazer você mesmo, implementando um classe para ser a célula da lista, e uma para ser a lista em si. A lista deve também conter métodos para adicionar, remover e buscar um elemento específico dado o índice.
Em um paradigma mais funcional por assim dizer, você pode acabar em uma implementação para a célula, parecida com a minha:
|
1
2 3 4 5 6 7 8 |
|
1
2 3 4 5 6 7 8 |
package br.com.dclick.typeparameters
class StringEmptyCell extends StringListCell{ |
|
1
2 3 4 5 6 7 8 9 |
Implementação diferente do usual, né? ![]()
Repare que criei dois tipos de célula: uma célula que está sempre vazia, e uma célula que contém de fato o valor a ser guardado. Fiz isso pois em Scala não existe (ou a idéia é não utilizar) null, assim a checagem é feita por células vazias e não por null. Como Scala utiliza código Java, é possível passar e receber valores nulos. Código puramente em Scala estão praticamente livres de NullPointerException, justamente por essa restrição com valores nulos. Vamos ver logo mais um caso em que é possível obter tal erro.
Em Scala uma das alternativas para execução de funções que devolvem null como resultado para certas circunstâncias, é o Option. Option, como o próprio nome sugere, se trata de um opção por devolver, ou não um valor. No caso de se devolver algum valor, deve-se devolver um Some contendo o valor. No caso de não se devolver o valor, deve-se devolver um None. Ambos são Case Classes, portanto não necessitam da palavra new, e possuem todas a propriedades que vimos em um post anterior.
Pensando em todas essas características da linguagem, cheguei nesta implementação para a lista, e usei uma abordagem recursiva. Vou mostrar o resultado:
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
package br.com.dclick.typeparameters
class StringList { |
Outra implementação não muito convencional… mas mesmo assim não deixa de ser clara. Mesmo não send o foco do post, vou dar uma breve explicação: na lista guardo o último elemento, assim sei onde devo adicionar o novo elemento; na função add verifico se o início da lista está vazio para adicionar, ou se devo adicionar no final; na função remove se a célula passada estiver vazia devolvo uma nova célula vazia, se o índice atual for zero, ou seja, queremos remover o atual, devolvo o próximo, e como caso padrão seto o próximo item como sendo o resto da lista com o item correto removido (Repare que não funciona para remover o primeiro item, mas não é esse o intuito mesmo :p); na função get verificao se a célula está vazia, logo não devolvo nada, e itero no resto dos itens de maneira recursiva no caso padrão.
Para termos certeza que nossa lista está funcionando corretamente, criei o seguinte teste unitário usando JUnit4, que para utilizá-lo é muito fácil, como foi visto no post anterior.
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package br.com.dclick
import br.com.dclick.typeparameters._ import org.junit._ import Assert._ class ListTest { @Test |
Rode o teste para ter certeza de que está tudo rodando.
Generalizando nossa Lista
Agora imagine que queremos fazer uma lista de inteiros ao invés de Strings. O problema é o mesmo que temos em Java. Se generalizarmos nossa lista para guardar AnyRef do Scala ao invés de Strings (em Java faríamos o mesmo com Object), teremos o problema de ter que fazer cast do resultado em toda chamada para nossa lista. Para isso que existem os Generic Types.
Se você reparou como utilizamos Option, tivemos que passar um tipo de parâmetro para ele, que no nosso caso foi String, dessa forma quem utiliza o resultado do método, sabe que está lidando com Strings e portanto não precisa fazer o cast do resultado. Vamos implementar a mesma funcionalidade em nossa lista. Para isso temos que modificar nossas células e nossa lista, de maneira que recebam o tipo como parâmetro. Segue o código da lista modificada:
|
1
2 3 4 5 6 7 8 |
package br.com.dclick.typeparameters
abstract class ListCell[T] { |
|
1
2 3 4 5 6 7 8 9 |
package br.com.dclick.typeparameters
class ValueCell[T](v: T) extends ListCell[T]{ |
|
1
2 3 4 5 6 7 8 |
package br.com.dclick.typeparameters
class EmptyCell[T] extends ListCell[T]{ |
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
package br.com.dclick.typeparameters
class TypeList[T] { |
Note que a implementação é praticamente a mesma, a única coisa diferente é o tipo do valor passado para guardar na lista. Repare que para quem usa a lista, basta adicionar um tipo no momento de criação da mesma, por isso em nosso teste mude apenas a linha que instancia uma nova lista, e rode o teste.
|
1
|
Crie um outro teste com um tipo de valor diferente e veja que funciona da mesma maneira que no Java.
Restrições de Tipos Genéricos
Nosso tipo T pode ser qualquer coisa. Existe em Scala uma sintaxe própria inclusive para inicializar uma varável de um tipo genérico, basta utilizar um ‘_‘. Dessa forma você está explicitando que sua variável pode ou não ter um valor do tipo genérico, afinal não tem como saber se o tipo será um primitivo ou não. Este é o caso em que estamos sujeito ao NullPointerException que citei no começo do post: para definir uma variável em Scala é necessário iniciá-la, e no caso de tipos genéricos, como não possível iniciá-los, as variáveis possuem valor nulo (‘_‘), logo geram código que pode lançar NullPointerException.
Com nosso tipo genérico podendo ser qualquer coisa, não conseguiríamos, por exemplo, ordenar nossa lista, afinal nem tudo é ordenável. A solução em Java é a mesma, mas a sintaxe em Scala me parece bem mais amigável. O que é necessário fazer, é dizer que o seu tipo obrigatoriamente extende de alguma classe determinada. No caso de querermos uma lista ordenada, basta obrigarmos o tipo passado extender de Comparable (no Java), ou Ordered (em Scala). Ordered extende Comparable, sobrescrevendo os operadores de comparação básico (>, <, >=, <=) e utilizando o método de comparação disponível em Comparable (compareTo).
Vamos mudar o parâmetro de nossa lista para obter tal comportamento. Para isto, basta mudar a assinatura da classe com seguinte código:
|
1
|
Estou usando Comparable, pois não teremos erros de compilação usando String como parâmetro. Porém em Scala é possível dizer que um parâmetro poder ser convertido para o tipo definido na restrição. Esse tipo de restrição é chamado de View Bound e Scala já implementa a conversão implícita para os tipos primitivos e String, portanto se mudarmos nossa classe para:
|
1
|
class TypeList[T <% Ordered[T]] {
|
nosso código ainda compila, e nossos testes funcionam, o que é o mais importante
.
Invariância e Covariância
Pensando em termos de orientação a objetos, se definirmos um lista TypeList[String], esta lista deve ser subtipo de TypeList[AnyRef]? Parece uma pergunta simples de se responder, mas as respostas tem suas implicações. Se a resposta é não, então estamos dizendo que quando criarmos uma lista de Strings, só faremos referência a essa lista sabendo que é do tipo String, e que portanto só adicionamos valores String na lista. Se a resposta é não, então estamos dizendo que ao criarmos um lista de Strings, podemos referenciá-la como uma lista de AnyRef e portanto conseguimos adicionar qualquer valor em nossa lista. O primeiro caso é dito invariante, e o segundo caso é dito Covariante.
Em Scala os tipos são por padrão sempre invariantes. Diferente do Java que é covariante.
Quando os tipos são covariantes, temos o problema de lidar com tipos diferentes dos esperados. Java resolve esse problema fazendo a verificação em tempo de execução, no momento da atribuição de um tipo. Em Scala o problema é resolvido de maneira estática impedindo o código de compilar. Ainda assim é possível estabelecer tipo covariantes em Scala. Para nossa lista, basta mudar a assinatura, com o operador +:
|
1
|
class TypeList[+T] {
|
Fazendo isso, seremos obrigados a mudar em todas as classes que usem T, por exemplo, nossas células.
Mas com isso surge um problema: como que o compilador irá saber tratar o tipo T de maneira que este possa ser de algum supertipo do mesmo? Para isso podemos definir nosso tipo T, como um limitante inferior. Basta usarmos a sintaxe simétrica a de restrições de limitantes superiores:
|
1
|
A >: T
|
Assim estamos dizendo que A é supertipo de T, e portanto podemos usá-lo em nossas funções sem que o compilador reclame.
Também podemos definir um range para nossas restrições combinando os operadores, por exemplo:
|
1
|
A >: B <: C
|
Objects
Scala não permite que usemos parâmetros em objects, por isso mesmo nossa célula vazia teve que definir um tipo para o parâmetro.
Para definirmos um object como super classe de uma classe que necessite de parâmetros, precisamos que a classe pai seja cocariante, e em nosso object, basta extendermos tal classe com o tipo Nothing. O tipo Nothing é subclasse de todos os tipos em Scala, portanto é o menor limitante inferior. Com isso conseguimos definir um object que extende uma classe que precisa de parâmetros.
Antes de falarmos de listas em Scala, ainda falta falarmos de tuplas e Functions. Veremos isso em um screencast que será divulgado em breve.
Espero que a série tenha andado bem até aqui, e que as informações que estou publicando sejam úteis a muitas pessoas. Lembrando que todos os tipos de comentários são bem vindos: críticas, dúvidas e sugestôes.
Por @Gust4v0_H4xx0r
Case Classes com Scala
Vimos que Scala na verdade é uma linguagem puramente orientada a objetos, com conceitos de classe, instância, heirarquia e polimorfismo como Java por exemplo. Mas Scala traz também alguns conceitos próprios muito interessantes, que é o caso de Case Classes.
Case Classes são um caso de classes com regras pré-definidas em tempo de compilação, de maneira que se permita executar algumas outras operações sobre tais classes facilitando algumas modelagens, e ajudando na aplicação de algumas boas práticas. Vamos estudar mais a fundo o conceito de Case Classes nesse post. Usei como referência o Scala By Example do Martin Odresky, o qual citei em posts anteriores.
Para acompanhar o post, é muito recomendado alguma IDE de desenvolvimento de scala. Estou usando o scala-ide plugin para o Eclipse, que pode ser encontrado em http://www.scala-ide.org/.
Motivação para Case Classes
Eu gostei bastante do exemplo disponível no livro, por isso vou seguir a mesma idéia nesse post só que com as minhas palavras do que eu consegui aprender sobre assunto pesquisando um pouco mais na internet, e citando também as dificuldades que enfrentei.
Imagine que queremos modelar um interpretador de expressões matemáticas que são descritas em objetos do nosso domínio. Por exemplo, queremos interpretar o resultado de 1 + (3 + 7), e representando em nosso modelo, queremos obter algo como:
Uma maneira orientada a objetos de se implemetar tal domínio poderia ser algo como:
|
1
2 3 4 5 6 7 8 9 |
trait Expr {
def eval: Int } class Number(n: Int) extends Expr { def eval: Int = n } class Sum(e1: Expr, e2: Expr) extends Expr { def eval: Int = e1.eval + e2.eval } |
A implementação é bastante limpa e nosso interpretador seria mais simples ainda:
|
1
2 3 4 |
object Inter {
def inter(e: Expr) = e.eval } |
Se você assistiu o screencast sobre integração com Java, e JUnit rodando testes de Scala, você pode fazer um teste para verificar que nosso interpretador está funcionando corretamente:
|
1
2 3 4 5 6 7 8 9 10 11 12 |
Para essa modelagem, quando quisermos adicionar uma nova operação que pode ser interpretada, basta criar uma nova classe que extends Expr e pronto! Nada mais precisa ser alterado em nosso código, e nosso interpretador continuará funcionando.
Agora imagine que antes de executar a expressão, queremos imprimir no console de um jeito amigável a expressão matemática que está sendo executada. Nesse caso, de acordo com nossa modelagem, teríamos que definir em nosso Expr uma função print, para que todas as suas implementações definam o comportamento de tal função. Para isso temos que alterar o código em todas as classes que já existem, o que pode ser ruim dependendo do nosso sistema.
Aplicando Case Classes
Em Scala existe a definição de Case Class. Para criar uma Case Class basta adicionar o modificador case antes da definição da classe. Em nosso exemplo faça as seguintes mudanças:
|
1
2 |
Case classes seguem as seguintes regras:
1 – Possuem um construtor definido exatamente com o mesmo nome definido na classe. Em nosso exemplo:
Dessa forma é possível escrever nossa expressão 1 + (3 + 7) da seguinte forma:
Substitua no teste para ter certeza do funcionamento.
2 – Os métodos toString, equals e hashCode já estão implementados seguindo a definição da classe com seus atributos. Para nosso exemplo com Number, esses métodos já levam em consideração o atributo n, deixando de comparar os objetos por endereço de memória. Os operadores == e != também já estão definidos em função do equals.
3 – Todos os atributos passados no construtor possuem os métodos de acesso público já definidos. Em nosso exemplo, Number e Sum já possuem os seguintes métodos:
|
1
|
def n: Int
|
|
1
|
def e1: Expr, e2: Expr
|
Fato importante é que tais classes se tornam imutáveis, ou seja, não existe um método para alterar o valor dos atributos, e nem é possível definí-los.
4 – Case Classes podem ser usadas para pattern matching, que é uma operação disponível em Scala e que veremos agora.
Match, não Switch
Vimos que a classe base do Scala é o Any, e que todos objetos podem ser tratados como tal. Any define uma função que permite verificar o tipo de classe que está sendo tratado e tomar a atitude correspondente. Vamos ao exemplo que explica melhor o comportamento. Em nosso object Inter, mude a função inter para o seguinte:
|
1
2 3 4 5 |
Rode nosso teste novamente e verifique que está tudo correto.
Para entender o que está acontecendo: estamos dizendo que um dos casos esperados é o caso em que e é do tipo Number, que recebe um parâmetro que chamamos de n. Note que não precisamos definir o tipo de n, pois o compilador consegue inferir baseado na definição do construtor de Number. Feito isso, n está disponível no contexto do case atual. Para nosso caso com Number, precisamos apenas devolver o valor de n.
No segundo caso, estamos esperando uma Expr do tipo Sum, que recebe dois parâmetros e que baseado no construtor definido na classe, sabemos que é do tipo Expr. Nesse caso devolvemos o inter de a somado ao inter de b.
Simples não?
Nessa nossa nova modelagem fica fácil adicionar uma nova funcionalidade ao comportamento das expressões, como por exemplo imprimir de maneira legível. Basta adicionar mais um método ao nosso interpretador. Claro que agora temos o problema adicionar um novo caso quando criarmos uma nova operação, mas a quantidade de código a ser implementada é consideravelmente menor.
Match é definida como uma função como qualquer outra em Scala, portanto pode ser atribuída a variáveis, ser devolvida como resultado e ser passada como parâmetro para outras funções. Pode-se também criar um bloco como o seguinte:
|
1
|
e usá-lo como uma função anônima. Dessa forma será criado uma função match para tratar com seus casos definidos no bloco.
Exceptions
Veremos Exceptions mais para frente, mas perceba como funciona o bloco try/catch em Scala, e veja se há alguma semelhança com o que acamos de ver:
|
1
2 3 4 5 6 7 |
try {
println } catch { case npe: NullPointerException => print(npe.getMessage) case ioe: IOException => print(ioe.getMessage) case e: Exception => print(e.getMessage) } |
Pois bem, catch possui o mesmo comportamento de match para execeções.
Exercício
Vou me basear no exercício disponível no livro do Martin.
Considere que queremos implementar uma árvore binária ordenada de inteiros. E tome a seguinte implementação como base:
|
1
|
abstract class IntTree
|
|
1
|
case object EmptyTree extends IntTree {}
|
|
1
|
case class Node(elem: Int, left: IntTree, right: IntTree) extends IntTree {}
|
Agora complete a seguinte implementação dentro de IntTree:
|
1
2 3 4 5 6 7 8 9 10 11 12 |
abstract class IntTree {
def contains (t: IntTree, v: Int): Boolean = |
Lembre-se que Node é imutável, e que é obrigatório usar Case Classes. Boa sorte
!
A reposta está no screencast a seguir, junto com a explicação:
Aprendendo Scala – Parte 2
Funções em Scala
Este post é a continuação do primeiro post sobre Scala onde eu explico como instalar e como entender o básico da sintaxe. Caso você não tenha visto ainda, vale a consulta rápida: primeiros passos com Scala.
Funções em Scala assim como valores comuns, podem ser passados como parâmetro, armazenados em variáveis e até mesmo serem devolvidas como resultado de outras funções. Funções que recebem outras funções como parâmetro, ou devolvem alguma função como resultado são chamadas de ‘higher-order functions’. Tais funções nos permite diferentes tipos de refatorações e abstrações de código, quebrando bastante o paradigma de programação orientada a objeto, por isso exigem um bom entendimento de seu funcionamento para aproveitar bem da flexibilidade que Scala disponibiliza.
Implementando Higher-Order Functions
Vamos ver um exemplo de função que recebe outra função como parâmetro. Abra o terminal do Scala e como exemplo vamos implementar uma função que calcula a soma entre dois números passados:
|
1
|
scala> def somaEntre(a: Int, b: Int) = if (a > b) 0 else a + somaEntre(a+1, b)
|
Tente compilar o código acima e perceba o erro que irá aparecer no console: “5: error: recursive method somaEntre needs result type“. Aqui vale ressaltar que apesar de parecer que Scala não tem tipos bem definidos, o que acontece na verdade é que os tipos podem ser inferidos pelo compilador. Neste caso não estamos dando nenhuma pista para o compilador de qual o tipo que somaEntre devolve, logo ele tenta associar o tipo como Unit (visto no post anterior) e obtem um erro de sintática, pois o valor devolvido no if é do tipo Int. Para que o código compile basta adicionar o tipo ao valor que a função devolve:
|
1
2 3 4 5 |
scala> def somaEntre(a: Int, b: Int):Int = if (a > b) 0 else a + somaEntre(a+1, b)
somaEntre: (a: Int,b: Int)Int scala> somaEntre(1, 10) |
Agora imagine que ao invés da soma, queremos calcular a soma dos quadrados dos números entre os números passados. Para isso poderíamos implementar as seguintes funções:
|
1
2 3 4 5 6 7 8 |
scala> def quadrado(x: Int) = x * x
quadrado: (x: Int)Int scala> def somaQuadradosEntre(a: Int, b: Int):Int = if (a > b) 0 else quadrado(a) + somaQuadradosEntre(a+1, b) scala> somaQuadradosEntre(1, 10) |
Acredito que tenha ficado claro o padrão seguido em ambas as funções, o que podemos fazer agora é refatorar o código de uma maneira mais funcional por assim dizer. Ao invés de criar uma função diferente para somar os valores entre os dois números passados, vamos usar a mesma funçao de maneira que agora ela receba a função que deve ser executada com o número antes de ser somado ao total:
|
1
2 |
scala> def somaEntreComF(a: Int, b: Int, f: Int => Int): Int = if(a > b) 0 else f(a) + somaEntreComF(a+1, b, f)
somaEntreComF: (a: Int,b: Int,f: (Int) => Int)Int |
Dessa forma conseguimos passar uma função que será aplicada aos números entre a e b antes de somá-los, por exemplo para o segundo caso: quadrado.
|
1
2 |
scala> somaEntreComF(1, 10, quadrado)
res2: Int = 385 |
Bom, só que queremos que o comportamento antigo seja mantido, onde era devolvido apenas a soma dos valores sem que se aplique nenhuma função. Nesse caso basta implementarmos uma função bem conhecida nas linguagens funcionais, ‘id’:
|
1
2 3 4 5 |
scala> def id(x: Int) = x
id: (x: Int)Int scala> somaEntreComF(1, 10, id) |
Só para se ter uma idéia da flexibilidade que conseguimos com Scala, vamos supor que queremos poder escolher se calcularemos a soma ou a subtração dos valores entre os dois passados. Antes de olhar uma maneira de fazer, tente implementar do seu jeito. A resposta está no fim do post (1).
Funções anônimas
Existe um problema com a função id que fica bem claro quando queremos utilizá-la em outros lugares. Com a função definida da maneira que definimos tivemos que fixar um tipo, no caso Int, e se fomos usá-la em um lugar como String por exemplo, não será possível. Poderíamos definir uma função id com o tipo Any, mas neste caso não conseguiríamos utilizá-la em nosso exemplo, pois a operação de + não está definida em Any.
Para facilitar nossa vida, e para evitar a definição de funções que serão pequenas e para casos específicos, existe um açucar sintático em Scala chamado de funções anônimas. Como o próprio nome diz, nada mais são do que funções sem um nome definido. Em nosso exemplo não é necessário criar a função id se fizermos a seguinte chamada:
|
1
2 |
scala> somaEntreComF(1, 10, (x: Int) => x)
res6: Int = 55 |
Repare que a sintaxe é exatamente a mesma de quando definimos uma função da maneira convencional, apenas não explicitamos um nome para ela. Podemos usar a mesma idéia inclusive para o segundo exemplo com a soma dos quadrados:
|
1
2 |
scala> somaEntreComF(1, 10, (x: Int) => x * x)
res7: Int = 385 |
Como exercício, pegue o código que você criou no primeiro exercício e faça a chamada apenas utilizando funções anônimas, sem que seja preciso definir por exemplos uma função soma ou multiplica para somar ou multiplicar os resultados. A resposta está no fim do post também (2).
Mas lembrando que em Scala o compilador tenta sempre ser nosso amigo
, temos algumas definições na assinatura de somaEntreComF que garantem que a função passada como parâmetro tem que receber um Int e devolver outro. Portanto é possível saber o tipo do argumento passado em nossa função anônima. Outra consideração, é que como temos apenas um parâmetro na função passada, não precisamos dos () para explicitar o parâmetro. Ficamos com uma chamada bem mais limpa, tanto para a função id como quadrado:
|
1
2 3 4 5 |
scala> somaEntreComF(1, 10, x => x)
res9: Int = 55 scala> somaEntreComF(1, 10, x => x * x) |
Faça o mesmo com nosso exercício e remova os tipos dos argumentos em uma chamada para a função, a resposta está no final do post (3).
Currying
Vimos funções que recebem outras funções como parâmetro, e vimos também que existe um outro tipo de higher-order functions, que são as funções que devolvem outra função como parâmetro. Para fazer o currying de parâmetros em funções precisamos utilizar esse tipo de funções. Vamos usar nosso exemplo implementado até aqui para entender melhor do que se trata.
Imagine que vamos fazer chamadas seguidas de nossa função somaEntreComF para intervalo de números diferentes, porém com o mesma função a ser aplicada entre os números. Teríamos algo como o seguinte:
|
1
2 3 4 5 |
scala> somaEntreComF(5, 15, x => x)
res13: Int = 110 scala> somaEntreComF(10, 20, x => x) |
Nesse caso estamos repetindo o parâmetro da função para ambas as chamadas, e talvez iremos perceber que iremos repetir muitas outras vezes. Para isso existe uma funcionalidade em Scala (não é exclusivo de Scala) que permite uma abstração maior sobre esse código. Ao invés de devolvermos o resultado da operação, vamos devolver uma função, que ao executada com os parâmetros que você precise no momento, lhe dê o resultado. Para isso basta implementar a seguinte função:
|
1
2 |
scala> def somaEntre(f: Int => Int): (Int, Int) => Int = { def retFunc(a: Int, b: Int): Int = if (a > b) 0 else f(a) + retFunc(a+1, b); retFunc }
somaEntre: (f: (Int) => Int)(Int, Int) => Int |
Definimos uma função somaEntre que recebe uma outra função como parâmetro que recebe um Int e devolve outro Int, e estamos dizendo que a função somaEntre devolve outra função que recebe dois parâmetros do tipo Int e devolve um Int. Na implementação de somaEntre estamos definindo um função que será devolvida, lembrando que funções são elementos de primeira classe em Scala, portanto podemos criá-los dentro de outras funções, e implementando a função que será devolvida da mesma maneira que nossa função somaEntreComF. A diferença, é que agora estamos fixando a função f que é executada sobre os números do intervalo. Tal técnica é chamada de currying.
Para aplicar nossa nova função podemos executar o seguinte trecho de código:
|
1
2 3 4 5 6 7 8 9 10 11 |
scala> def somaIdEntre = somaEntre(x => x)
somaIdEntre: (Int, Int) => Int scala> somaIdEntre(1, 10) scala> def somaQuadradoEntre = somaEntre(x => x * x) scala> somaQuadradoEntre(1, 10) |
Agora temos duas funções diferentes que recebem o intervalo de números para serem executadas, e aplicam a função específica de cada uma, só que dessa vez se aparecer uma outra maneira de tratar os números antes de somá-los, basta executar somaEntre passando a nova operação e a função nova já estará pronta sem que tenha que se definir sempre a nova operação para executar a soma.
Podemos executar as funções com currying sem definir uma outra função para armazená-la, e a sintaxe é bem intuitiva:
|
1
2 3 4 5 |
scala> somaEntre(x => x)(1, 10)
res17: Int = 55 scala> somaEntre(x => x * x)(1, 10) |
Isso é possível pois a execução das funções é feita da esquerda para a direita, portanto 1 e 10 são parâmetros passados para a função devolvida por somaEntre.
Como exercício implemente nossa função de exercício de maneira que seja feito o currying da função executada entre os números do intervalo. Para isso utilize nossa função definida neste último exemplo somaEntre. Resposta no final do post (4), e sim a resposta é meio feia, mas a idéia é entender o conceito
.
Próximos passos
No próximo post iremos tratar mais a fundo de classes e objetos. Para isso usaremos a IDE de Scala disponível como plugin do Eclipse que pode ser encontrada em http://www.scala-ide.org/.
É importante que a IDE esteja funcionando corretamente para facilitar a compreensão do comportamento e da organização de classes e objetos em Scala.
Respostas dos exercícios:
Resposta (1):
|
1
2 3 4 5 6 7 8 |
scala> def aplicaFEntreComG(f: (Int, Int) => Int, a: Int, b: Int, g: Int => Int): Int = if (a > b) 0 else f(g(a), aplicaFEntreComG(f, a+1, b, g))
aplicaFEntreComG: (f: (Int, Int) => Int,a: Int,b: Int,g: (Int) => Int)Int scala> def soma(x: Int, y: Int) = x + y scala> aplicaFEntreComG(soma, 1, 10, quadrado) |
Resposta (2):
|
1
2 |
scala> aplicaFEntreComG((a: Int, b: Int) => a + b, 1, 10, (x: Int) => x * x)
res8: Int = 385 |
Resposta (3):
|
1
2 3 4 5 |
scala> aplicaFEntreComG((a, b) => a + b, 1, 10, x => x)
res11: Int = 55 scala> aplicaFEntreComG((a, b) => a + b, 1, 10, x => x * x) |
Resposta (4):
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
scala> def aplicaFEntre(f: (Int, Int) => Int):((Int => Int) => (Int, Int) => Int) = {
def retFunc(g: Int => Int): (Int, Int) => Int = { def ret(a:Int, b: Int): Int = if (a > b) 0 else f(g(a), ret(a+1, b)); ret }; retFunc } aplicaFEntre: (f: (Int, Int) => Int)((Int) => Int) => (Int, Int) => Int scala> aplicaFEntre((x, y) => x + y)(x => x)(1, 10) scala> aplicaFEntre((x, y) => x + y)(x => x * x)(1, 10) scala> def somaAplicaFEntre = aplicaFEntre((x, y) => x + y) scala> def somaQuadradoEntre = somaAplicaFEntre(x => x * x) scala> somaQuadradoEntre(1, 10) |
Dúvidas, críticas e sugestões são bem vindas, como de praxe
.
Scala – Primeiros Passos
Aprendendo Scala
Estou começando uma série de posts sobre Scala para divulgar as minhas experências com a linguagem e suas aplicações. Vou tentar passar o funcionamento da linguagem e seus recursos, assim como também um pouco do paradigma de programação funcional, e tentar chegar em aplicações práticas no ambiente de desenvolvimento.
Existem alguns tutoriais disponíveis na internet bem interessantes mas que as vezes jogam muito conteúdo sem dar tempo para assimilar os conceitos. Nessa série de posts tentarei colocar os conceitos e ferramentas de Scala e linguagens funcionais de uma maneira natural, conforme eu mesmo for evoluindo na linguagem e também dando exemplos e exercícios para que você possa assimilar o que está sendo passado. Vou tentar explicar também conceitos mais teóricos do paradigma funcional, para que a linguagem que estamos falando seja a mesma.
Minha experiência com Scala é algo bem próximo do zero, por isso estou escrevendo esta série de posts com o intuito de me forçar a estudar a linguagem e também ajudar a divulgar uma das possíveis alternativas ao Java, afinal se trata de uma linguagem nova e que ainda não foi muito difundida na comunidade. Tratarei bem do básico nos primeiros posts com exemplos bem trivias apenas para ajudar na compreensão dos conceitos básicos. Também colocarei algumas bibliografias de onde se pode encontrar documentação sobre a linguagem.
Linguagens funcionais e Scala
Não é nova a ideia de se programar em um paradigma funcional. Linguagens funcionais existem a quanse tanto tempo quanto existe linguagens de programação. Com certeza na sua faculdade você viu, ou verá, linguagens como LISP, Scheme, Erlang, Clojure, etc… e atualmente que está ficando bem conhecida: Scala que roda em cima da JVM, a virtual machine do Java. Linguagens funcionais mudam o paradigma para focar em ‘funções’. Assim como em linguagens orientadas a objeto onde a unidade básica é uma intância de um objeto, em linguagens funcionais a unidade básica é uma função. Uma função nada mais é do que uma operação que recebe parâmetros, executa alguma ação e devolve um resultado. O funcionamento é muito parecido com o dos métodos em linguagens orientadas a objeto.
O interessante sobre Scala, é que a linguagem permite uma mescla do paradigma funcional com orientação a objeto. Existem conceitos de ambos implementados na linguagem e que fazem sentido quando aplicados em conjunto. Por exemplo, em scala existe o conceito de classe e de instâncias de classes, que são os objetos. Também existem funções que independem de uma classe para existirem. Se trata de um conceito muito poderoso, pois possibilita toda padronização e controle do design do sistema que as linguagens orientadas a objeto permitem, sem restringir o uso do dinamismo e abstração da linguagem funcional.
Mas claro que Scala e linguagens funcionais tem seus problemas. Por exemplo, não existe uma IDE sólida como o Eclipse ou o NetBeans para Scala. O que dificulta no momento de trabalhar com projetos muito grandes, com uma equipe com vários programadores. O dinamismo da linguagem também impede uma análise de código que por exemplo o PMD, ou o FindBugs disponibilizam no Java. Dessa forma, deveríamos garantir que as boas práticas e o design está sendo seguido com nossos testes unitários.
Chega de filosofar sobre a linguagem, vamos para a parte prática.
Instalando Scala
Para instalar o Scala basta acessar a página oficial de download neste link, e fazer o download de acordo com sua plataforma. Feito isso, descompacte o conteúdo em alguma pasta na sua máquina e adicione a pasta ‘bin‘ ao path do seu sistema operacional. Para se certificar que foi tudo instalado corretamente, abra um console e digite ‘scala‘, o terminal do scala deve abrir e aparecer uma tela como essa:
Agora que o terminal está sendo executado, digite ‘1 + 1‘ e repare na saída:
|
1
|
res0: Int = 2
|
É uma saída um tanto quanto confusa, mas que faz sentido no contexto do Scala. O que está acontecendo é que a unidade básica em Scala é uma função, portanto a operação + também é uma função, que foi executada no objeto ‘1‘, passando o parâmetro ‘1‘. Para se ter uma idéia melhor desse funcionamento, digite o seguinte no terminal:
|
1
|
scala> 1.+(1)
|
e aperte Enter. Repare na saída:
|
1
|
res1: Double = 2.0
|
Com exceção do tipo que era int e agora é Double, o funcionamento é exatamente o mesmo. Tal sintaxe já deve estar bem claro na cabeça da maioria dos programadores, mas a primeira sintaxe é só uma maneira difetente de se fazer a mesma chamada.
Conceitos básicos da linguagem
Vamos definir uma função para entender um pouco mais da sintaxe. Digite o seguinte:
|
1
2 |
scala> def a = 2 + 2
a: Int |
Aqui estamos definindo uma função com o nome ‘a‘ que não recebe parâmetros, e devolve o resultado da soma ‘2 + 2‘. A palavra restrita return não é necessária, o compilador consegue inferir que a última expressão da função será o valor devolvido. Repare também que não precisamos definir o tipo que a função devolve, o qual também foi inferido pelo compilador. Portanto ao contrário do que possa parecer, Scala é uma linguagem tipada em tempo de compilação, portanto o seguinte trecho não compila:
|
1
2 3 4 5 6 7 8 9 |
scala> var b = “abc”
b: java.lang.String = abc scala> b = a |
Repare que o compilador inferiu que o tipo da variável ‘b‘ é String (note que é o String do java), e que o tipo devolvido por ‘a‘ é Int, logo o código não compila. De certa forma esse comportamento é bom, pois garante uma maior integridade do código, sem que você tenha que programar para “deixar o compilador feliz“, ou seja, não existem tantas regras na escrita do código, porque muitas podem ser deduzidas a partir do contexto.
Um outro conceito de Scala é a diferença entre chamada por valor, e chamada por nome. Na chamada por valor é feita a evaluação da expressão assim que o código é executado, mesmo que não seja necessário usar o resultado da chamada. A vantagem é que evitamos executar outras vezes a expressão para obter o resultado, pois este já foi determinado. Na chamada por nome a evaluação da expressão ocorre apenas quando o resultado da chamada será usado, tendo um comportamento mais lazy. A desvantagem é a execução da expressão várias vezes para se obter o mesmo resultado, mas a vantagem é não fazer a execução quando não for necessário. Vamos ver um exemplo da diferença entre a chamada por valor e por nome. Volte no terminal e defina a seguinte função:
|
1
2 |
scala> def loop: Int = loop
loop: Int |
Agora defina uma função que recebe dois inteiros e devolve o primeiro:
|
1
2 |
scala> def primeiro(x: Int, y: Int) = x
primeiro: (x: Int,y: Int)Int |
Agora execute a chamada passando ‘loop‘ como segundo parâmetro e se prepare porque seu console irá travar na execução =):
|
1
2 |
scala> primeiro(0, loop)
_ |
Vamos fazer uma pequena modificação na definição de ‘primeiro‘ para tornar possível a execução de tal código:
|
1
2 |
scala> def primeiro (x: Int, y: => Int) = x
primeiro: (x: Int,y: => Int)Int |
O que definimos agora foi que o segundo parâmetro de ‘primeiro‘ será uma função que devolve um Int. Assim seu resultado só será obtido quando for feita a chamada direta na expressão. Agora conseguimos fazer a chamada em nossa função:
|
1
2 |
scala> primeiro(0, loop)
res0: int = 0 |
Nossa função executou e finalizou sem problemas dessa vez, isso porque não foi necessário utilizar o segundo valor para obter o resultado. Essa é chamada por nome, e o primeiro exemplo é a chamada por valor.
Até agora vimos como invocar funções, como definir funções (def) e como definir variáveis (var). Vimos também o básico de inferência de tipo pelo compilador do scala e os primeiros conceitos da linguagem. Mas como eu havia dito, Scala também tem conceitos de orientação a objetos, portanto vamos definir nossa primeira classe em Scala:
|
1
2 |
scala> class AcumulaSoma { var acumulado = 0; def acumula(i: Int) = { acumulado = acumulado + i; acumulado } }
defined class AcumulaSoma |
Lembre-se que não somos obrigados a definir o tipo devolvido pela função caso esteja explícito e também não precisamos da palavra reservada ‘return‘. O que fizemos foi definir uma classe que se chama ‘AcumulaSoma‘ que tem um atributo, que por padrão do Scala é de acesso privado, que se chama acumulado e que o compilador inferiu o tipo inteiro baseado no valor atribuído. Também definimos para a classe a função ‘acumula‘ que recebe um inteiro, guarda o valor acumulado até então da soma com o parâmetro passado e devolve tal valor. Vamos instanciar um objeto da nossa classe e fazer a chamada ao método:
|
1
2 |
scala> val ac = new AcumulaSoma
ac: AcumulaSoma = AcumulaSoma@cbbe37 |
Algumas diferenças com o Java (supondo que estamos mais acostumados com a sintaxe do Java) já podem ser notadas. Além da inferência do tipo de ‘ac‘ para ‘AcumulaSoma‘, criamos ‘ac‘ com a a palavra reservada ‘val‘, ou seja, estamos dizendo que diferente de ‘var‘ onde criamos uma variável, estamos criando um valor e estamos garantindo em tempo de compilação que este valor não pode ser alterado no código. No Java teríamos que usar a palavra ‘final‘ para obter o mesmo comportamento, em Scala temos que “satisfazer menos o compilador“. Também não foi necessário usar ‘()‘ para invocar o construtor de AcumulaSoma, e assim como no Java toda classe em Scala tem por padrão o construtor que não recebe parâmetros com acesso público. Continuando o exemplo:
|
1
2 3 4 5 |
scala> ac.acumula(2)
res0: Int = 2 scala> ac.acumula(1) |
Nada de novidade aqui, estamos invocando a função ‘acumula‘ no objeto ‘ac‘. Porém ‘acumula‘ é uma função que recebe um único parâmetro e devolve um valor. Dessa forma podemos executar a chamada da nossa função da outra forma que vimos anteriormente:
|
1
2 |
scala> ac acumula 4
res2: Int = 7 |
No Java conseguimos definir classes estáticas. Em Scala não existe exatamente o conceito de classes, atributos e parâmetros estáticos, porém existe um conceito mais bem definido: ‘object‘. Criamos um ‘object‘ igual criamos uma classe, com a diferença de que não é possível instanciarmos um object, afinal estamos definindo o próprio. Com essa diferença, estamos garantindo em tempo de compilação que o design do código, em que foi tomada a decisão de restringir a existência de apenas uma definição para determinada classe seja seguida. Em Java também é possível garantir tal critério em tempo de compilação, mas demanda a escrita de bem mais código e de uma compreensão um pouco maior do funcionamento e limitações da linguagem. Um exemplo de object:
|
1
2 3 4 5 |
scala> object Objeto { def valor = 5 }
defined module Objeto scala> Objeto.valor |
Existem os tipos primitivos básicos em Scala como Int, Long, Boolean, Float, Double, Short, Byte, Char e String, sendo que String é a String do ‘java.lang‘. Um fato interessante sobre Scala é que como a linguagem roda em cima da JVM, todas as bibliotecas disponíveis para Java também estão disponíveis para Scala.
O tipo que não vimos ainda é o ‘void‘. Em Scala não existe o conceito de void, toda função devolve um valor e quando não há um valor explícito a ser devolvido o compilador se encarrega de devolver um valor do tipo ‘Unit‘. Exemplo:
|
1
2 |
scala> def imprime = print(“Hello!”)
imprime: Unit |
‘print’ é uma função que imprime um valor no terminal e não devolve valor, logo nossa função ‘imprime‘ também não devolve valor, portanto o tipo devolvido é ‘Unit‘, que também pode ser expressado/escrito como ‘()‘. A diferença básica entre void e Unit, é que Scala permite criar valores e variáveis do tipo Unit, e em Java por exemplo não é possível criar uma variável do tipo void. Também é possível analisar o valor devolvido por qualquer função mesmo que a função não devolva um valor definido e neste caso será um Unit. Um exemplo trivial de como usar este comportamento:
|
1
2 3 4 5 |
scala> def num = 9
num: Int scala> def verificaUnit(f: => Any) = { f match { case x:Unit => print(“Eh Unit!”) case _ => print(“Nao eh Unit!”) } } |
O tipo ‘Any‘ é análogo ao ‘Object‘ do Java, portanto pode ser qualquer valor, função ou objeto.
Nessa função estamos usando um tipo de switch case para obter o mesmo comportamento do ‘instanceof‘ em Java, afinal não existe tal operação em Scala. O que está acontecendo é que a função verificaUnit recebe uma função que não recebe argumentos e devolve um valor qualquer. A função ‘f‘ é então evaluada e o resultado da função é testado nos cases da operação ‘match‘. Caso seja do tipo Unit, imprimimos “Eh Unit!”, e no caso padrão imprimimos “Nao eh Unit!”. Repare que diferente do ‘switch‘, no ‘macth‘ não temos que chamar a operação ‘break‘ para não executar os outros casos. Para testar verificaUnit vamos fazer a chamada usando nossas outras duas funções que foram definidas:
|
1
2 3 4 |
scala> verificaUnit(num)
Nao eh Unit! scala> verificaUnit(imprime) Hello!Eh Unit! |
Um exemplo mais prático
Vamos implementar um exemplo um pouco mais prático que calcula a raiz quadrada aproximada de um número. Para isso vamos usar o método de Newton de aproximações sucessivas. A idéia é bem simples, nada mais é do que uma busca binária entre os palpites que se aproximam cada vez mais da raiz quadrada do número passado. Vamos começar definindo uma função que diz se a raiz encontrada é boa o suficiente para ser o resultado:
|
1
2 3 4 5 |
scala> def abs (x: Double) = if (x > 0) x else -x
abs: (x: Double)Double scala> def proximaOSuficiente(palpite: Double, x: Double) = abs((palpite * palpite) – x) < 0.0001 |
O bloco if é igual a sintaxe usada no Java, o mesmo vale para o else e o else if. Na segunda função estamos verificando se o número elevado ao quadrado é próximo o bastante do original. Agora vamos definir a função que faz um palpite melhor:
|
1
2 |
scala> def melhoraPalpite(palpite: Double, x: Double) = (palpite + x / palpite) / 2
melhoraPalpite: (palpite: Double,x: Double)Double |
O que fizemos foi pegar um número para palpite que está entre o nosso palpite anterior e x dividido sobre o palpite anterior, onde x é o número que queremos obter a raiz quadrada. O método de Newton garante que para x >= 1 essa aproximação é sempre válida. Vamos agora a função que executa o método de Newton propriamente dito:
|
1
2 3 |
scala> def raizNewton(palpite: Double, x: Double): Double =
| if (proximaOSuficiente(palpite, x)) palpite else raizNewton(melhoraPalpite(palpite, x), x) raizNewton: (palpite: Double,x: Double)Double |
Estamos calculando a raiz quadrada de modo recursivo até que o resultado seja bom o suficiente. Agora basta definir uma base para nossa função, para isso iremos pré-definir um valor para o palpite base, guardando a chamada nesse escopo em uma outra função:
|
1
2 |
scala> def raizQuadrada(x: Double) = raizNewton(1, x)
raizQuadrada: (x: Double)Double |
Pronto, basta brincar com algumas chamadas para nossa função que calcula a raiz quadrada =).
|
1
2 |
scala> raizQuadrada(169)
res4: Double = 13.000000070110696 |
O resultado obtido não é muito preciso, mas também essa não é a melhor maneira de se calcular a raiz quadrada, inclusive para números muito grandes o algorítmo não funciona por restrições com a precisão do Double.
Algumas Considerações
A grande moda das linguagens funcionais está surgindo porque acreditasse que elas são o futuro para a programação concorrente e paralela, de que se o seu programa foi escrito em uma linguagem funcional você terá ganho o benefício da execução concorrente sem grandes esforços, bem, isso não é verdade. Linguagens funcionais como qualquer outro paradigma necessitam que o programa seja escrito de maneira que seu design seja voltado para execução concorrente ou paralela, a difenrença é que a sintaxe e as ferramentas disponíveis nas linguagens funcionais facilitam tal design.
Existem algumas técnicas de programação que surgiram com linguagens funcionais, como por exemplo Recursão em Cauda, que não entrarei no mérito nesse post mas que vale a pena pesquisar sobre o assunto. Tais técnicas facilitam a vida do compilador no momento em que for possível ser feita algum tipo de otimização.
Uma comparação que logo surge na cabeça dos programadores é: qual linguagem é melhor, Scala ou Java? A resposta é fácil… nenhuma. Cada linguagem tem um escopo e paradigma de atuação diferente, não tem como comparar ambas. O que talvez faça sentido comparar sejam fatores mais fora do domínio da linguagem, como por exemplo o tamanho da comunidade que utiliza efetivamente a linguagem, o tipo de suporte, a documentação disponível, as restrições de uso, mas em geral depende do caso de uso específico para o seu problema, e principalmente do gosto do programador.
Por enquanto é isso, em um próximo post tratarei de alguns outros tipos de funções e falarei um pouco mais sobre currying. Também darei exemplos de tipos diferentes de declarações de classes e objetos.
Referência: Scala By Example, Martin Odersky
Injeção de dependência com Spring
Começando com o Spring
Este post visa ajudar na compreensão de um dos conceitos mais difundidos de OO e que ajudaram a tornar o Spring um dos frameworks mais utilizados na comunidade: Inversão de Controle. Um dos casos de Inversão de Controle é a Injeção de Dependências, e é dela que trataremos neste post.
Inversão de Controle e Injeção de Dependência
Imagine que queremos implementar um serviço web que disponibiliza algumas informações do sistema, recuperando algumas informações do banco de dados e executa algumas regras de negócio sobre elas. Para isso nosso serviço deveria de alguma maneira acessar o banco de dados da aplicação, por exemplo através de um ‘DAO‘, e a partir do DAO recuperar os dados necessários. Com os dados em mãos nosso serviço consegue delegar para alguma classe do sistema executar as regras de negócio que se apliquem, por exemplo um ‘Manager‘.
Para este exemplo o mais direto seria instanciar em nosso serviço um objeto da classe DAO e um objeto da classe Manager para que possam ser usados. Em teoria isso seria uma tarefa simples, porém no nosso dia-a-dia de desenvolvimento sabemos que configurar um DAO que acessa o banco de dados não é tão simples assim. Além do DAO, nosso serviço precisa instanciar também uma ‘SessionFactory‘, configurar uma conexão com o banco e outras coisas dependendo da implementação da JPA que se está utilizando. O mesmo vale para a classe de negócio, pois tal classe pode precisar de outras classes mais especializadas e sendo assim, todas essas classes também devem ser intanciadas em nosso serviço.
A situação fica pior se quisermos criar um novo serviço que por exemplo usu o mesmo DAO, mas use classes de negócio diferente. Toda a lógica de instanciar o DAO deve ser replicada no novo serviço.
Agora imagine que temos o mesmo problema para ser resolvido, implementar um serviço web, porém ao invés de nos preocupar em instanciar as classes que iremos precisar, elas já estivessem instanciadas e configuradas corretamente e inclusive ‘setadas‘ em nosso serviço prontas para uso. Esse padrão de deixar a responsabilidade de criar e configurar classes que estarão disponíveis na aplicação fora das classes que as usam se chama Inversão de Controle. Estamos tirando a lógica de criação de dependências das classes chamadas, e deixando a cargo das classes que chamam. Quando temos as dependências criadas e configuradas, e temos as classes que irão usá-las e ‘injetamos‘ as denpendências nas mesmas, temos o que chamamos de Injeção de Dependências.
No Spring o responsável por criar e configurar as classes que serão usadas é chamado de ‘BeanFactory‘, e tais classes são chamadas de ‘beans‘. ‘Beans’ do Spring estão disponíveis no ‘container‘ e podem ser recuperados a qualquer momento dentro do contexto da aplicação. No Spring o contexto da aplicação é representado pelo ‘ApplicationContext‘ que pode ser criado de várias maneiras. Iremos abordar isso mais pra frente.
Definindo um ApplicationContext
Tudo no Spring começa a partir de um contexto da aplicação. Por isso precisamos criar um antes de utilizarmos os princípios de inversão de controle e injeção de dependência. Dentro das maneiras de criar um ApplicationContext usaremos a criação por XML, que é simples o suficiente para começarmos a usar o Spring. Para isso defina o seguinte arquivo XML no seu projeto com o nome de ‘applicationContext.xml‘:
|
1
2 3 4 5 6 7 8 9 |
<?xml version=“1.0″ encoding=“UTF-8″?>
<beans xmlns=“http://www.springframework.org/schema/beans” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:context=“http://www.springframework.org/schema/context” xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd”> </beans> |
Lembre que para utilizar o framework, você precisa ter os jars do Spring referenciados no seu projeto. Estamos usando a versão 3, que podem ser encontrados no próprio site do Spring (http://www.springsource.org/download). A princípio só usaremos os módulos ‘spring-beans‘ e ‘spring-context‘.
Dica: se o seu projeto está usando o maven, você pode apenas adicionar as seguintes dependências que o maven se encareguará de referenciá-las no seu projeto:
|
1
2 3 4 5 6 7 8 9 10 |
<dependency>
<groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>3.0.2.RELEASE</version> </dependency> |
Crie uma classe para executarmos alguns testes, como por exemplo a classe abaixo com um método ‘main‘:
|
1
2 3 4 5 6 7 8 |
Dentro do nosso método ‘main’, iremos criar um novo ApplicationContext a partir do XML que definimos acima:
|
1
2 |
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext(“applicationContext.xml”); |
Definindo nossos primeiros Beans
Iremos definir agora os beans que estarão disponíveis em nossa aplicação. Existem duas maneiras de se fazer isso. A primeira e que era usada até a versão 2.0 do Spring é através de XML. Para isso basta definir os beans dentro do nosso aaplicationContext.xml. Veremos exemplo em breve. Uma segunda maneira que foi introduzida a partir da versão 2.5 do Spring, é através de anotações. Para usarmos anotações precisamos colocar uma linha a mais no applicationContext.xml:
|
1
|
<context:component-scan base-package=“br.com.dclick” />
|
No atributo ‘base-package‘ você deve colocar o caminho do pacote onde ficarão as classes dos seus beans.
Precisamos agora de classes para serem nossos beans. Vamos criar uma classe qualquer apenas para entender a injeção de dependências:
|
1
2 3 4 5 6 7 8 9 |
E uma outra classe que dependa de MockTestClass:
|
1
2 3 4 5 6 7 8 9 10 |
package br.com.dclick;
public class MockTestClass2 { private MockTestClass mockBean; public MockTestClass getMockBean() {return mockBean;} public void setMockBean(MockTestClass mockBean) {this.mockBean = mockBean;} |
Repare que as classes possuem atributos privados e seus respectivos ‘getters‘ e ‘setters‘. O Spring consegue injetar dependências diretamente nos atributos, mas para começar iremos usar o padrão de ‘getter‘ e ‘setter‘.
Vamos disponibilizar os beans de nossas duas classes em nosso ApplicationContext. Se você preferir usar XML, basta adicionar o seguinte dentro da tag ‘beans‘ do applicationContext.xml:
|
1
2 3 4 5 |
<bean class=“br.com.dclick.MockTestClass” id=“mockBean”>
</bean> <bean class=“br.com.dclick.MockTestClass2″ id=“mockBean2″> |
Repare que a tag ‘bean‘ recebe dois parâmetros: ‘class‘ e ‘id‘. A tag ‘class‘ indica o caminho o absoluto do no bean, nesse caso temos o MockTesClass e MockTestClass2 como nossos beans. A tag ‘id‘ define um nome para os nossos beans, no caso ‘mockBean‘ e mockBean2‘. Usaremos esse nome para recuperá-los do container.
Caso você deseje usar anotações, basta adicionar o seguinte nas classes dos beans (não esqueça de adicionar a linha no applicationContext.xml que indica o pacote com os beans):
|
1
2 |
E para MockTestClass2:
|
1
2 |
Agora temos nossos dois beans disponíveis no container da aplicação. Para validar que tudo está criado corretamente, volte em nossa classe com método ‘main’ e adicione o seguinte código:
|
1
2 |
MockTestClass mockBean = (MockTestClass) applicationContext.getBean(“mockBean”);
System.out.println(“Mock Bean: “ + mockBean); |
Rode o código e repare que foi impresso no console o endereço do seu bean. Faça o mesmo para o segundo bean:
|
1
2 |
MockTestClass2 mockBean2 = (MockTestClass2) applicationContext.getBean(“mockBean2″);
System.out.println(“Mock Bean 2: “ + mockBean2); |
Adicione o seguinte código no final do método:
|
1
|
Rode e perceba que mockBean está nulo. Isso porque não falamos para o Spring que ele deveria injetar essa dependência em nosso bean de maneira automática. Existem algumas formas de se configurar esse comportamento. Seguindo nosso padrão até agora, vamos injetar a dependência pelo XML e via anotações, em um post futuro trataremos de injeção automática de dependências.
No XML modifique o ‘mockBean2‘ adicionando o seguinte trecho:
|
1
2 3 4 5 |
<bean class=“br.com.dclick.framework.utils.test.mock.MockTestClass2″>
<property name=“mockBean”> <ref bean=“mockBean”/> </property> </bean> |
Na tag ‘property‘ definimos uma propriedade do bean que deve ser gerenciada pelo Spring, nesse caso a propriedade é o ‘mockBean‘. Com isso o Spring irá invocar o ‘setMockBean‘ e injetar a dependência. Já com anotações basta anotar o ‘setter‘ de ‘mockBean‘ com ‘@Autowired‘. Dessa forma o Spring irá buscar por um bean com nome ‘mockBean’ no container e caso encontre este será injetado automaticamente:
|
1
2 |
@Autowired
public void setMockBean(MockTestClass mockBean) {this.mockBean = mockBean;} |
O resultado de ambas as maneiras é exatamente o mesmo. Para ter certeza, rode novamente nossa classe de teste, e repare no endereço impresso no console. Note que o endereço do ‘mockBean‘ é o mesmo tanto quando recuperamos direto do ApplicationContext, quanto do ‘mockBean2‘. Isso acontece porque por padrão, todo bean do Spring é ‘Singleton‘ dentro do contexto da aplicação. Iremos tratar de escopo de beans em um post futuro, mas se você deseja testar basta adicionar o seguinte para que seu bean seja criado sempre que requisitado:
No XML adicione ‘scope=”prototype”‘:
|
1
2 |
<bean class=“br.com.dclick.MockTestClass” id=“mockBean” scope=“prototype”>
</bean> |
Via anotação adicione ‘@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)‘ na classe:
|
1
2 3 |
Rode a classe e repare que os endereções agora são diferentes.
Cobrimos o básico de injeção de dependência do Spring. Existem muitas outras maneiras de se injetar dependências nos beans, de se gerenciar criação de beans, de declarar beans enfim, vimos pouca coisa mas que já suficiente para começarmos a estudar alguns conceitos um pouco mais avançados de OO e entendermos melhor o funcionamente de alguns frameworks. A documentação do Spring é uma das melhores disponíveis na comunidade, por isso recomendo a consulta (http://www.springsource.org/documentation). Vamos nos aprofundar mais no funcionamento e diferentes usos do Spring em posts futuros.
Adobe AIR Update Framework + ANT Task + Hudson
Umas das coisas que eu acho mais interessante quando estou desenvolvendo para Adobe AIR é a facilidade de realizar as atualizações da aplicação, é muito simples e fácil de implementar uma maneira automatica para as atualizações. Na verdade a implementação já existe, só é preciso saber como utilizar.
Neste post vou falar um pouco sobre o framework de update das aplicações air e como tirar proveito disso com um processo deployment também automatico utilizando ANT para compilar o projeto e Hudson para separar os ambientes e etc.
Air Update Framework – Atualização da aplicação
O framework de update do air trabalha basicamente com 3 coisas:
A classe ApplicationUpdaterUI e os arquivos de configurações update.xml e updateConfig.xml . Independente de como será feito o deployment da sua aplicação, o modo de atualização será sempre baseado nesta classe e nestes arquivos.
Você também pode utilizar esta classe atribuindo manualmente as propriedades que são lidas pelos arquivos de configuração, entretanto, com arquivo é possível alterar os valores depois que o AIR ja tiver sido compilado.
ApplicationUpdaterUI
Esta classe utiliza o arquivo de configuração updateConfig.xml para saber aonde procurar por uma atualização e informar em caso exista uma versão mais recente.
Esta classe precisa basicamente de 3 coisas.
- Setar a propriedade configurationFile com o arquivo updateConfig.xml
- Inicializar
- E verificar se há uma nova versao.
Segue abaixo um trecho de código com a utilização:
|
1
2 3 4 5 6 7 8 9 10 11 |
public static function checkNow(updateConfigFile:File):void
{ updater.configurationFile = updateConfigFile; updater.addEventListener(UpdateEvent.INITIALIZED, updateInitializeHandler); updater.initialize(); } private static function updateInitializeHandler(event:UpdateEvent):void |
No projeto que irei disponibilizar no final do post eu estou utilizando a classe VersionManager criada aqui na DClick para facilitar um pouco essa parte de configurar e inicializar o updater
updateConfig.xml
Dentro do arquivo updateConfig.xml está todas as configurações de como a verificação de novas atualizações serão feitas. A classe ApplicationUpdaterUI lê estas informações durante a inicialização.
update.xml
Este arquivo contem a informação de qual é a versão atual do sistema e onde baixa-la. O update framework compara a versão do software que está sendo executada ( token <version> dentro do xml que descreve a aplicação air ) e caso for diferente da que esta no update.xml ele irá tentar fazer atualização para a nova versão.
ANT Task – Compilação através de linha de comando
Agora que agente ja viu como funciona o sistema para atualização de versão dos aplicativos AIR, podemos podemos deixar o processo de distribuição ainda mais rapido. Vamos ver como gerar a nova versão pelo plugin do flex e depois como gerar utilizando um script ANT
O plugin do flex possibilida fazer o export do projeto para o arquivo .air, basta clicar em project/export release build (imagem abaixo).
Entretanto deste modo cabe a você antes de executar o export configurar qual será a nova versão do aplicativo, e para qual ambiente ( producao/teste/cliente/etc ) você esta compilando esta nova versão.
Em um projeto desenvolvido com integração continua com o cliente, onde varias versões são compiladas durante o desenvolvimento, fazer o build da aplicação não é a melhor escolha.
Uma alternativa é criar um script ANT para buildar o projeto, com ele é possível buildar o projeto e a cada build alterar a versão do aplicativo, deste modo sem muito esforço você tem uma maneira mais prática para gerar as versões da aplicação. O esforço fica em criar o script a primeira vez porque dependendo do tamanho do projeto ( dependencia entre outros projetos, etc ) o script pode ficar grande e complexo.
No final do post vou deixar um link para um projeto no github, a ideia é que este projeto funcione como um template básico para quando for desenvolver um app em air, pois ele ja define a estrutura de pastas build/ambiente e também um script ANT padrao para buildar o projeto de acordo com cada ambiente que for necessário. O script esta todo comentado, de modo que é possível enter cada passo que esta sendo feito.
Como funciona o ANT para o Flex/Air?
O script é dividido em targets ( passos ) que são executados numa ordem definida pelo criador.
Por ex: Os passos básicos para gerar o .air são
- Compilar o projeto e gerar o swf.
- Gerar o instalador .air a partir do swf.
Compilação do SWF:
Para compilar o projeto e gerar o swf o script ANT utiliza um jar chamado flexTasks.jar. Primeiro é necessário incluir este jar dentro do script atráves da tag <taskdef /> do ant ( veja abaixo ):
Depois de incluir o jar como recurso é possível compilar o projeto utilizando a tag
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<mxmlc file=“${air.src}/${air.app}.mxml” output=“${temp.dir}/${air.app}.swf” actionscript-file-encoding=“UTF-8″
keep-generated-actionscript=“false” incremental=“false” benchmark=“true” locale=“pt_BR” debug=“false” configname=“air” target-player=“10.0.0″ services=“${temp.dir}/${services.file.name}” context-root=“{context.root}”> <license product=“flexbuilder3″ serial-number=“xxxx-xxxx-xxxx-xxxx-xxxx-xxxx” /> <default-size height=“768″ width=“1024″ /> <source-path path-element=“${air.src}” />
<compiler.library-path dir=“${air.libs}” append=“true”> </mxmlc> |
Este exemplo foi extraido do script ANT que está no projeto template. Para ver mais detalhes sobre como utilizar a tag
Gerando o .AIR
Uma vez que você ja compilou o projeto e ja gerou o .swf, para gerar o .air você irá utilizar o jar .adt ( Adobe Developer Tool ). Diferente do flexTask.jar, o adt nós não vamos incluir como recurso no script, vamos executa-lo através da tag
Veja abaixo um exemplo extraido do script que esta no projeto de template:
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<java jar=“${air.adt}” fork=“true” failonerror=“true”>
<arg value=“-package” /> <arg value=“-storetype” /> <arg value=“${air.storetype}” /> <arg value=“-storepass”/> <arg value=“${air.certpassword}”/> <arg value=“-keystore”/> <arg value=“${air.cert}”/> <arg value=“${air.installer}” /> <arg value=“${app.description.final}” /> <arg value=“-C” /> <arg value=“${temp.dir}” /> <arg value=“${air.app}.swf” /> <arg value=“-C” /> <arg value=“${temp.dir}” /> <arg value=“update” /> </java> |
Com o ADT é possível gerar o .air já autenticado com um certificado ou simplesmente gerar um arquivo .airi sem autenticação e depois em um outro passo autenticar este arquivo intermediario. Para ver mais detalhes dos argumentos que o ADT recebe como passa-los segue o link da adobe
Como alterar a versão da aplicação a cada build?
Apesar de nós já termos visto como é feita a compilação do swf e como é gerado o .air, ainda não vimos como alterar a versão da aplicação a cada build. Muito bem, no script que está no projeto template, isto é feito através da tag
Veja abaixo a sequencia de como isto é feito no script que esta no projeto template.
|
1
2 3 4 5 6 7 8 9 10 |
<!– Atualiza o contador do arquivo que é refenciado pela propriedade ‘build.number.file’ –>
<buildnumber file=“${build.number.file}” /><!– Gera o codigo final da nova versão –> <!– Gera o código da nova versão. Neste caso estamos concatenando o valor de ‘project.version’ ( default.properties ) <!– atualiza o token version do description final –> |
Nestes 3 passos nós atualizamos o numero da versão do ambiente que esta sendo compilado, montamos o codigo final da versão ( neste caso V1, V2, V3 ) e em seguida alteramos o node <version /> do xml que descreve a aplicação de <version>[version]</version> para <version>v1</version>.
Na próxima vez que rodar o script o token será alterado novamente de <version>[version]</version> para <version>v2</version>. E assim sussesivamente.
Utilizar o Hudson para buildar o projeto com o script ANT
Apesar de ja estar utilizando o ANT para buildar o projeto e o update framework para verificar atualizações, não é muito interessante que o desenvolvedor tenha que ficar rodando manualmente o script ant, e sendo responsável por compilar o ambiente correto e etc. Para fazer este trabalho nós vamos utilizar o hudson.
Com o hudson você pode configurar um job que irá rodar o script ANT de maneira automatica. Além disso, o hudson pode ser integrado com o repositório de código ( git, subversion, cvs, etc ). Portanto após todos os commits da versão serem feitos, basta executar o job do hudson que ele ira baixar todas as alterações no código e em seguida executar o ANT para gerar a nova versão.
No script ANT o parâmetro que indica para qual ambiente estamos rodando é fornecido pelo Hudson, deste modo, para cada ambiente vamos criar um job no hudson.
Abaixo segue um screencast passo a passo de como criar um novo job no hudson ja integrado com o projeto template que esta no github e disponível para download.
Para aqueles que quiserem ver mais detalhes sobre o Hudson, segue o link oficial.
Segue o url para o pessoal que quiser dar uma olhada nos fontes ou até mesmo baixar o projeto/template com essas configuracões de ANT e etc.
O que é o Maven, e seus primeiros passos com a ferramenta
Entendendo melhor Maven 2
O objetivo deste post é entender um pouco melhor o que é o Maven e como que o Maven lida com as fases do build, além de entender um pouco de como funciona a resolução de dependências.
O que é o Maven?
Um dos principais problemas desenvolvendo sistemas é como fazer com que toda a equipe construa o artefato final da mesma maneira com as bibliotecas e configurações corretas. Existem várias maneiras de tratar esse tipo de problema. Uma das maneiras mais comuns é usar alguma ferramenta que entenda alguma linguagem de script e copie as dependências e configurações para os lugares corretos e gere um pacote com a aplicação. Uma ferramenta comum muito usada no mundo Java é o Ant (http://ant.apache.org/). Com o Ant é possível escrever scripts de ‘build‘ para copiar bibliotecas de lugares específicos, arquivos de configurações e qualquer outro tipo de recurso para o artefato da aplicação. Com isso garantimos que qualquer desenvolvedor consiga gerar o artefato da aplicação localmente, sabendo que este artefato gerado é o mesmo que o resto da equipe irá usar e que possivelmente será usado em produção. Porém existem alguns problemas com essa abordagem. O primeiro problema que vem a cabeça é, onde guardamos as bibliotecas externas ao nosso projeto para que o Ant as encontre? A solução mais comum é guardá-las no repositório junto com o código fonte, assim todos os desenvolvedores compartilharão as mesmas dependências e os caminhos das pastas são mantidos.
Esse tipo de organização é muito adotada e funciona muito bem para projetos pequenos e com equipes bem pequenas. Quando começamos a prolongar um pouco a vida do projeto percebemos que o overhead de manutenção do mesmo começa a interferir no andamento do desenvolvimento. Isso porque o número de dependências começa a aumentar e com o tempo vão saindo versões novas de algumas dependências antigas(podemos entender dependências como sendo bibliotecas). Se todas as dependêcias estão na mesma pasta, fica muito difícil fazer o controle de versão e de transitividade das mesmas. Agora pense em um caso mais complicado: decidimos criar um novo módulo para o projeto. Nesse caso, todas as dependências que este novo módulo necessita e estão no módulo já existente, devem ser duplicadas no repositório. Deve-se fazer isso pois já que estamos falando de módulo, devemos ser capazes de construir o tal módulo independente da existência dos outros. Ou seja, duplicamos também o esforço para manter a organização das dependências.
Essa é uma das motivações pricipais pela qual a apache criou o Maven (http://maven.apache.org/).
O Maven é uma ferramenta de integração de projetos. É responsável por gerenciar dependências, controlar versão de artefatos, gerar relatórios de produtividade, garantir execução de testes, manter nível de qualidade do código dentre outras. Muitas pessoas confundem o maven como sendo uma alternativa para o Ant, ou para o Ivy (http://ant.apache.org/ivy/) o que não é verdade. O Maven inclusive disponibiliza a funcionalidade de rodar arquivos do Ant durante o build.
Voltando ao nosso problema citado anteriormente, com o Maven consiguimos isolar as bibliotecas usadas no projeto em um ‘repositório‘ compartilhado pela equipe, ou por toda internet no caso do repositório central do Maven. Dessa forma não nos preocupamos com duplicidade de dependências entre módulos do projeto e nem em disponibilidade das mesmas no repositório de código. Quanto a versão das dependências, estas ficam centralizadas em arquivos de configuração dos projetos de forma explícita e hierarquisada pelos módulos (POM). Com isso o Maven consegue se encarregar de fazer as devidas substituições de bibliotecas e identificar possíveis falhas no grafo de dependências.
O que torna o Maven muito poderoso é a facilidade que ele fornece para se trabalhar com vários módulos de um mesmo sistema e sua extensibilidade para novas funcionalidades com o uso de ‘plugins‘. Existem plugins de geração de código, de integração com plataformas de teste e inclusive suporte a IDEs como Eclipse e NetBeans. Isso torna o projeto muito mais flexível dentro da equipe, pois cada desenvolvedor pode escolher a IDE com que vai trabalhar sem se preocupar em atrapalhar o resto da equipe. Um outro plugin que será visto no post sobre Flex + Maven, é o flexmojos (http://flexmojos.sonatype.org/). Esse plugin é responável por integrar o Flex aos projetos do Maven de maneira transparente, executando o build da mesma forma que nos projetos Java. O plugin também disponibiliza a funcionalidade de preparar o projeto Flex para ser importado no FlexBuilder.
Vamos começar a entender melhor o Maven estudando seu funcionamento.
Fases do build
Algo interessante sobre este post é que o que será abordado é sempre referente a ‘um pouco’, ou ‘um pouco melhor’. Isso porque o maven é uma ferramenta bastante complexa dependendo do nível de customização e da necessidade que se tenha. A primeira parte do post cobre as fases executadas durante o build.
Uma fase nada mais é do que um estágio onde são executadas algumas regras sobre o projeto e se obtem algum resultado no final. Por exemplo, a fase de testes roda os testes da aplicação e obtém um ‘OK’ ou um ‘FAIL’, no segundo caso o build é interrompido. Tais fases são executadas dentro do ‘lifecycle‘ do build. O ‘lifecycle‘ é composto de ‘goals‘ e são estes:
- Validate
- Compile
- Test
- Package
- Verify
- Install
- Deploy
Se você está lembrado do post anterior, usamos o comando do maven com os goals ‘clean‘ ‘install‘. Note que o goal ‘clean‘ não está presente no lifecycle do build, por isso tivemos que invocá-lo na linha de comando além do goal ‘install‘. O ‘clean‘ apenas apaga a pasta target, que como visto no post anterior contém os ‘.class‘ e os artefatos gerados pelo build. Os goals do lifecycle são executados nessa ordem mostrada acima. Um pouco sobre cada goal:
- validate: valida que os ‘poms‘ dos projetos involvidos estão corretamente escritos e que todas as informações necessárias para o build estão presentes;
- compile: compila todos os códigos do projeto, inclusive os códigos das classes de teste;
- test: roda os testes que estão em ‘src/test/java‘, e certifica-se que todos estão passando, caso contrário o build é interrompido;
- package: usa o código compilado e testado que está em ‘src/main/java‘ e cria um arquivo reusável, por exemplo jar;
- integration-test: nesta fase serão rodados os testes que necessitam do jar do projeto ‘deploiado‘ para serem rodados;
- verify: roda as verificações necessárias para se certificar que os pacotes gerados estão corretos e passam nos critérios de qualidade;
- install: copia o arquivo gerado para o repositório local para que esteja disponível localmente para outros projetos;
- deploy: copia o arquivo gerado para um repositório na rede ou remoto, para que esteja disponível para outros desenvolvedores.
Ainda lembrando o exemplo anterior, em um dos comandos executados possuia um goal que não está no lifecycle e que não foi citado: ‘eclipse:eclipse‘. Neste caso não se trata de um goal, mas sim da execução de um ‘plugin‘. ‘Plugins‘ no maven alteram o lifecycle adicionando novos goals em determinadas fases. No caso do ‘eclipse:eclipse‘, estamos invocando o método ‘eclipse‘ no plugin ‘eclipse‘, para isso usamos os ‘:‘. Esse plugin é responsável por criar os arquivos que o Eclipse entende e dessa forma consiga importar o projeto. Não está no objetivo deste post tratar de plugins, por isso se você se interessar pelo assunto existe uma boa documentação no site do próprio maven: http://mojo.codehaus.org/. Plugins do maven são chamados de ‘Mojos‘. Trataremos de ‘plugins‘ em um post futuro.
POM
Project Object Model – se trata do ponto de partida para o maven executar seu lifecycle. Nada mais é do que um arquivo XML que descreve propriedades e características do projeto, como por exemplo a versão do compilador que será usada (exemplo no post anterior):
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<project xmlns=“http://maven.apache.org/POM/4.0.0″ xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd”> <modelVersion>4.0.0</modelVersion> <groupId>br.com.dclick</groupId> <artifactId>aprendendo-maven</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>aprendendo-maven</name> <url>http://maven.apache.org</url> <build> |













