MindView Inc.

Pensando em Java, 3ª ed. Revisão 4.0


[ Viewing Hints ] [ Book Home Page ] [ Free Newsletter ]
[ Seminars ] [ Seminars on CD ROM ] [ Consulting ]

Anterior Próximo Página Inicial Índice Conteúdo

1: Introdução
a Objetos



“Dividimos a natureza em partes, organizando-a em conceitos, e atribuimos significados como o fazemos, principalmente por sermos as partes de um acordo que se sustenta na nossa comunidade verbal e está codificado em nossos padrões lingüísticos ... não podemos conversar senão obedecendo à organização e classificação de dados que o acordo determina.” Benjamin Lee Whorf (1897-1941)



A gênese da revolução dos computadores foi numa máquina. A gênese de nossas linguagens de programação portanto tende a parecer com essa máquina.



Mas os computadores não são mais máquinas do que ferramentas de amplificação da mente (“bicicletas para a mente,” como Steve Jobs gosta de dizer) e um tipo diferente de meio de expressão. Conseqüentemente, as ferramentas estão começando a parecer menos com máquinas e mais com partes de nossas mentes, e também com outras formas de expressão como a escrita, a pintura, a escultura, a animação e o cinema. A programação orientada a objetos (POO) é parte desse movimento em direção ao uso do computador como um meio de expressão. Comentários (em inglês)



Este capítulo introduzirá os conceitos básicos da POO, incluindo uma visão geral de métodos de desenvolvimento. Este capítulo, e este livro, assumem que o leitor já tenha tido experiência com uma linguagem de programação procedural, embora não necessariamente C. Se você acha que precisa de uma melhor preparação na programação e sintaxe de C antes de explorar este livro, você deve trabalhar com o CD ROM Foundations for Java anexo no final do livro. Comentários (em inglês)



Este capítulo apresenta uma base para os estudos, além de material suplementar. Muitas pessoa não se sentem confortávei de se aventurar na programação orientada a objetos sem ter uma visão mais completa do assunto. Dessa forma, há muitos conceitos introduzidos aqui para dar ao leitor uma visão sólida do que é POO. Entretanto, outras pessoas podem não conseguir entender os conceitos mais gerais antes de ver como a mecânica, mais específica funciona; estas pessoas podem ficar bloqueadas e perdidas sem algum código para mexer. Se você fizer parte deste último grupo, e estiver ávido para conhecer a linguagem, fique à vontade para pular este capítulo—pular neste ponto não o impedirá de escrever programas nem de aprender a linguagem. No entanto, em algum ponto, você desejará voltar aqui para completar o seu conhecimento, de forma que possa entender porque os objetos são importantes e como desenvolver com eles. Comentários (em inglês)

O progresso da abstração



Todas a linguagens de programação provêem abstrações. Pode-se argumentar que a complexidade dos problemas passíveis de solução está diretamente ligada ao tipo e à qualidade da abstração. Por “tipo” quero dizer, “O que é que você está abstraindo?”. A linguagem assembly é uma pequena abstração da máquina que a suporta. Muitas das chamadas linguagens “imperativas” que a seguiram (tais como FORTRAN, BASIC, e C) eram abstrações da linguagem assembly. Estas linguagens são melhorias consideráveis em relação à linguagem assembly, mas sua abstração primária ainda requer que se pense em termos da estrutura do computador em vez da estrutura do problema que se tenta resolver. O programador precisa estabelecer a associação entre o modelo da máquina (no “espaço de soluções,” que é onde se modela o problema, no caso um computador) e o modelo do problema que está efetivamente sendo resolvido (no “espaço de problemas,” que é onde o problema existe). O esforço requerido para realizar este mapeamento, e o fato de ele ser extrínseco à linguagem de programação, produz programas difíceis de escrever e caros para manter, e como efeito colateral criou toda a indústria dos “métodos de programação”. Comentários (em inglês)



A alternativa à modelagem da máquina é modelar o problema. Linguagens antigas, como LISP e APL, escolheram visões particulares do mundo (“Todos os problemas são, no final das contas, listas” ou“Todos os problemas são algoritmicos,” respectivamente). PROLOG transforma todos os problemas em cadeias de decisões. Algumas linguagens foram criadas para programação baseada em restrições, outras para programação exclusivamente através da manipulação de símbolos gráficos. (A segunda provadamente restritiva demais.) Cada uma dessas estratégias apresenta uma boa solução para a classe de problemas para a qual são projetadas, mas quando se pisa fora desse domínio, elas ficam complicadas e ineficazes.Comentários (em inglês)



A estratégia da orientação a objetos vai um passo adiante, provendo ferramentas para o programador representar elementos no espaço de problemas. Esta representação é geral o suficiente de forma que o programador não fique restrito a nenhum tipo particular de problema. Referimo-nos aos elementos no espaço de problemas e às suas representações no espaço de soluções como “objetos.” (Você também precisará de outros objetos que não têm análogos no espaço de problemas.) A idéia é que o programa pode se adaptar à linguagem do problema adicionando-se novos tipos de objetos, de forma que, quando se lê o código descrevendo a solução, lê-se palavras que também expressam o problema. Essa é uma abstração de linguagem mais flexível e poderosa do que o que tivemos antes.[2]Assim, a POO permite que se descreva o problema em termos do problema, em vez de fazê-lo em termos do computador onde a solução rodará. Mas ainda há uma conexão com o computador: cada objeto parece um pouco com um pequeno computador—ele tem um estado, e tem operações que se pode pedi-lo para realizar. No entanto, isto não parece uma analogia assim tão ruim a objetos no mundo real—eles têm características e comportamentos. Comentários (em inglês)



Alan Kay resumiu as cinco características básicas do Smalltalk, a primeira líguagem orientada a objeto de sucesso e uma das línguagens no qual o Java foi baseado. Estas características representam a pura aproximação a programação orientada a objeto : Comentários (em inglês)

  1. Tudo é um objeto. Pense num objeto como uma variável imaginária; este armazena dados, mas você pode fazer requisições para o objeto, requisitando a ela para efetuar operações em si mesmo. Em teoria, você pode se apropriar de qualquer componente conceitual do problema que você esta tentando resolver (cachorros, predios, serviços, etc.) e representar este como um objeto em seu programa. Feedback
  2. Um programa é um conjunto de objetos que se comunicam entre eles através da emissão de mensagens. Para fazer a requisição a um objeto, você envia uma mensagem para aquele objeto. Mais concretamente, você pode pensar numa mensagem como a requisição de uma chamada a um método que pertence a um objeto em particular.Feedback
  3. Cada objeto é composto por outros objetos. De outro ponto de vista, você cria um novo tipo de objeto fazendo um pacote contendo objetos já existentes. Assim, você pode construir uma complexidade num programa enquanto esconde esta através da simplicidade dos objetos. Feedback
  4. Cada objeto tem um tipo. Usando o jargão, cada objeto é uma instância de uma classe, em que “classe” é o sinônimo de “tipo”. A mais distinta característica de uma classe é “Que tipo de mensagens você pode enviar para ela?” Feedback
  5. Todos os objetos de um tipo em particular podem receber as mesmas mensagens. Este é atualmente a indicador carregado, como você irá ver mais tarde. Por isto um objeto do tipo “círculo” é também um objeto do tipo “figura”, um círculo garante a aceitar as mensagens da figura. Isto significa que você pode escrever código que fala para a figura e automaticamente interpretar qualquer coisa que se comporte na descrição da figura. Esta substituição é um dos conceitos mais poderosos da POO. Feedback


Booch oferece uma descrição mais sucinta de um objeto:



Um objeto tem estado, comportamento e identidade.



Isto significa que um objeto tem dados internos (que lhe dá estado), métodos (para criar comportamento), e cada objeto pode ser unicamente distingüido de outro objeto—para que isto tenha um senso concreto, cada objeto tem um único endereço de memória.[3] Comentários (em inglês)

Um objeto tem uma interface



Aristóteles foi provavelmente o primeiro a iniciar um estudo cuidadoso do conceito de tipo; Ele falou "a classe de peixes e a classe de pássaros.” A ideia que todos os objetos, enquanto únicos, são também parte de uma classe de objetos que tem características e comportamentos em comum foi usada diretamente na primeira línguagem orientada a objeto, Simula-67, onde introduziu uma nova palavra chave class que introduz um novo tipo num programa. Comentários (em inglês)



Simula, como o seu nome indica, foi criado para o desenvolvimento de simulações como o clássico “problema do caixa de banco.” Neste, você tem um grupo de caixas, clientes, contas, transações e unidades de dinheiro—um monte de “objetos.” Objetos que são identicos exceto pelo seu estado durante uma execução de programa são agrupados juntos em “classes de objetos” é dai de onde a palavra chave class veio. Criando tipos abstratos de dados (classes) é o conceito fundamental na programação orientada a objeto. Tipo de dados abstratos trabalham exatamento como na construção de tipos internos: Você pode criar variáveis do tipo (chamado objetos ou instâncias no jargão da orientação a objetos) e manipular estas variáveis (chamado enviando mensagens ou requisições; você envia uma mensagem e um objeto que a processa). Os membros (elementos) de cada classe compartinha as mesmas coisas em comum: cada conta tem um balanço, cada caixa pode aceitar um depósito, etc. Ao mesmo tempo, cada membro tem seu próprio estado: cada conta tem um diferênte balanço, cada caixa tem um nome. Assim, os caixas, clientes, contas, transações, etc., podem cada um ser representado com uma única entidade num programa de computador. Esta entidade é um objeto, e cada objeto pertence a uma classe particular que define suas características e comportamentos. Comentários (em inglês)



Portanto, muito embora o que realmente fazemos na programação orientada a objetos seja criar novos tipos de dados, virtualmente todas as linguagens orientadas a objetos usam a palavra-chave “class”. Quando vir a palavra “type” pense em “class” e vice-versa.[4] Comentários (em inglês)



Como uma classe descreve um conjunto de objetos que têm características (elementos de dados) e comportamentos (funcionalidade) idênticos, uma classe é na verdade um tipo de dado porque um número em ponto flutuante, por exemplo, também tem características e comportamento. A diferença é que um programador define uma classe para se adequar a um problema em vez de ser forçado a usar um tipo de dado existente que foi desenvolvido para representar uma unidade de armazenamento numa máquina. Você extende a linguagem de programação adicionando novos tipos de dados específicos às suas necessidades. O sistema de programação recebe as novas classes e realiza todo o tratamento e verificação de tipos nela que realiza com os tipos internos. Comentários (em inglês)



A abordagem orientada a objetos não se limita à construção de simulações. Quer você concorde ou não que todo programa é uma simulação do sistema que você está desenvolvendo, o uso de técnicas de POO podem facilmente reduzir um grande conjunto de problemas a uma solução simples. Comentários (em inglês)



Uma vez estabelecida a classe, você pode fazer quantos objetos dessa classe quantos quiser, e então manipular esses objetos como se fossem elementos existentes no problema que você está tentando resolver. Na verdade, um dos desafios da programação orientada a objetos é criar um mapeamento um-para-um entre os elementos no espaço do problema e objetos no espaço de solução. Comentários (em inglês)



Mas como fazer um objeto fazer algo de útil para você? Deve haver alguma maneira de fazer umpedido ao objeto de forma que ele faça algo, tal como completar uma transação, desenhar algo na tela, ou ligar um interruptor. E cada objeto só pode satisfazer certos pedidos. Os pedidos que você pode fazer a um objeto são definidos por sua interface, e o tipo é o que determina a interface, Um exemplo simples pode ser a representação de uma lâmpada:Comentários (em inglês)

PEJ304.png



Light lt = new Light();
lt.on();




A interface estabelece quais pedidos você pode fazer a um objeto específico. Entretanto, precisa haver código em algum lugar para satisfazer o pedido. Isso, junto com os dados ocultos, compõe a implementação. Do ponto de vista da programação procedural, não é tão complicado. Um tipo tem um método associado a cada possível pedido, e quando você faz um dado pedido a um objeto, o método é chamado. Este processo é normalmente sumarizado dizendo que você “envia uma mensagem” (faz um pedido) a um objeto, e o objeto sabe o que fazer com a mensagem (ele executa o código). Comentários (em inglês)



Aqui, o nome do tipo/classe é Light o nome deste objeto Light específico é lt, e os pedidos que você pode fazer a um objeto Light são ligar (on), desligar (off), aumentar a intensidade (brighten), ou diminui-la (dim). Você cria um objeto Light definindo uma "referência" (lt) para o objeto e chamando new para pedir um novo objeto daquele tipo. Para enviar uma mensagem ao objeto, você dá o nome do objeto e o conecta à mensagem através de um ponto. Do ponto de vista do usuário de uma classe pré-definida, basta isso pra programar com objetos. Comentários (em inglês)



O diagrama acima segue o formato da Linguagem de Modelagem Unificada . Cada classe é representada por uma caixa, com o nome do tipo na parte superior da caixa, os membros de dados (se você decidir descrevê-los) na parte do meio, e os métodos (as funções que pertencem a este objeto, que recebem as mensagens que você enviar ao objeto) na parte de baixo. É comum mostrar apenas o nome da classe e os métodos públicos nos diagramas de UML, de forma que a perte do meio não é mostrada. Se você só estiver interessado no nome da classe, então a parte de baixo também não precisa ser mostrada. Comentários (em inglês)

Um objeto presta serviços



Enquanto você está tentando desenvolver ou entender a estrutura de um programa, uma das melhores maneiras de pensar sobre objetos é como “prestadores de serviço.” O próprio programa que você está fazendo prestará serviços ao usuário, e ele fará isso usando os serviços oferecidos por outros objetos. Seu objetivo é produzir (ou melhor ainda, localizar em bibliotecas de código já existentes) um conjunto de objetos que prestem os serviços ideais para resolver o seu problema. Comentários (em inglês)



Uma maneira de começar é perguntar “se eu pudesse tirá-los magicamente de uma cartola, que objetos resolveriam o meu problema imediatamente?” Por exemplo, suponha que você esteja criando um programa de gerenciamento de livros. Você pode imaginar alguns objetos que contenha telas de entrada pré-definidas para o gerenciamento de livros, outro conjunto de objetos que realizem os cálculos do gerenciamento de livros, e um objeto que lide com a impressão de cheques e recibos em vários tipos de impressoras. Talvez alguns desses objetos já existam, e para os que ainda não existam, como seriam? Que serviços esse objetos prestariam, e que objetos eles precisariam para completar suas obrigações? Se continuar fazendo isso, você terminará chegando a um ponto onde poderá dizer “este objeto parece simples o suficiente para eu sentar e escrevê-lo” ou “Tenho certeza que este objeto já deve existir.” Esta é uma maneira razoável de decompor um problema num conjunto de objetos.Comentários (em inglês)



Pensar num objeto como um prestador de serviços tem um benefício adicional: ajuda a melhorar a coesão do objeto. Alta coesão é uma qualidade fundamental do projeto de software: isso significa que os vários aspectos de um componente de software (tal como um objeto, embora também possa se referir a um método ou a uma biblioteca de objetos) “se combinam”. Um dos problemas que as pessoas têm quando projetam um objeto é colocar excessivas funcionalidades em um objeto. Por exemplo, no seu módulo de impressão de cheques, você pode decidir que precisa de um objeto que saiba tudo sobre formatação e impressão. Você provavelmente descobrirá que isso é demais para um único objeto, e que o que você precisa são três ou mais objetos. Um objeto pode ser um catálogo de todos os possíveis layouts de cheques, que possa ser perguntado sobre informações de como imprimir um cheque. Um objeto, ou um conjunto de objetos, poderia ser uma interface de de impressão genérica que saiba tudo sobre os diferentes tipos de impressoras (mas nada sobre gerenciamento de livros—este é um candidato a ser comprado em vez de você escrevê-lo). E um terceiro objeto poderia usar os serviços dos outros dois para realizar a tarefa. Dessa forma, cada objeto tem um conjunto coeso de serviços que oferece. Em um bom projeto orientado a objetos, cada objeto faz uma coisa bem, mas não tenta fazer muito. Como visto aqui, isso não só permite a descoberta de objetos que poderiam ser comprados (o objeto de interface de impressora), como também produz a possibilidade de um objeto que poderia ser usado em outro lugar (o catálogo de layouts de cheques). Comentários (em inglês)



Tratar os objetos como prestadores de serviços é uma ótima ferramenta de simplificação, e também é utilíssimo, não só no processo de projeto como também quando alguém estiver tentando entender o seu código ou reusar um objeto—se a pessoa puder ver o valor do objeto baseado nos serviços que ele presta, fica muito mais fácil encaixá-lo no projeto. Comentários (em inglês)

A implementação oculta



É útil dividir o campo de trabalho em criadores de classescriadores de classes (aqueles que criam novos tipos de dados) e Programadores de aplicações no lado cliente [5](os consumidores das classes, que usam os tipos de dados em suas aplicações). O objetivo do client programmer é juntar uma caixa cheia de classes para usar no desenvolvimento rápido de aplicações. O objetivo do class creator é construir uma classe que exponha somente o que for necessário ao client programmer e mantenha todo o resto oculto. Por quê? Porque se estiver oculto, o client programmer não poderá acessar, o que significa que o class creator pode modificar a porção oculta como bem desejar sem se preocupar com o impacto sobre quem quer que seja. A porção oculta normalmente representa as partes internas, mais sensíveis, de um objeto, as quais poderiam facilmente ser corrompidas por um client programmer descuidado ou desinformado, portanto, ocultar a implementação reduz bugs no programa. Comentários (em inglês)



O conceito da ocultação da implementação não pode ser superenfatizado. Em qualquer relação é importante ter limites que sejam respeitados por todas as partes envolvidas. Quando você cria uma biblioteca, estabelece uma relação com o client programmer, que é um programador também, mas um que está montando uma aplicação usando a sua biblioteca para isso, possivelmente para montar uma biblioteca maior. Se todos os membros de uma classe estiverem disponíveis para todos, então o client programmer poderá fazer qualquer coisa com a classe, sem que haja maneiras de forçar regras. Mesmo que você preferisse que o client programmer não manipulasse diretamente alguns dos membros de sua classe, não há como evitar sem um controle de acesso. Tudo está aberto para o mundo. Comentários (em inglês)



Portanto, a primeira razão para usar controle de acesso é manter as mãos dos client programmers longe das partes que não devem tocar—partes que sejam necessárias à operação interna do tipo de dado, mas não parte das interface que os usuários precisam para resolver os seus problemas específicos. Isto é na verdade um serviço aos usuários, porque eles podem facilmente ver o que é importante e do que podem ignorar. Comentários (em inglês)



A segunda razão para usar controle de acesso é permitir ao desenvolvedor de bibliotecas modificar o funcionamento interno da classe sem se preocupar como isso afetará o client programmer. Por exemplo, você poderia implementar uma dada classe de forma simples para facilitar o desenvolvimento, e depois descobrir que terá que reescrevê-la para fazê-la rodar mais rápido. Se a interface e a implementação estiverem claramente separadas e protejidas, você poderá fazer as modificações com facilidade.Comentários (em inglês)



O Java usa três palavras-chave explicitas para definir os limites em uma classe: public, private, and protected. Seus usos e significados são bem simples e diretos. Estes especificadores de acesso determinam quem poderá usar as definições que as seguem. public significa que o elemento seguinte está disponível para todos. A palavra-chave private por outro lado, significa que ninguém mais pode acessar o elemento exceto você, o criador do tipo, métodos internos do tipo. private é um muro entre você e o client programmer. Alguém que tente acessar um membro private receberá um erro de tempo de compilação. A palavra-chave private receberá um erro de tempo de compilação. A palavra-chave protected age como o private, com a excessão que uma classe herdeira tem acesso aos membros protected, mas não aos membros private. A herança será introduzida em breve. Comentários (em inglês)



Java também possui um acesso “padrão”, que entra em ação se não for utilizado nenhum dos especificadores mencionados acima. Este normalmente é chamado de package access (acesso de pacote) porque as classes podem acessar os membros de outras classes no mesmo pacote, mas fora do pacote aqueles membros parecerão ser private. Comentários (em inglês)

Reutilizando a implementação



Uma vez criada e testada a classe, ela deve (idealmente) representar uma unidade de código útil. Acontece que essa reusabilidade não é assim tão fácil de se atingir como muitos esperariam; é preciso experiência e visão para produzir um projeto de objeto reusável. Uma vez que você tenha este projeto, ele pedirá para ser reusado. O reuso de código é uma das maiores vantagens que as linguagens de programação orientadas a objetos têm. Comentários (em inglês)



A forma mais simples de reutilizar uma classe é usar um objeto da classe diretamente, mas você pode também incluir um objeto de uma classe em uma nova classe. Nós denominamos esta ação de "criar um objeto membro." Sua nova classe pode ser formada por quaisquer quantidades e tipos de objetos, combinados livremente da maneira que você necessite para conferir à sua nova classe a funcionalidade desejada. Como você está compondo uma nova classe a partir de classes existentes, este conceito é denominado composição (costumamos chamar de agregação as composições que ocorrem dinamicamente). A composição é comumente referenciada como uma relação "tem-um", como em "um carro tem um motor." Comentários (em inglês)

PEJ305.png



(Este diagrama UML indica a composição com um losango preenchido, o que indica que existe um carro. Eu usarei tipicamente uma forma mais simples: apenas uma linha, sem o losango, para indicar uma associação.[6]) Comentários (em inglês)



A composição trás grande flexibilidade. Os objetos membros da sua nova classe são tipicamente privados, deixando-os inacessíveis aos client programmers que estiverem usando a classe. Isto permite que você modifique esses membros sem afetar código já existente dos usuários. Você também pode modificar os objetos membros em tempo de execução, para modificar dinamicamente o comportamento do seu programa. A herança, que será descrita a seguir, não tem esta flexibilidade, já que o compilador precisa colocar restrições de tempo de compilação em classes criadas com herança. Comentários (em inglês)



Devido à herança ser tão importante na programação orientada a objetos, ela é com freqüência muito enfatizada, e o programador novato pode ter a idéia de que a herança deve ser usada em todo lugar. Isto pode resultar em projetos ineficazes e muito complicados. Em vez da herança, você deve olhar primeiro para a composição quando estiver criando classes, já que é mais simples e mais flexível. Se você tomar essa estratégia, seus projetos ficarão mais claros. Quando você tiver tido mais experiência, será razoavelmente óbvio quando estiver precisando de herança. Comentários (em inglês)

Herança:
reusando a interface



Sozinha, a idéia de um objeto é uma ferramenta conveniente. Ela permite a você empacotar dados e funcionalidade juntos conceitualmente, assim você pode representar uma idéia do espaço do problema apropriadamente em lugar de ser forçado a usar os idiomas do mecanismo de sustentação. Estes conceitos são expressados com unidades funcionais na linguagem de programação através da utilização da palavra chave class.Comentários (em inglês)



Seria uma pena, entretanto, ter todo o trabalho de criar uma classe e então ser forçado a criar uma outra, nova, com funcionalidade similar. Seria melhor se pudéssemos pegar a classe existente, cloná-la, e então fazer adições ou modificações ao clone. Isto é efetivamente o que você obtém com a herança, com a exceção de que se a classe original (diz-se a classe base ou superclasse ou classe pai) for modificada, o “clone” alterado (diz-se a classe derivada ou classe herdeira ou subclasse ou classe filha) também reflete essas mudanças. Comentários (em inglês)

PEJ306.png



(A seta neste diagrama UML aponta de uma classe derivada para uma classe base. Como você irá ver, existe mais frequentemente do que uma classe derivada.) Comentários (em inglês)



Um tipo de dado realiza mais do que simplesmente descrever as restrições em um conjunto de objetos; ele também possui um relacionamento com outros tipos de dados. Dois tipos de dados podem ter características e comportamentos em comum, mas um tipo pode ter mais características do que outro e pode também manipular mais mensagens (ou manipulá-las de modo diferente). A herança expressa esta semelhança entre tipos de dados pelo uso dos conceitos de tipos base e tipos derivados. Um tipo base contém todas as características e comportamentos que são compartilhados pelos tipos derivados a partir dele. Você cria um tipo base para representar suas idéias centrais sobre algum objeto de seu sistema. Do tipo base, você deriva outros tipos para expressar as diferentes maneiras pelas quais aquelas idéias centrais se concretizam.Comentários (em inglês)



Por exemplo, uma máquina de reciclagem de lixo classifica pedaços de lixo. O tipo base é "lixo", e cada pedaço de lixo tem seu peso, valor, e assim por diante, e cada pedaço de lixo pode ser triturado, fundido, ou decomposto. Deste tipo básico, tipos mais específicos de lixo são derivados, os quais podem ter características adicionais (uma garrafa tem uma cor) ou comportamentos (alumínio pode ser compactado, aço é magnetizável). Além disso, alguns comportamentos podem ser diferentes (o valor do papel depende de seu tipo e condição). Usando herança, você pode construir uma hierarquia de tipos que expresse o problema que você esta tentando resolver em termos dos tipos existentes no problema.Comentários (em inglês)



Um segundo exemplo é o clássico exemplo da "forma", possivelmente usado em um sistema de desenho com auxílio do computador (CAD) ou simulação de jogos. O tipo base é "forma", e cada forma tem uma medida, uma cor, uma posição e assim por diante. Cada forma pode ser desenhada, apagada, movida, colorida, etc. Deste tipo básico, tipos específicos de formas são derivados (herdados) - círculo, quadrado, triângulo, e assim por diante - cada um dos quais pode ter características e comportamentos adicionais. Certas formas podem ser invertidas, por exemplo. Alguns comportamentos podem ser diferentes, como quando você tenta calcular a área de uma forma. A hierarquia de tipos incorpora e expressa a ambas, similaridades e diferenças entre as formas. Comentários (em inglês)

PEJ307.png



Modelar a solução nos mesmos termos do problema é tremendamente benéfico porque você não precisa de uma porção de modelos intermediários para ir da descrição do problema à descrição da solução. Com os objetos, a hierarquia de tipos é o modelo primário, de modo que você vai diretamente da descrição do sistema no mundo real para a descrição do sistema em código. De fato, uma das dificuladades que as pessoas têm com o design orientado a objetos é que com ele é muito simples ir do ponto de partida ao ponto final. Uma mente treinada para procurar por soluções complexas pode inicialmente ficar embaraçada com esta simplicidade.Comentários (em inglês)



Quando você herda de um tipo existente, você cria um novo tipo. Este novo tipo contém não somente todos os membros do tipo existente (embora aqueles declarados como private estejam ocultos e inacessíveis), mas, o que é mais importante, ele duplica a interface da classe base. Quer dizer, todas as mensagens que você pode enviar para objetos da classe base você também pode enviar para objetos da classe derivada. Visto que conhecemos o tipo de uma classe através das mensagens que podemos enviar para ela, isto significa que a classe derivada é do mesmo tipo da classe base. Esta equivalência entre tipos via herança é um dos portais de acesso fundamentais para a compreensão do significado da programação orientada a objetos.Comentários (em inglês)



Visto que a classe base e a classe derivada têm a mesma interface fundamental, deve haver alguma implementação que acompanhe esta interface. Ou seja, tem que existir algum código para ser executado quando um objeto recebe uma determinada mensagem. Se você simplesmente herda uma classe e não faz nada além disso, os métodos da interface da classe base vêm junto, direto para dentro da classe derivada. O que significa que objetos da classe derivada não têm somente o mesmo tipo, eles também têm o mesmo comportamento, o que não é particularmente interessante.Comentários (em inglês)



Você tem duas maneiras de diferenciar sua nova classe derivada da classe base original. A primeira é absolutamente clara: Você simplesmente adiciona métodos novos à classe derivada. Estes novos métodos não fazem parte da interface da classe base. Isto significa que a classe base simplesmente não faz tudo o que você gostaria que ela fizesse, logo você adicionou mais métodos. Este uso simples e primitivo para a herança é, às vezes, a solução perfeita para o seu problema. Entretanto, você deveria considerar atentamente a possibilidade que que a sua classe base talvez necessite destes métodos adicionais. Este processo de descoberta e repeticão de seu projeto acontece regularmente na programação orientada a objetos.Comentários (em inglês)

PEJ308.png



Embora a herança possa às vezes significar (especialmente em Java, em que a palavra reservada para herança é extends) que você vá adicionar novos métodos à interface, isto não é necessariamente verdade. A segunda e mais importante maneira de diferenciar sua nova classe é mudar o comportamento de um método já existente de uma classe base. Isto é conhecido como uma sobrecarga daquele método.Comentários (em inglês)

PEJ309.png



Para sobreescrever um método, você simplesmente cria uma nova definição para o método numa classe derivada. Você esta dizendo, “Eu estou usando a mesma método da interface aqui, mas eu quero que isto faça algo diferente para o meu novo tipo.” Comentários (em inglês)

Relacionamentos é um vs. é como um



Existe uma certa polêmica que pode acontecer quando se trata de herança: A herança deveria sobrecarregar apenas métodos da classe base (e não adicionar novos métodos que não estejam na classe base)? Isto significaria que o tipo derivado é exatamente o mesmo tipo da classe base já que ele tem exatamente a mesma interface. Como resultado, você pode substituir um objeto da classe derivada exatamente por um objeto da classe base. Isto pode ser encarado como pura substituição, e é frequentemente referido como o princípio da substituição. Em um certo sentido, esta é a maneira ideal de tratar a herança. Nós frequentemente nos referimos à relação entre a classe base e a classe derivada, neste caso, como uma relação é-um, porque você pode dizer "um círculo é uma forma." Um teste para a herança é determinar se faz sentido você expressar como é-um ou é-uma a relação entre as classes. Comentários (em inglês)



Há vezes em que você deve adicionar novos elementos de interface à uma classe derivada, deste modo estendendo a interface e criando um novo tipo. O novo tipo pode ainda ser substituído pelo tipo base, mas a substituição não é perfeita porque seus novos métodos não são acessíveis a partir da classe base. Isto pode ser descrito como uma relação é-como-um (minha terminologia). O novo tipo tem a interface do antigo tipo mas ele também contém outros métodos, assim, você não pode realmente dizer que ele é exatamente a mesma coisa. Por exemplo, considere um refrigerador de ar. Suponha que sua casa tem instalados todos os controles para refrigeração; quer dizer, ela tem uma interface que permite que você controle a refrigeração. Imagine que o refrigerador de ar quebre e que você o substitua por um condicionador de ar, que pode tanto refrigerar quanto aquecer. O condicionador de ar é-como-um refrigerador de ar, mas ele pode fazer mais. Já que o sistema de controle da sua casa é projetado somente para controlar a refrigeração, ele está restrito a se comunicar apenas com a parte refrigeradora do novo objeto. A interface do novo objeto foi estendida, e o sistema existente não reconhece nada além da interface original. Comentários (em inglês)

PEJ310.png



Naturalmente, uma vez que você olhe para este projeto, se torna claro que a classe base "cooling system" (sistema de refrigeração) não é suficientemente genérica, e deveria ser renomeada como "temperature control system" (sistema de controle de temperatura) de maneira que pudesse incluir também o aquecimento - ponto no qual o princípio da substituição vai funcionar. Entretanto, este diagrama é um exemplo do que pode acontecer com projetos no mundo real. Comentários (em inglês)



Quando você vê o princípio da substituição, é natural ter a percepção como se esta técnica (substituição pura) fosse a única maneira de fazer as coisas e, de fato, é legal se seu projeto funcionar desta maneira. Mas você vai descobrir que há vezes em que é igualmente claro que você deve adicionar novos métodos à interface de uma classe derivada. Com a inspeção ambos os casos deveriam ficar razoavelmente óbvios. Comentários (em inglês)

Objetos intercambiáveis
através do polimorfismo



Quando estiver lidando com hieraquia de tipos, você frequentemente precisará tratar um objeto não como do tipo específico dele, mas, em vez disso, como de seu tipo base. Isto permite a você escrever código que não depende de tipos específicos. No exemplo da forma, métodos manipulam formas genéricas sem levar em conta que sejam círculos, quadrados, triângulos, ou alguma outra forma que sequer tenha sido definida ainda. Todas as formas podem ser desenhadas, apagadas e movidas, assim, esses métodos simplesmente enviam uma mensagem para um objeto forma, sem se preocupar sobre como o objeto lida com a mensagem. Comentários (em inglês)



Tal código não é afetado pela adição de novos tipos, e adicionar novos tipos é o modo mais comum de estender um programa orientado a objetos para que ele manipule novas situações. Por exemplo, você pode derivar um novo subtipo de forma chamado pentágono sem modificar os métodos que lidam somente com formas genéricas. Esta habilidade de facilmente estender um projeto derivando novos subtipos é uma das maneiras essenciais de encapsular alterações. Isto melhora consideravelmente os projetos ao mesmo tempo que reduz o custo de manutenção de software. Comentários (em inglês)



Há um problema, entretanto, com a tentativa de tratar objetos de tipos derivados como seus tipos básicos genéricos (círculos como formas, bicicletas como veículos, cormorões como pássaros, etc.). Se um método vai pedir para uma forma genérica desenhar-se a si mesma, ou para que um veículo genérico tome direções, ou a um pássaro genérico para se mover, o compilador não tem como saber em tempo de compilação exatamente qual pedaço de código será executado. Esta é toda a questão - quando a mensagem é enviada, o programador não precisa saber qual pedaço de código será executado; o método desenhar pode ser aplicado igualmente a um círculo, um quadrado ou um triângulo, e o objeto executará o código apropriado dependendo de seu tipo específico. Se você não tem que saber qual pedaço de código será executado, então quando você adiciona um novo subtipo, o código que ele executa pode ser diferente sem que isso requeira mudanças na chamada do método. Assim, o compilador não precisa saber exatamente qual pedaço de código é executado, então, o que ele faz? Por exemplo, no diagrama seguinte o objeto BirdController simplesmente funciona com objetos Bird genéricos e não sabe de que tipo eles são exatamente. Isto é conveniente do ponto de vista de BirdController porque ele não tem que escrever código específico para determinar o tipo exato de Bird com que ele está trabalhando ou qual o comportamento daquele Bird. Assim, como isso se dá, quando move( ) é chamado enquanto ignora o tipo específico de Bird, o comportamento correto irá ocorrer (um Ganso corre, voa ou nada, e um Pinguim corre ou nada)? Comentários (em inglês)

PEJ311.png



A resposta é a trança primária na programação orientada a objetos: o compilador não pode fazer uma chamada de função da forma tradicional. A chamada de função gerada por um compilador não-POO causa o que chamamos de early binding (ligação atual), um termo que talvez você ainda não conheça por nunca ter pensado nisto de qualquer outra forma. Significa que o compilador gera uma chamada a um nome de função específico e o linker traduz esta chamada ao endereço absoluto do código a ser executado. Em POO, o programa não pode determinar o endereço do código antes do tempo de execução, então algum outro esquema é necessário quando uma mensagem é enviada para um objeto genérico.Comentários (em inglês)



Para resolver o problema, linguagens orientadas a objeto utilizam o conceito de late binding (ligação tardia). Quando você envia uma mensagem para um objeto, o código inicialmente chamado não é determinado antes do tempo de execução. O compilador se assegura que o método existe e executa checagem de tipo nos argumentos e valor de retorno (uma linguagem na qual isto não é verdade é chamada de fracamente tipada), mas não sabe qual seria o codigo exato a ser executado. Comentários (em inglês)



Para realizar ligação tardia, Java usa um pedaço especial de código em lugar da chamada absoluta. Este código calcula o endereço do corpo do método, usando informações armazenadas no objeto (este processo é coberto com grande detalhamento no Capítulo 7). Deste modo, cada objeto pode se comportar de maneira diferente, de acordo com o conteúdo daquele pedaço especial de código. Quando você envia uma mensagem para um objeto, o objeto realmente não tem idéia do que fazer com aquela mensagem. Comentários (em inglês)



Em algumas linguagens você tem que declarar explicitamente que você quer que um método tenha a flexibilidade da propriedade da ligação tardia (C++ usa a palavra reservada virtual para fazer isto). Nestas linguagens, por padrão, os métodos não são dinamicamente ligados. Em Java, ligação dinâmica é o comportamento padrão e você não precisa lembrar de adicionar qualquer palavra reservada extra para obter polimorfismo. Comentários (em inglês)



Considere o exemplo da forma. A família de classes (todas baseadas na mesma interface uniforme) foi desenhada anteriormente neste capítulo. Para demonstrar o polimorfismo, precisamos escrever um único pedaço de código que ignora os detalhes específicos de tipo e que somente se comunica com a classe base. Este código é despido de informações específicas do tipo e assim é mais simples de escrever e mais fácil de entender. E, se um novo tipo - um Hexágono, por exemplo - for adicionado através de herança, o código que você escrever simplesmente funcionará tão bem para o novo tipo de Forma quanto funcionou nos tipos que já existiam. Deste modo, o programa é extensível. Comentários (em inglês)



Se você escrever um método em Java (o que você logo aprenderá a fazer):Comentários (em inglês)



void doStuff(Shape s) {
  s.erase();
  // ...
  s.draw();
}




Este método fala com qualquer Shape, assim é independente do tipo específico de objeto que ele desenha ou apaga. Se outras partes do programa utilizarem o método doStuff( ): Comentários (em inglês)



Circle c = new Circle();
Triangle t = new Triangle();
Line l = new Line();
doStuff(c);
doStuff(t);
doStuff(l);




as chamadas para doStuff( ) automaticamente funcionam corretamente, sem levar em consideração o tipo exato do objeto. Comentários (em inglês)



Isto é um truque particular inacreditável. Considere a linha:



doStuff(c);




Oque acontece aqui é que um Circle está sendo passado para um método que está aguardando um Shape. Como um Circle é um Shape ele pode ser tratado como um por doStuff( ). Isto é, qualquer mensagem que doStuff( ) puder remeter para um Shape, um Circle pode aceitar. Assim é completamente seguro e a coisa lógica a fazer. Comentários (em inglês)



Nós chamamos este processo de tratar um tipo derivado como se ele fosse seu tipo base de upcasting. O nome cast é usado no sentido de atirar em um molde e o up vem da maneira como o diagrama de herança é normalmente arrumado, com o tipo base no topo e as classes derivadas distribuidas mais abaixo. Então, converter para um tipo base é mover para cima no diagrama de herança: “upcasting.” Comentários (em inglês)

PEJ312.png



Um programa orientado a objeto contêm alguns upcasting em algum lugar, porque estes são como você amaina exigências com você mesmo do conhecimento a cerca do tipo exato com o qual está trabalhando. Observe o código em in doStuff( ): Comentários (em inglês)



  s.erase();
  // ...
  s.draw();




Observe que não diz “Se você é um Circle, faça isso, se você for um Square, faça aquilo, etc.” Se você escreve este tipo de código, o qual verifica por todos os tipos possíveis que um Shape pode ser no momento, é confuso e você precisa alterá-lo toda vez que você adiciona um novo tipo de Shape. Aqui, você só diz “Você é um shape, Eu sei que você pode erase( ) e draw( ) a você mesmo, faça isso, e tome cuidado dos detalhes corretamente.” Comentários (em inglês)



Oque é impressionante sobre o código em doStuff( ) é que, de certo modo, a coisa certa acontece. Chamando draw( ) para Circle causa código diferente a ser executado que quando chamando draw( ) para um Square ou um Line, mas quando a mensagem draw( ) é remetida para um Shape anônimo, o comportamento correto ocorre baseado no tipo atual de Shape. Isto é incrível porque, como mencionado mais cedo, quando o compilador Java está compilando o código para doStuff( ), ele não pode saber exatamente com que tipos está negociando. Assim ordinariamente, você aguardaria o fim para chamar a versão de erase( ) e draw( ) para a classe base Shape, e não para um específico Circle, Square, ou Line. E ainda a coisa certa acontece por causa do polimorfismo. O compilador e o sistema de execução manuseiam os detalhes; tudo que você precisa por agora é saber que ele faz acontecer, e mais importante, como projetar com ele. Quando você envia uma mensagem para um objeto, o objeto fará a coisa certa, mesmo quando um upcasting estiver envolvido. Comentários (em inglês)

Classes base abstratas e interfaces



Frequentemente em um projeto, você quer a classe base apresente somente uma interface para suas classes derivadas. Isto é, você não quer que alguém crie agora um objeto da classe base, somente para moldá-la assim como sua interface pode ser usada. Isto é complementado por tornar aquela classe abstract pelo uso da palavra chave abstract . Se alguém tentar fazer um objeto de uma classe abstract, o compilador o previne. Esta é uma ferramenta para reforçar um projeto particular. Comentários (em inglês)



Você pode também usar a palavra chave abstract para descrever um método que ainda não foi implementado—como uma seta indicando “aqui está um método de interface para todos os tipos herdados desta classe, mas até este momento eu não tenho qualquer implementação para ele.” Um método abstract pode ser criado somente dentro de uma classe abstract . Quando a classe é herdada, aquele método deve ser implementado, ou a classe herdante se torna abstract também. Criar um método abstract permite a você colocar um método em uma interface sem ser forçado a providenciar um possível corpo de código sem significado para aquele método. Comentários (em inglês)



A palavra chave interface leva o conceito de uma classe abstract um passo adiante para prevenção de quaisquer definições de método como um todo. A interface é uma ferramenta muito útil e comumente usada, pois ela provê a separação perfeita da interface e da implementação. Em adição, você pode combinar muitas interfaces juntas, se você desejar, enquanto que herança de múltiplas classes regulares ou abstratas não é possível. Comentários (em inglês)

Criação de objetos, uso e tempo de vida



Tipicamente a Programação Orientada a Objetos aborda assuntos como tipo de dados abstratos, herança e polimorfismo, bem como outras características importantes. Esta seção cobrirá essas características.Comentários (em inglês)



Um dos fatores mais importantes sobre objetos é a maneira que eles são criados e distribuídos. Onde está o dado de um objeto e como é controlado o tempo de vida do objeto? Há diferentes filosofias no trabalho aqui. C++ tem uma abordagem onde o controle de performance é a mais importante característica, então C++ dá ao programador uma opção de escolha. Para otimizar a velocidade de execução, o armazenamento e o tempo de vida podem ser determinado enquanto o programa está sendo escrito, através da alocação (criação) dos objetos na memória (estes são algumas vezes chamados automáticos ou variáveis de escopo) ou em uma área estática de armazenamento. Isto coloca uma prioridade na velocidade de alocação e liberação de armazenamento, e o controle da alocação e desalocação podem ser muito válido em algumas situações. Entretanto, a flexibilidade pode ser prejudicada porque é necessário saber a quantidade exata, tempo de vida, e tipo dos objetos enquanto você está escrevendo o programa. Se você estiver tentando resolver um problema mais geral como o desenvolvimento auxiliado por computador, gerenciamento de estoque, ou o controle de tráfego aéreo, isto é também específico.Comentários (em inglês)



Uma segunda característica é a criação de objetos dinamicamente em um pool de memória chamada de “memória heap”. Nesta abordagem, você não sabe até o momento da execução quantos objetos você precisará, qual o tempo de vida, ou quais os tipos exatos destes objetos. Tudo isso é determinado exatamente no momento da execução. Se você precisar de um novo objeto, você simplesmente o aloca na “memória heap” na hora que o objeto for necessário. Pelo motivo de o armazenamento ser gerenciado dinamicamente, no tempo de execução, a quantidade de tempo necessário para alocar na “memória heap” pode ser notavelmente maior que o tempo para criar armazenamento no “memória stack”. (Criação de armazenamento na “memória stack”é frequentemente uma única montagem de instrução para mover o ponteiro da “memória stack” para baixo e outra para volta-lo ao início. O tempo de criação do armazenamento na “memória heap” depende do mecanismo de armazenamento projetado). A abordagem dinâmica geralmente supõe que os objetos tendem a ser complicados, então o esforço extra de encontrar o armazenamento e liberá-lo não terá um impacto importante na criação do objeto. Em adição, uma maior flexibilidade é essencial para resolver os problemas gerais de programação.Comentários (em inglês)



Java usa a segunda abordagem, exclusivamente. [7]Cada vez que você quer criar um objeto, você usa a palavra reservada new para criar uma instância do objeto.Comentários (em inglês)



Há outra característica, que é o tempo de vida de um objeto. Como em linguagens que permitem que o objeto seja criado na “memória stack”, o compilador determina em quanto tempo os últimos objetos serão automaticamente destruídos. Entretanto se você criá-los na “memória heap” o compilador não tem conhecimento do tempo de vida destes objetos. Em uma linguagem como C++, você deve determinar programaticamente quando destruir o objeto, o que pode levar a uma invasão de memória se você não fizer corretamente (e isto é um problema comum em programas C++). Java fornece uma característica chamada garbage collector que automaticamente descobre quando um objeto não está mais em uso e o destrói. O “garbage collector” é muito mais conveniente porque ele reduz o número de casos que você pode acompanhar e o código que você deve escrever. Mais importante, o "garbage collector" fornece um nível muito alto de segurança que vai de encontro ao problema de invasão de memória (que tem levado muitos projetos em C++ ao fracasso).Comentários (em inglês)

Coleções e iterações



Se você não sabe quantos objetos você precisará para solucionar um problema em particular ou qual o tempo de vida deles, você também não saberá como armazená-los. Como você pode saber qual o espaço necessário para criar tais objetos? Você não pode, levando em conta que esta informação não é conhecida até o momento da execução. Comentários (em inglês)



A solução para a maioria dos problemas em desenvolvimento orientado a objetos parece impertinente: você cria outro tipo de objeto. O novo tipo de objeto que resolve este problema em particular manipula a referência para outros objetos. Naturalmente, você pode fazer a mesma coisa com “array”, que existe na maioria das linguagens. Mas este novo objeto, chamado de container (também chamado de collection, mas a biblioteca Java usa este termo de uma forma diferente então neste livro usaremos "container"), irá se expandir sempre que necessário para acomodar tudo que você acrescentar nele. Então você não precisa saber quantos objetos você vai manipular em um “container”. É só criar um objeto “container” e deixá-lo cuidar dos detalhes.Comentários (em inglês)



Felizmente, uma boa linguagem de Programação Orientada a Objetos vem com um conjunto de “containers” como parte do pacote. Em C++, é parte da Biblioteca Padrão C++ e algumas vezes chamado de “Standard Template Library (STL)”. Object Pascal tem “containers” na “Visual Component Library (VCL)”. Smaltalk tem um conjunto muito completo de “containers”. Java também tem “containers” em sua biblioteca padrão. Em algumas bibliotecas, um “container” genérico é considerado bom o suficiente para tudo o que for necessário, e em outras (Java, por exemplo) a biblioteca tem diferentes tipos de “containers” para diferentes necessidades: vários tipos diferentes de classes List (para manipular seqüências), Map (também conhecidos como arrays associativos, para associar objetos com outros objetos), e classes Set (para manipular cada um dos tipos de objetos). As bibliotecas de “container” também devem incluir “queues” (filas), “trees” (árvores), “stacks” (pilhas), etc.Comentários (em inglês)



Todos os “containers” disponibilizam maneiras de se colocar coisas nele e recuperá-las; normalmente através de métodos que adicionam elementos ao “container”, e outros que removem elementos do “container”. A recuperação de elementos pode ser mais problemático, porque o método de seleção simples é restritivo. O que fazer se você quiser manipular ou comparar um conjunto de elementos em um “container” ao invés de somente um elemento?Comentários (em inglês)



A solução é um iterador, que é um objeto que tem como função selecionar os elementos em um container e apresentá-los ao usuário do iterador. Como uma classe, ele também provê um nível de abstração. Esta abstração pode ser utilizada para separar os detalhes do container dos detalhes do código que estão acessando o container. O container, via iterador, é abstraído para ser simplesmente uma seqüência. O iterador permite percorrer uma seqüência sem se preocupar com a estrutura base — isto é, será um ArrayList, um LinkedList, um Stack, ou algo parecido. Isto dá flexibilidade para facilmente mudar a estrutura base de dados sem interferir no código de seu programa. Java iniciou (nas versões 1.0 e 1.1) com um padrão de iterador, chamado Enumeration, para todas as classes container. Java 2 incluiu uma biblioteca container muito mais completa, que contém um iterador chamado Iterator, que faz mais que o velho Enumeration. Comentários (em inglês)



Do ponto de vista de projeto, tudo que você realmente quer é uma seqüência que pode ser manipulada para resolver seu problema. Se um tipo simples de seqüência satisfizer tudo o que você precisar, não haverá razão para ter tipos diferentes. Há 2 razões que podem levar você a escolher entre “containers”. Primeiro, os “containers” fornecem diferentes tipos de interface e comportamento externo. Uma “stack” (pilha) tem uma interface diferente de uma “queue” (fila), que é diferente do “set” ou da “list”. Um deles pode fornecer uma solução mais flexível para resolver seu problema que outra. Segundo, “containers” diferentes têm performances diferentes para certas operações. O melhor exemplo compara dois tipos de List: um ArrayList e um LinkedList. Ambos são seqüências simples que podem ter interfaces idênticas e comportamentos externos. Mas certas operações podem ter custos radicalmente diferentes. Aleatoriamente acessando elemento em um ArrayList é uma operação de tempo constante; ele gasta o mesmo tempo desconsiderando o elemento que você seleciona. Porém, um LinkedList é expansivo para percorres a lista a fim de randomicamente selecionar um elemento, e demora para encontrar um elemento que está mais no final da lista. Em contrapartida, se você quiser inserir um elemento no meio da seqüência, isto é mais fácil em um LinkedList que em um ArrayList. Essas e outras opções têm diferentes performances dependendo da estrutura base da seqüência. Na fase de projeto, você pode iniciar com uma LinkedList e, quando buscando melhorar a performance, alterar para um ArrayList. Por causa da abstração da classe base List e dos “iterators”, você pode mudar de um para o outro com impacto mínimo no seu código.Comentários (em inglês)

Fundamentos de hierarquia



Uma das características da Programação Orientada a Objeto que tem sido especialmente destacada desde a introdução do C++ é que todas as classes devem herdar de uma única classe base. Em Java (bem como todas as outras linguagens de Programação Orientada a Objeto, exceto para C++) a resposta é sim, e o nome desta classe base é Object. Isto demonstra que os benefícios da hierarquia são muitos.Comentários (em inglês)



Todos os objetos na árvore hierárquica têm uma interface em comum, então todos eles são, no final das contas, os mesmos tipos base. Uma alternativa (fornecida por C++) é que você não sabe que todos possuem o mesmo tipo base. Do ponto de vista de compatibilidade interna isto cabe no módulo C melhor e pode ser considerado menos restritivo, mas quando é necessário que a programação seja completamente orientado a objeto você deve construir sua própria hierarquia para fornecer a mesma conveniência que usa em outras linguagens de Programação Orientada a Objeto. E em qualquer biblioteca de classe nova que você adquire, algumas outras interfaces incompatíveis serão usadas. É necessário esforçar (possivelmente como herança múltipla) para funcionar com nova interface no seu projeto. É uma “flexibilidade” extra de valor de C++? Se você precisar - um grande investimento em C - é bastante válido. Se você estiver começando a arriscar, outras alternativas como Java podem ser mais produtivas.Comentários (em inglês)



Todos os objetos em uma árvore hierárquica (como fornecido por Java) garantidamente possue certas funcionalidades. Você sabe pode executar algumas operações básicas em todo objeto no seu sistema. Uma árvore hierárquica, quando cria todos os objetos na memória, simplifica extremamente a passagem de argumentos (um dos tópicos mais complexos em C++).Comentários (em inglês)



Uma árvore hierárquica torna muito mais fácil a implementação de um “garbage collector” (que já é convenientemente implementado em Java). O suporte necessário pode ser instalado na classe base, e o ”garbage collector” pode então enviar as mensagens apropriadas para todos os objetos no sistema. Sem a árvore hierárquica em um sistema que manipula um objeto através da referência, é difícil implementar o “garbage collector” Comentários (em inglês)



No tempo de execução, a informação do tipo é garantida em todos os objetos, você nunca finalizará os objetos sem que eles tenham seus tipos definidos. Isto é importante especialmente com operações de nível de sistema, como a manipulação de exceção, a fim de permitir maior flexibilidade no programa.Comentários (em inglês)

Conversões versus genéricos



Para tornar esses “containers” reutilizáveis, eles manipulam o tipo universal em Java: Object. A árvore hierárquica significa que tudo herda de Object, então o “container” que manipula Object pode manipular qualquer outra classe.[8]Isto torna os “containers” fáceis de reutilizar.Comentários (em inglês)



Para usar um “container”, você simplesmente acrescenta referências para objetos e mais tarde solicita a remoção dos mesmos. Mas, desde que o “container” manipula somente Object, quando você adiciona sua referência no “container” ele é promovido para Object, perdendo assim a identidade. Quando você recupera o objeto, você obtém uma referência de Object, e não a referência para o tipo que você colocou. Então como você recupera alguma coisa que tem uma interface útil do objeto que você colocou no container?Comentários (em inglês)



Aqui, o “cast” (conversão) é usado novamente, mas desta vez você não está elevando a herança para um tipo mais geral. Ao contrário, você está convertendo para um tipo mais específico. Esta maneira de conversão é chamada downcasting(conversão p/ mais específico). Com a conversão para mais genérico, você sabe, por exemplo que um Circle (círculo) é um tipo de Shape (forma geométrica) então é seguro uma conversão para o mais genérico, mas você não sabe se um Object é necessariamente um Circleou um Shape então não é seguro fazer uma conversão para o específico, a menos que você saiba exatamente com o que você está lidando.Comentários (em inglês)



Isto não é completamente impraticável, entretanto, se você fizer uma conversão para o específico para um tipo errado você terá um erro em tempo de execução chamado exception,, que será descrito brevemente. Quando você recupera referências de objeto de um container, então, você deve ter alguma maneira de lembrar exatamente o que eles são e como você pode executar uma conversão para o tipo específico de forma apropriada. Comentários (em inglês)



A conversão para o tipo específico e a verificação em tempo de execução necessita de tempo extra para executar o programa e esforço extra do programador. Não teria problema se quando criar o “container”, ele souber os tipos que ele manipula, eliminando a necessidade de conversão para o tipo específico como possível engano? A solução é chamada como mecanismo de tipo parametrizado. Um tipo parametrizado é uma classe que o compilador pode automaticamente ser customizado para funcionar com tipos particulares. Por exemplo, com um “container” parametrizado, o compilador deve customizar o “container” para que ele aceite só Shapee recupere somente Shapes. Comentários (em inglês)



Tipos parametrizados é uma parte importante de C++, principalmente porque C++ não tem hierarquia. Em C++, a palavra chave que implementa tipos parametrizados é “template”. Java atualmente não tem tipos parametrizados desde que seja possível ser recuperado usando herança simples. Entretanto, o propósito atual dos tipos parametrizados usa uma sintaxe que é extremamente similar a C++, e nós podemos esperar que tipos parametrizados (que serão chamados genéricos) na própria versão de Java. Comentários (em inglês)

Assegurando uma limpeza apropriada



Todo objeto requer recursos a fim de existir, sendo o principal deles memória. Quando o objeto não é mais necessário o mesmo deve ser extinto (retirado ou limpado da memória) para que estes recursos sejam liberados para reuso. Em situações de programação a questão sobre como um objeto é retirado (limpado) não parece muito desafiador: você cria um objeto, usa o objeto enquanto for necessário, então o mesmo deve ser destruído. Entretanto, não é difícil de encontrar situações que são mais complexas.Comentários (em inglês)



Suponha, por exemplo, que você esteja projetando um sistema para gerenciar o tráfico aéreo para um aeroporto. (O mesmo modelo deve também funcionar para outros exemplos.) Inicialmente isto parece simples: Faça um contêiner para gerenciar os aviões, então crie um novo avião e coloque o mesmo no contêiner para cada avião que entrar na zona de controle de tráfego aéreo. Para limpar, simplesmente delete o objeto avião apropriado quando a aeronave deixar a zona.Comentários (em inglês)



Mas quem sabe você tem algum outro sistema para gravar dados sobre as aeronaves; quem sabe dados que não requerem atenção imediata como a função de controle principal. Talvez estes registros são de todas as pequenas aeronaves que deixam o aeroporto. Então você tem um contêiner secundário para aeronaves pequenas, e a qualquer momento em que você criar um objeto aeronave você também o colocará no contêiner secundário se este é uma aeronave pequena. Então alguns processos secundários executam operações nos objetos neste contêiner durante momentos ociosos.Comentários (em inglês)



Agora o problema é mais difícil: Como você pode possivelmente saber quando destruir os objetos? Quando você tiver parado de usar o objeto, alguma outra parte do sistema poderia ainda o estar usando. O mesmo problema pode ser levantado em um número de outras situações, e na programação de sistemas (como os em C++) nos quais você deve excluir explicitamente um objeto quando você tiver terminado e isto pode se tornar bastante complexo. Comentários (em inglês)



Com Java, o coletor de lixo é projetado para tomar conta do problema da liberação de memória (embora isto não inclua outros aspectos da limpeza de objetos). O coletor de lixo “sabe” quando um objeto não é usado a muito, e então libera automaticamente a memória daquele objeto. Isto (combinado com o fato de que todos os objetos são herdados da classe raiz única Object e que você pode criar objetos somente de uma maneira—na pilha) torna o processo de programação em Java muito mais simples que a programação em C++. Você tem poucas e distantes decisões por tomar e obstáculos a ultrapassar. Comentários (em inglês)

Coletores de lixo x eficiência e flexibilidade



Se todas estas são boas idéias, por que eles não fizeram a mesma coisa em C++? Bem, naturalmente, há um preço a pagar por toda esta conveniência de programação, e este preço é o tempo de execução elevado. Como mencionado antes, em C++ você pode criar objetos no stack, e neste caso eles são automaticamente limpos (mas você não tem a flexibilidade criação tão grande quanto você gostaria em tempo de execução). Criar objetos no stack é a maneira mais eficiente para alocar memória para objetos e liberar aquela memória. Criar objetos na pilha pode ser muito mais custoso. Sempre herdando da classe base e tornando polimórficos todos os métodos chamados também cobra um pequeno pedágio. Mas o coletor de lixo é um problema particular porque você nunca sabe o suficiente quando será iniciado ou quanto irá demorar. Isto significa que há uma inconsistência na taxa de execução de um programa Java, assim você não pode usá-lo em certas situações, como quando a taxa de execução de um programa está uniformemente crítica. (Estes são chamados geralmente de programas em tempo real, embora não todos os problemas da programação em tempo real sejam assim estritos.) Comentários (em inglês)



Os projetistas da linguagem C++, tentando atrair programadores C (e com muito sucesso, a isso), não quiseram adicionar qualquer artifício a linguagem que pudesse impactar na velocidade ou no uso do C++ em qualquer situação onde programadores devessem escolher outro que C. Este objetivo foi alcançado, mas ao preço da grande complexidade da programação em C++. Java é mais simples que C++, mas o prejuízo está na eficiência e algumas vezes na aplicabilidade. Para uma parte significativa de problemas de programação, contudo, Java é a opção superior. Comentários (em inglês)

Manuseio de exceções: Negociando com erros



Desde o inicio das linguagens de programação, tratamento de erros tem sido um dos assuntos mais difíceis. Pelo fato de ser muito difícil projetar um bom esquema de tratamento de erros, muitas linguagens simplesmente ignoram este assunto, passando o problema para os projetistas de bibliotecas que surgem com soluções que funcionam na maioria das situações mas que podem ser facilmente dribladas. O maior problema com a maioria dos esquemas de tratamento de erros é que eles contam com a vigilância dos programadores em seguir convenções que não são forçadas pela linguagem. Se o programador não é vigilante o tempo todo ou caso ele esteja em uma situação de pressão este esquema pode facilmente ser esquecido.Comentários (em inglês)



Tratamento de exceção liga tratamento de erros diretamente dentro da linguagem de programação e às vezes ao sistema operacional. Uma exceção (exception) é um objeto que é lançado (thrown) no lugar do erro e pode ser capturado (catch) por um tratador de exceções apropriado que foi projetado para tratar aquele tipo de erro em particular. O tratador de exceções e como se fosse, um caminho paralelo de execução que pode ser tomado quando as coisas vão mal. E pelo fato dele usar um caminho de execução separado, ele não precisa interferir com seu código de execução normal. Isto faz com que o código seja mais simples de ser escrito uma vez que você não é constantemente forçado a chegar os erros (código de checagens entre o código normal). Adicionalmente, uma exceção lançada é diferente de um valor de erro que é retornado de um método ou um flag que é setado por um método com o propósito de indicar uma situação de erro-isto pode ser facilmente ignorado. Uma exceção não pode ser ignorada, então temos a garantia de que a mesma será tratada em algum ponto. Finalmente, exceções prove uma forma confiável de se recuperar de situações ruim (de erro). Ao invés de apenas sair do programa, você é freqüentemente capaz de reorganizar a situação e restaurar a execução, o que produz programas muito mais robustos.Comentários (em inglês)



O tratamento de exceção de Java destaca-se entre as linguagens de programação, porque em Java, tratamento de exceção foi inserido no inicio e você é forçado a usar. Se você não escrever seu código para adequadamente tratar as exceções, você receberá uma mensagem de erro em tempo de compilação. Esta garantia de consistência pode algumas vezes tornar o tratamento de erros mais fácil.Comentários (em inglês)



É importante notar que tratamento de exceção não é uma característica da orientação a objetos, ainda que em linguagens orientadas a objetos uma exceção seja normalmente representada por um objeto. Tratamento de exceção existe antes das linguagens orientadas a objetos.Comentários (em inglês)

Concorrência



Um conceito fundamental na programação de computadores é a idéia de manusear mais de uma tarefa ao mesmo tempo. Vários problemas de programação requer que o programa seja capaz de parar o que ele está fazendo, lidar com algum outro problema, e então retornar para o processo principal. Aproximou-se da solução de várias formar. Inicialmente, programadores com conhecimento em baixo-nível (low-level) da máquina escreveram rotinas de serviço de interrupção, e a suspensão do processo principal era iniciado através do hardware. Apesar de funcionar bem, isto era difícil e não portável, então se tornava demorado e caro a transferência de um programa para um novo tipo de máquina.Comentários (em inglês)



As vezes, interrupções são necessárias para lidar com tarefas de tempo-critico (time-critical), mas há uma grande classe de problemas em que você está apenas tentando particionar o problema em pedaços de execução separados então o programa como um todo pode ser mais rápido na resposta ou usuário. Em programas, estes pedaços de execução separados são chamados threads, e o conceito geral é chamado de concorrência ou multithreading. Um exemplo simples de multithreading é a interface de usuário. Usando threads, um usuário pode pressionar um botão e receber uma resposta rápida ao invés de ser forçado a esperar até que o programa finalize sua tarefa atual.Comentários (em inglês)



Geralmente, thread é apenas uma maneira de alocar o tempo de um único processador. Mas se o sistema operacional suportar múltiplos processadores, cada thread pode ser atribuída para um processador diferente, e elas podem verdadeiramente executar em paralelo. Uma das características convenientes de multithreading em nível de linguagem de programação é que o programador não precisa se preocupar se há muitos processos ou apenas um. O programa é logicamente dividido em threads e se a máquina tem mais de um processador, então o programa executa mais rápido, sem a necessidade de ajustes especiais.Comentários (em inglês)



Tudo isso faz com que threading pareça um tanto simples. Há um complicador: recursos compartilhados. Se você tem mais de uma thread em execução que irá acessar o mesmo recurso, você tem um problema. Por exemplo, dois processos não podem simultaneamente enviar informações para um impressora. Para resolver o problema, recursos que podem ser compartilhados, como uma impressora, devem estar trancados (locked) enquanto eles estão sendo usados. Então uma thread tranca (lock) um recurso, completa sua tarefa, e então libera a tranca para que outros possam usar o recurso.Comentários (em inglês)



Threading em Java é construído dentro da linguagem, o que faz de um assunto complicado muito mais simples. Threading é suportado a nível de objetos, então uma thread de execução é representada por um objeto. Java também provê recursos para lock de recursos. Podendo fazer lock de memória de qualquer objeto (este é, um tido de recurso compartilhado) então apenas uma thread pode usar este recurso em um momento. Isto é alcançado com a utilização da palavra chave synchronized. Outros tipos de recursos devem ser travados explicitamente pelo programador, tipicamente criando um objeto para representar o lock que todas threads devem checar antes de acessar aquele recurso específico.Comentários (em inglês)

Persistência



Quando você cria um objeto, o mesmo existe enquanto você precisa dele, mas em nenhuma circunstância o mesmo existirá depois que o programa terminar. Mesmo que isto não faça sentido inicialmente, há situações em que seria útil se um objeto pudesse existir e manipular suas informações mesmo que o programa não esteja mais em execução. Então da próxima vez que você iniciar o programa, o objeto estaria lá e ele teria as mesmas informações que tinha no momento em que o programa executou da última vez. É claro, você pode ter um efeito similar escrevendo a informação para um arquivo ou para um banco de dados, mas no espírito de fazer tudo um objeto, seria um tanto conveniente ser capaz de declarar um objeto persistente e ter todos os detalhes sobre a recuperação dos dados cuidados para você.Comentários (em inglês)



Java prove suporte para "persistência leve," o que significa que você pode facilmente armazenar objetos em um disco e mais tarde recuperar o mesmo. A razão para "leve" é que você continua sendo forçado a fazer chamadas explícitas para proceder a persistência e a recuperação dos objetos. Persistência leve pode ser implementada de ambas as formas, através de serialização de objetos (mostrado no capitulo 12) e através de Java Data Objects (JDO, mostrado em pensando em Java Enterprise). Comentários (em inglês)

Java e a Internet



Se Java é, de fato, mais uma linguagem de programação de computadores, você pode questionar porque ela é tão importante e porque tem sido propalada como um passo revolucionário em programação de computadores. A resposta não é imediatamente óbvia se você está vindo de uma perspectiva de programação tradicional. Ainda que Java seja muito útil para solucionar problemas tradicionais de programação "standalone", ela também é importante porque vai solucionar problemas na World Wide Web. Comentários (em inglês)

O que é a Web?



A Web pode parecer ligeiramente misteriosa, com toda essa conversa de "surfar", "navegar", e "estar na web". É de grande ajuda dar um passo atrás e olhar para o que ela realmente é, mas para fazer isso você precisa compreender os sistemas cliente/servidor, um outro aspecto de computação recheado de questões confusas.Comentários (em inglês)

Sitemas Cliente/Servidor



A idéia básica de um sistma cliente/servidor é que você tenha um repositório central de informações - dados de alugum tipo, muitas vezes em um banco de dados - que você precisa distribuir para um grupo de pessoas ou máquinas, sob demanda. Uma idéia chave para o conceito cliente/servidor é que o repositório de informações está geograficamente centralizado, de modo que ele possa ser mudado e assim essas mudanças sejam propagadas para os consumidores das informações. Juntando tudo, o repositório de informação, o programa que distribui a informação e a máquina (ou máquinas) onde a informação e o programa residem é chamado de servidor. O programa que reside na máquina remota, comunica-se com o servidor, traz a informação, processa-a, e então a exibe na máquina remota é chamado de cliente. Comentários (em inglês)



Assim, o conceito básico de computação cliente/servidor não é tão complicado. O problema aparece quando você tem um único servidor tentando atender a muitos clientes ao mesmo tempo. Geralmente, há também um banco de dados, em que o projetista distribui a informação em tabelas para uso otimizado. E mais, os sistemas geralmente permitem a um cliente inserir novas informações no servidor. Isto significa que você deve assegurar que os novos dados de um cliente não se sobreponham aos novos dados de outro cliente, ou que dados não sejam perdidos no processo de sua adição à base de dados (isto é chamado processamento da transação). Conforme o programa cliente muda, ele deve ser construído, depurado e instalado nas máquinas clientes, o que acaba sendo mais complicado e dispendioso do que você imagina. Ainda mais difícil é o suporte a múltiplos tipos de computadores e sistemas operacionais. E há também a questão importantíssima da performance: você pode ter centenas de clientes fazendo solicitações ao servidor simultaneamente em determinado momento, de modo que mesmo um pequeno atraso é crucial. Para minimizar a latência, os programadores se empenham em redirecionar a carga das tarefas de processamento, geralmente para a máquina cliente, mas também às vezes para outras máquinas no site do servidor, usando o assim chamado middleware (o middleware é também utilizado para facilitar a manutenção). Comentários (em inglês)



A simples idéia de distribuição de informações apresenta tantos níveis de complexidade que o problema como um todo pode parecer desesperadoramente enigmático. E ainda assim é crucial: a computação cliente/servidor corresponde a aproximadamente metade das atividades de programação. É usada em tudo, de transações com cartões de crédito até a distribuição de diversos tipo de dados - mercado de ações, pesquisas científicas, informações governamentais, e por aí vai. O que se tinha no passado eram soluções individuais para problemas individuais, com uma nova solução sendo inventada a cada vez. Estas eram difíceis de criar e difíceis de usar, tendo o usuário que aprender uma nova interface para cada uma delas. A questão cliente/servidor precisa ser resolvida de maneira ampla.Comentários (em inglês)

A Web como um servidor gigante



A Web é na verdade um sistema cliente/servidor gigante. É um pouquinho pior que isto, já que você tem todos os servidores e clientes co-existindo ao mesmo tempo em uma única rede. Você não precisa saber disto, porque tudo o que tem a fazer é se preocupar em conectar e interagir com um servidor de cada vez (embora você tenha que circular por toda a terra em sua busca pelo servidor correto).Comentários (em inglês)



Inicialmente o processo era simples e unidirecional. Você fazia uma requisição ao servidor e ele lhe entregava um arquivo, o qual o seu software navegador (i.e., o cliente) interpretaria, formatando-o localmente na sua máquina. Mas em pouco tempo as pessoas começaram a querer fazer mais do que simplesmente entregar páginas com um servidor. Elas quiseram a funcionalidade cliente/servidor completa, tal que o cliente pudesse alimentar o servidor com informações para, por exemplo, realizar consultas a bancos de dados, adicionar novas informações ao servidor, ou para fazer um pedido (o que requeria mais segurança do que os sistemas originais ofereciam). Estas são as mudanças que temos visto no desenvolvimento da Web.Comentários (em inglês)



O navegador Web (browser) foi um grande passo adiante: a noção de que uma informação poderia ser visualizada em qualquer tipo de computador sem ser alterada. Entretanto, os navagadores ainda eram bastante primitivos e rapidamente sobrecarregados com a demanda que lhes era imposta. Eles não eram essencialmente interativos, e tendiam a ocupar o servidor e a Internet porque sempre que se precisava fazer algo que requeria programação, era necessário enviar informações de volta ao servidor para processamento. Poderia-se levar vários segundos ou minutos para descobrir que houve um erro de digitação na requisição. Como o navegador era apenas um visualizador, não poderia executar nem as tarefas computacionais mais simples (por outro lado, ele era seguro, pois não poderia executar nenhum programa contendo erros ou vírus prováveis na máquina local).Comentários (em inglês)



Para resolver esse problema, diferentes estratégias foram adotadas. Para começar, padrões gráficos foram melhorados para permitir melhor capacidade de animação e vídeo aos navegadores. O resto do problema pode ser resolvido apenas pela incorporação da abilidade de executar programas na extremidade cliente, dentro do navegador. Isso é chamado de programação client-side.Comentários (em inglês)

Programação Client-side



O projeto inicial servidor-navegador da Web oferecia suporte para conteúdo interativo, mas a interatividade era completamente provida pelo servidor. O servidor produzia páginas estáticas para o navegador cliente, que simplesmente as interpretaria e mostraria. A HyperText Markup Language (HTML) básica inclui mecanismos simples para coleta de dados: caixas de texto, caixas de seleção, botões, listas e áreas te texto, bem como um botão que serve apenas para reinicializar os dados do formulário e outro que envia os dados ao servidor. Esse envio passa através da Common Gateway Interface (CGI) provida por todos os servidores Web. O texto contido no envio indica à CGI o que deve ser feito. A ação mais comun é executar um programa localizado no servidor em um diretório tipicamente chamado "cgi-bin" (se você prestar atenção na barra de endereços no topo do navegador quando pressiona um botão numa página Web, às vezes você pode distinguir o fragmento "cgi-bin" dentre a sopa de letrinhas encontrada ali). Esses programas podem ser escritos em várias linguagens. Perl têm sido uma escolha comum, porque ela é projetada para manipulação de texto e é interpretada, de modo que pode ser instalada em qualquer servidor, independente do processador ou sistema operacional utilizado. Entretanto, Python (minha favorita — veja www.Python.org) têm feito progresso devido ao seu maior poder computacional e simplicidade.Comentários (em inglês)



Vários sites na Web de hoje foram construídos exclusivamente com CGI, e de fato pode-se fazer qualquer coisa com CGI. Entretanto, os sites construídos com programas CGI podem rapidamente se tornar complicados de manter, e há também o problema do tempo de resposta. A responsividade de um programa CGI depende da quantidade de dados a enviar, bem como da quantidade de usuários conectadas ao servidor e à Internet (além disso, a inicialização de um programa CGI tende a ser lenta). Os primeiros projetistas da Web não previram quão rapidamente essa largura de banda seria exaurida para os tipos de aplicativos que as pessoas desenvolviam. Por exemplo, qualquer tipo de geração dinâmica de gráficos é praticamente impossível de realizar com consistência, pois um arquivo do tipo Graphics Interchange Format (GIF) deve ser criado e movido do servidor para o cliente para cada versão do gráfico. E com certeza você já teve experiência direta com algo tão simples como a validação de dados em um formulário Web. Você pressiona o botão "submit" em uma página; os dados são enviados ao servidor; o servidor dispara um programa CGI que descobre um erro, constrói uma página HTML informando-o do erro, e a envia de volta a você; você então deve voltar e tentar novamente. Além de lento, isso é deselegante.Comentários (em inglês)



A solução é a programação client-side. A maioria das máquinas que executam navegadores Web são capazes de realizar muito processamento e, com a estratégia original de HTML estático, elas estão apenas ali, esperando ociosas enquanto o servidor prepara a próxima resposta. Programação client-side significa aproveitar o navegador Web para fazer o trabalho que puder ser feito, resultando em uma experiência menos demorada e mais interativa para o usuário do seu site Web.Comentários (em inglês)



O problema com discussões sobre programação client-side é que elas não são muito diferentes das discussões sobre programação em geral. Os parâmetros são quase os mesmos, mas a plataforma é diferente; um navegador Web é como um sistema operacional limitado. No fim das contas, você ainda precisa programar, e isso justifica a vasta quantidade de problemas e soluções produzidas pela programação client-side. O restante desta seção provê uma discussão geral dos problemas e estratégias na programação client-side.Comentários (em inglês)

Plug-ins



Um dos avanços mais significativos na programação client-side foi o desenvolvimento do plug-in. Essa é uma forma de adicionar funcionalidade ao navagador através do download de um componente que se acopla no local apropriado dentro do navegador. Ele diz ao navegador: "de agora em diante você consegue realizar esta nova atividade" (o download do plug-in precisa ser feito apenas uma vez). Alguns comportamentos poderosos podem ser adicionados aos navegadores via plug-ins, mas construir um plug-in não é uma tarefa trivial, e não é algo você gostaria de fazer como parte do processo de desenvolver um site. O valor do plug-in na programação client-side é que ele permite a um programador experiente desenvolver uma nova linguagem e adicioná-la a um navegador sem a permissão do fabricante do navegador. Portanto, os plug-ins provêem uma "porta dos fundos" que permite a criação de novas linguagens de programação client-side (embora nem todas as linguagens sejam implementadas como plug-ins).Comentários (em inglês)

Linguagens de script



Os plug-ins resultaram numa explosão das linguagens de script. Com uma linguagem de script, voce insere o código-fonte para o seu programa (que roda no cliente) diretamente na página HTML, e o plug-in que interpreta aquela linguagem é automaticamente ativado quando a página é exibida. Linguagens de script costumam ser de fácil entendimento e, por serem simples texto inserido na página HTML, carregam rapidamente como parte de um único hit requerido para carregar a página. A desvantagem é que seu código fica exposto a todos para ver (e roubar). Geralmente, no entanto, você não está fazendo nada espantosamente sofisticado com linguagens de script, e essa desvantagem não conta muito.Comentários (em inglês)



Isto levanta a questão de que as linguagens de script usadas nos browsers de Internet têm a intenção de resolver problemas específicos, principalmente a criação de uma interface gráfica (GUI) mais rica e interativa. Entretanto, uma linguagem de script pode resolver 80% dos problemas encontrados na programação no lado do cliente. Seus problemas podem muito bem se encaixar completamente nesses 80%, e já que as linguagens de script permitem desenvolvimento mais fácil e rápido, você provavelmente deveria considerar uma linguagem de script antes de olhar para uma solução mais envolvente como Java ou ActiveX.Comentários (em inglês)



As linguagens de script para navegadores mais comentadas são JavaScript (que não tem nada a ver com Java; só foi batizada assim para tomar proveito do marketing em torno da linguagem Java), VBScript (que se parece muito com Visual BASIC), e Tcl/Tk, que vem da popular linguagem multi-plataforma de criação de interfaces gráficas (GUI). Existem outras por aí, e sem dúvida outras mais em desenvolvimento. Comentários (em inglês)



JavaScript é certamente a de maior aceitação pelos navegadores. Já vem instalada tanto no Netscape Navigator como no Microsoft Internet Explorer (IE). Infelizmente, nuances de JavaScript nesses dois navegadores podem variar imensamente (o navegador Mozilla, que pode ser baixado gratuitamente em www.Mozilla.org, oferece suporte ao padrão ECMAScript, o que pode um dia se tornar aceito universalmente). Além disso, existem provavelmente mais livros disponíveis sobre JavaScript do que sobre todas as outras linguagens de navegador, e algumas ferramentas já criam automaticamente páginas usando JavaScript. Entretanto, se você já é fluente em Visual BASIC ou em Tcl/Tk, será mais produtivo usar estas linguagens de script ao invés de aprender mais uma (você já vai estar bastante ocupado lidando com os demais problemas da Web).Comentários (em inglês)

Java



Se uma linguagem de script resolve 80% dos problemas de programação lado-cliente, o que fazer com os outros 20% — o "osso duro de roer"? Java é uma solução popular. Não apenas é uma linguagem poderosa, construída para ser segura, multi-plataforma e internacional, como também vem sendo continuamente extendida para prover funções de linguagem e bibliotecas que lidam elegantemente com problemas que são de difícil resolução em linguagems tradicionais, como multithreading, acesso a banco de dados, programação de rede, e computação distribuída. Java permite a programação lado-cliente através do applet e com o Java Web Start. Comentários (em inglês)



Um applet é um mini-programa que roda apenas em um navegador da Web. O applet é baixado automaticamente como parte de uma página Web (da mesma maneira, por exemplo, que uma imagem é automaticamente baixada). Quando o applet é ativado, ele executa um programa. Esta é uma de suas belezas — proporciona uma maneira de distribuir o programa cliente de forma automática no momento em que o usuário o necessita, e não antes. O usuário recebe a versão mais recente do programa, sem falha, e sem uma reinstalação dificultosa. Graças à maneira como Java é projetada, o programador só precisa criar um único programa, e esse programa vai automaticamente funcionar em todos os computadores que têm navegadores com um interpretador Java embutido (e isso inclui seguramente a vasta maioria dos computadores). Já que Java é uma linguagem de programação completa, você pode fazer o máximo de trabalho no lado do cliente antes e depois de fazer requerimento ao servidor. Por exemplo, você não precisa submeter um formulário através da Internet para descobrir que você entrou uma data incorreta ou algum outro parâmetro errado, e seu computador cliente pode rapidamente gerar um gráfico baseado em seus dados, ao invés de esperar pelo servidor para gerar o gráfico e enviar a imagem de volta para você. Não somente você tem o ganho imediato da velocidade e prontas respostas, mas também o tráfego da rede e a carga nos servidores pode ser reduzida, prevenindo uma vagarosidade na Internet toda.Comentários (em inglês)



Uma vantagem que um applet de Java tem sobre uma linguagem de script é ser compilado, portanto o código fonte não está disponível para o cliente. Por outro lado, um applet de Java pode ser descompilado sem muita dificuldade. Esconder seu código, porém, não é geralmente um ponto importante. Dois outros fatores podem ser de importância. Como você verá mais tarde neste livro, um applet de Java compilado pode requerer mais tempo para baixar, se for grande. Uma linguagem de script vai ser integrada na página Web como parte de seu texto (e em geral vai ser menor e reduzir o número de hits ao servidor). Isso pode ser importante para a capacidade do seu site Web de ser responsivo. Outro fator de importância é a curva de aprendizado. Não importa o que você tenha ouvido falar, Java não é uma linguagem tão fácil de se aprender. Se você é um programador de VISUAL BASIC, mudar para VBScript será a solução mais rápida (assumindo que você possa limitar seus usuários às plataformas Windows), e já que vai resolver a maioria dos problemas típicos em cliente/servidor, você deve sentir dificuldade em justificar o aprendizado de Java. Se você já tem experiência com uma linguagem de script, certamente se beneficiará em dar uma olhada em JavaScript ou VBScript antes de se envolver com Java, porque essas linguagens podem servir exatamente para o que você precisa e você vai ser mais produtivo, mais rapidamente. Comentários (em inglês)

.NET e C#



Durante muito tempo, o principal competidor dos applets de Java era o ActiveX da Microsoft, apesar de requerer que o cliente rodasse Windows. Desde então, a Microsoft produziu um competidor muito mais completo para a linguagem Java: a plataforma .NETe a linguagem C# . A plataforma .NET é a grosso modo o mesmo que a máquina virtual Java e as bibliotecas Java, e C# apresenta similaridades inconfundíveis à linguagem Java. Este é certamente o melhor que a Microsoft já fez na área de linguagens e ambientes de programação. Claro, eles tiveram a vantagem considerável de poder ver o que funcionava bem e o que não deu tão certo em Java, e construíram a partir daí, mas fizeram. Esta foi a primeira vez desde seu surgimento que a linguagem Java teve uma competição para valer, e se tudo der certo, o resultado será que os designers de Java na Sun vão dar uma olhada séria em C# e nos motivos que levariam os programadores a mudar para aquela linguagem, e vão reagir fazendo melhorias fundamentais na linguagem Java. Comentários (em inglês)



Atualmente, a principal vulnerabilidade e questão mais importante em relação à .NET é se a Microsoft vai permitir que ela seja implementada completamente em outras plataformas. Eles alegam que não há problemas em fazê-lo, e o projeto Mono (www.go-mono.com) tem uma implementação parcial de .NET rodando em Linux, mas até a implementação estar completa e a Microsoft decidir não espremer fora nenhuma parte, .NET como solução multi-plataforma ainda é um palpite arriscado. Comentários (em inglês)



Para aprender mais sobre .NET e C#, veja Thinking in C# de Larry O’Brien e Bruce Eckel, Prentice Hall 2003.

Segurança



Baixar e rodar programas automaticamente através da Internet pode parecer o sonho de um criador de vírus. Se você clicar num site da Web, você pode automaticamente baixar uma série de coisas junto com o HTML da página: arquivos GIF, código de script, código Java compilado, e componentes ActiveX. Alguns desses são benignos; arquivos GIF não podem causar mal, e linguagens de script são em geral limitadas no que podem fazer. Java também foi projetada para rodar seus applets em um "canteiro" de segurança ("sandbox"), que a impede de salvar dados no disco rígido ou acessar memória fora da sandbox. Comentários (em inglês)



O ActiveX da Microsoft está na extremidade oposta do espectro. Programar com ActiveX é como programar Windows — você pode fazer qualquer coisa que quiser. Portanto, se você clica numa página que baixa um componente ActiveX, esse componente pode causar danos aos arquivos no seu disco rígido. Claro, programas que você carrega no seu computador que não estão restritos a rodar dentro de um navegador Web podem fazer o mesmo. Os vírus baixados dos Bulletin-Board Systems (BBSs) têm sido um problema há tempos, mas a velocidade da Internet amplifica a dificuldade. Comentários (em inglês)



A solução parece ser as "assinaturas digitais", em que o código apresenta seu autor, e tal informação é verificada. Isso se baseia na idéia de que um vírus pega porque seu criador pode ser anônimo, logo se você remove a anonimidade, os indivíduos serão forçados a ser responsáveis por suas ações. Parece ser um bom plano porque permite aos programas ser muito mais funcionais, e eu imagino que vá eliminar condutas maliciosas. Se, no entanto, um programa tiver um erro destrutivo não intencional, causará problemas da mesma forma. Comentários (em inglês)



A abordagem Java é de prevenir esses problemas através do uso do canteiro de segurança. O interpretador Java que reside no seu navegador local examina o applet, procurando por desconfortáveis, enquanto o applet é carregado. Em particular, o applet não pode gravar arquivos em disco ou apagar arquivos (um dos suportes principais dos vírus). Applets são de modo geral considerados seguros, e como esse é um ponto essencial para sistemas cliente/servidor confiáveis, quaisquer problemas na linguagem Java que permitam vírus são rapidamente resolvidos (vale notar que o programa do navegador na verdade enforça essas restrições de segurança, e alguns navegadores permitem que o usuário selecione diferentes níveis de segurança para prover diversos graus de acesso a seu sistema). Comentários (em inglês)



Talvez você esteja cético quanto a esta restrição um tanto draconiana contra gravar arquivos em seu disco rígido. Por exemplo, você pode querer criar um banco de dados local ou salvar data para uso posterior, quando desconectado. A visão inicial parecia ser de que todo mundo iria se conectar para fazer qualquer coisa importante, mas logo se percebeu que isso seria impraticável (apesar da possibilidade das "ferramentas da Internet" de baixo custo poderem um dia satisfazer as necessidades de um segmento significante de usuários). A solução é o "applet assinado" que usa uma codificação de chave pública para verificar se um applet vem realmente de onde ele diz que vem. Um applet assinado ainda pode danificar seu disco, mas a teoria é de que já que você pode responsabilizar os criadores do applet, eles não farão maldades. Java provê uma estrutura para assinaturas digitais, para que quando necessário você seja capaz de permitir a um applet pisar fora do canteiro de segurança. O capítulo 14 apresenta um exemplo de como assinar um applet. Comentários (em inglês)



Adicionando-se a isso, o Java Web Start é uma maneira relativamente nova de distribuir com facilidade programas que rodam sozinhos e que não necessitam de um navegador web para funcionar. Essa tecnologia tem o potencial de resolver muitos problemas no lado cliente associados a rodar programas dentro de um navegador. Programas Web Start podem ser assinados, ou podem pedir ao cliente permissão todas as vezes que fazem alguma coisa potencialmente perigosa para o sistema local. O capítulo 14 tem um examplo simples e uma apresentação do Java Web Start. Comentários (em inglês)



Assinaturas digitais não estão levando em consideração um ponto importante, que é a velocidade com que as pessoas se movem através da Internet. Se você baixa um programa com erros e ele faz algo indesejável, quanto tempo vai levar para você descobrir o estrago? Pode ser dias ou até semanas. Aí então, como você vai encontrar o programa que fez o estrago? E que bem vai fazer a essa altura? Comentários (em inglês)

Internet vs. intranet



A Web é a solução mais geral para o problema cliente/servidor, portanto faz sentido usar a mesma tecnologia para resolver um subconjunto do problema - em particular o problema clássico cliente/servidor interno a uma companhia. Nas soluções cliente/servidor tradicionais há o problema da diversidade de tipos de computadores clientes, assim como a dificuldade de instalação de novos programas no cliente, ambas questões prontamente resolvidas com os navegadores para Web e programação lado-cliente. Quando a tecnologia Web é usada para uma rede de informações restrita a uma empresa, ela é definida como uma intranet. Intranets fornecem muito mais segurança do que a Internet, já que você pode controlar fisicamente o acesso aos servidores dentro de sua companhia. Em termos de treinamento, parece que uma vez que as pessoas tenham compreendido o conceito geral de navegador, é muito mais fácil para elas lidarem com as diferenças na forma em que as páginas e applets se apresentam, de modo que a curva de aprendizado para novos tipos de sistemas tende a ser reduzida. Comentários (em inglês)



O problema da segurança nos remete a uma das subdivisões que parecem ter se formado automaticamente no mundo da programação no lado-cliente. Se seu programa está rodando na Internet, você não sabe sob que plataforma ele está trabalhando, e você deseja tomar cuidado extra no sentido de não disseminar código defeituoso. Você precisa de alguma plataforma cruzada e segura, como uma linguagem de script ou Java.Comentários (em inglês)



Se você está rodando em uma intranet, você poderia ter um outro conjunto de restrições. Não é incomum que suas máquinas tenham todas a plataforma Intel/Windows. Em uma intranet, você é responsável pela qualidade de seu próprio código e pode reparar defeitos quando os mesmos forem descobertos. Além disto, pode ser que você já tenha um corpo de código pronto que era utilizado em uma aproximação cliente/servidor mais tradicional através da qual você tenha que instalar fisicamente programas clientes toda vez que você faz uma atualização. O tempo dispendido na instalação de atualizações é a razão mais forte que o compele a migrar para os navegadores, pois atualizações são invisíveis e automáticas (Java Web Start é também uma solução para este problema). Se você está envolvido com uma intranet deste tipo, o procedimento mais lógico a adotar é tomar o caminho mais curto que lhe permita usar sua base de código existente, ao invés de tentar recodificar seus programas em uma nova linguagem. Comentários (em inglês)



Quando confrontado com este conjunto confuso de soluções para o problema de programação lado-cliente, o melhor plano de ataque é uma análise de custo-benefícios. Considere as restrições do seu problema e qual seria o caminho mais curto para solucioná-lo. Como a programação lado-cliente é ainda programação, é sempre uma boa idéia adotar a abordagem de mais rápido desenvolvimento para sua situação particular. Esta é uma postura vigorosa para prepará-lo para os inevitáveis encontros com os problemas de desenvolvimento de programas. Comentários (em inglês)

Programação lado-servidor



Toda esta discussão ignorou o tema da programação lado-servidor. O que acontece quando você faz uma solicitação ao servidor? Na maioria das vezes a solicitação é simplesmente "envie-me este arquivo." Seu navegador então interpreta o arquivo com um tipo apropriado: como uma página HTML, uma imagem gráfica, um applet Java, um programa de script, etc. Uma solicitação ao servidor mais complicada geralmente envolve uma transação de banco de dados. Um cenário comum envolve uma solicitação para uma busca complexa em um banco de dados, que o servidor então formata como uma página HTML e envia a você como resultado. (Naturalmente se o cliente tiver mais capacidade através de Java ou uma linguagem de script, os dados podem ser enviados em sua forma original e formatados na extremidade do cliente, o que será muito mais rápido e reduzirá a carga no servidor). Ou você poderia querer registrar seu nome em um banco de dados quando se associar a um grupo ou enviar um convite, o que envolveria alterações naquele banco de dados. Estas solicitações aos bancos de dados devem ser processadas por meio de algum código no lado servidor, o que é geralmente denominado programação lado-servidor. Tradicionalmente, a programação lado-servidor tem sido executada usando Perl, Python, C++, ou alguma outra linguagem, para criar programas CGI, mas sistemas mais sofisticados têm aparecido. Estes incluem servidores Web baseados em Java que permitem a você executar toda a sua programação lado-servidor em Java escrevendo os assim chamados servlets. Servlets e seus descendentes, JSPs, são duas das razões mais persuasivas pelas quais as companhias que desenvolvem sites Web estão migrando para Java, especialmente porque são eliminados os problemas de lidar com navegadores de capacidades diferentes (estes tópicos são abordados em Thinking in Enterprise Java). Comentários (em inglês)

Aplicações



Muito do alarido sobre Java gira em torno dos applets. Java é na verdade uma linguagem de programação de objetivos gerais que pode resolver os tipos de problemas que podem ser resolvidos com outras linguagens - pelo menos na teoria. E como salientado anteriormente, podem existir maneiras mais eficazes de se solucionar a maioria dos problemas cliente/servidor. Quando você sai da arena do applet (e simultaneamente libera as restrições, tais como aquela de impedir que se escreva no disco) você entra no mundo de aplicações gerais que são executadas sem depender de um sistema operacional, sem um navegador para Web, da mesmo forma como os programas comuns fazem. Neste aspecto, a força de Java não é apenas sua portabilidade, mas também sua programabilidade. Como você verá no decorrer deste livro, Java possui muitas características que permitem a você criar programas robustos em períodos mais curtos do que com as linguagens de programação anteriores. Comentários (em inglês)



Esteja consciente de que este é um benefício relativo. Você paga pelos aperfeiçoamentos através de uma velocidade de execução mais baixa (embora haja um volume significativo de trabalho sendo desenvolvido nesta área - em particular, os aprimoramentos denominados "hotspots" nas versões de Java mais recentes). Como qualquer linguagem, Java tem limitações de origem que podem torná-la inadequada para resolver certos tipos de problemas de programação. Entretanto, Java é uma linguagem que evolui rapidamente e a cada nova liberação torna-se mais e mais atraente para resolver conjuntos maiores de problemas. Comentários (em inglês)

Por quê o sucesso de Java



A razão em que Java tem sido assim um sucesso é que o objetivo foi o de resolver vários dos problemas que encaram os desenvolvedores de hoje. Um objetivo fundamental do Java é aumentar a produtividade. Esta produtividade vem de várias maneiras, mas a linguagem é projetada para ter uma melhoria significante sobre seus predecessores, e fornecer importantes benefícios para o programador. Comentários (em inglês)

Sistemas são facilmente
expressados e entendidos



Classes designed to fit the problem tend to express it better. This means that when you write the code, you’re describing your solution in the terms of the problem space (“Put the grommet in the bin”) rather than the terms of the computer, which is the solution space (“Set the bit in the chip that means that the relay will close”). You deal with higher-level concepts and can do much more with a single line of code. Comentários (em inglês)



O outro benefício desta facilidade de expressão é a manutenção que (se pudermos acreditar em relatórios) é uma significativa parcela do custo durante a vida útil do programa. Se um programa for mais fácil de se compreender, então será mais fácil de manter. Isto pode também reduzir o custo de elaboração e manutenção da documentação. Comentários (em inglês)

Máximo aproveitamento com bibliotecas



A maneira mais rápida de se criar um programa é utilizar código que já esteja escrito: uma biblioteca. Um objetivo importante em Java é tornar o uso de bibliotecas mais fácil. Este é atingido moldando-se bibliotecas em novos tipos de dados (classes), de modo que importar uma biblioteca signifique adicionar novos tipos à linguagem. Como o compilador Java cuida da forma como a biblioteca é usada - garantindo inicialização adequada e limpeza, e assegurando que os métodos sejam chamados adequadamente - você pode concentrar sua atenção naquilo que você deseja que a biblioteca faça, e não em como você deve fazê-lo. Comentários (em inglês)

Manipulando erros



O tratamento de erros em C é um problema notório, e um que é freqüentemente ignorado -cruzar os dedos está freqüentemente envolvido. Se você está construindo um programa grande, complexo, não há nada pior que ter um erro enterrado em algum lugar sem nenhuma pista de onde possa ter vindo. O tratamento de exceções do Java é um modo de garantir que um erro foi notado, e que algo acontece como resultado.Comentários (em inglês)

Programando em larga escala



Muitas linguagens tradicionais trazem em seu cerne limitações quanto ao tamanho e complexidade do programa. BASIC, por exemplo, pode ser excelente para reunir soluções rápidas para certas classes de problemas, mas se o programa atingir mais de algumas páginas de tamanho, ou se aventurar além do domínio normal de problemas daquela linguagem, é como tentar nadar através de um fluido cada vez mais viscoso. Não há uma linha clara para informá-lo de que sua linguagem está falhando, e mesmo que tivesse, você a ignoraria. Você não diria. "Meu programa em BASIC ficou grande demais; terei que reescrevê-lo em C!" Ao invés disto, você iria tentar acrescentar algumas linhas a mais para adicionar uma nova funcionalidade. Então os custos extras viriam sobre você. Comentários (em inglês)



Java é projetada para auxiliar a programação em grande escala - isto é, para apagar aqueles limites de complexidade entre um programa pequeno e um grande. Você certamente não precisa usar POO quando está escrevendo um programa utilitário do tipo "hello, world", mas as características estão lá para quando você necessitar delas. E o compilador é eficiente em barrar erros que produzem defeitos em programas pequenos da mesma forma que em grandes. Comentários (em inglês)

Java vs. C++?



Java se parece bastante com C++, então naturalmente parece que C++ vai ser substituido por Java. Mas eu estou começando a questionar esta lógica. Por um motivo, C++ ainda possui algumas características que Java não possui, e embora haja muitas promessas sobre algum dia Java ser tão rápido quanto, ou mais que C++, nós temos visto constantes aperfeiçoamentos mas nenhuma mudança drástica. Além disso, parece haver um continuo interesse por C++, portanto eu não acho que aquela linguagem irá desaparecer tão cedo.Comentários (em inglês)



Eu estou começando a pensar que a força de Java reside em uma arena ligeiramente diferente da de C++, que é uma linguagem que não tenta se ajustar a um molde. Certamente ela foi adaptada em numerosas vezes para resolver problemas particulares. Algumas ferramentas C++ combinam bibliotecas, modelos de componentes e ferramentas de geração de código para resolver o problema do desenvolvimento de aplicações windows para usuário final (para o Microsoft Windows). E ainda, o que é que a grande maioria dos desenvolvedores Windows utilizam? Microsoft Visual BASIC (VB). Isto apesar do fato de que VB produz código que se torna intratável quando o programa tem apenas algumas páginas de extensão (e sintaxe que pode ser absolutamente enganadora). Tão bem sucedida e popular quanto é VB, não é um bom exemplo de projeto de linguagem. Seria mais agradável ter a facilidade e potencialidade de VB sem o código intratável resultante. E é aí que eu penso que Java irá brilhar: como o "próximo VB.[9]” Você pode tremer ou não ao ouvir isto, mas pense a respeito: a maior parte de Java tem como objetivo tornar fácil para o programador resolver problemas no nível de aplicação como redes e UI multi-plataformas, e ainda tem um projeto de linguagem que permite a criação de corpos de código muito grandes e flexíveis. Adicione a isto o fato de que a verificação de tipos e manipulação de erros em Java é um grande aprimoramento em relação à maioria das linguagens e você terá a estrutura de um salto significativo em direção à produtividade de programação. Comentários (em inglês)



Se você está desenvolvendo todo o seu código antes de mais nada, a partir do zero, então a simplicidade de Java sobre C++ reduzirá significativamente seu tempo de desenvolvimento-as lendas (estórias contadas por equipes de C++, com as quais conversei, que migraram para Java) sugerem o dobro da velocidade em relação a C++. Se a performance de Java não importar ou se você encontrar alguma forma de contrabalançá-la, questões de tempo para colocar o produto no mercado tornarão difícil a escolha de C++ ao invés de Java. Comentários (em inglês)



A maior questão é a performance. Nos interpretadores originais, Java imterpretada pode ser lenta, até 20 a 50 vezes mais lenta que C. Com o tempo, este desempenho melhorou significativamente (especialmente com as versões mais recentes de Java), mas ainda permanece um número significativo. Em computadores a velocidade é determinante; se não fosse significativamente mais rápido fazer algo no computador então você o faria a mão. (Eu já ouvi a sugestão de se começar com Java, para ganhar no tempo de desenvolvimento mais curto, então usar uma ferramenta e bibliotecas de suporte para traduzir seu código para C++, se você necessitar de maior velocidade de execução.) Comentários (em inglês)



A chave para tornar Java possível em muitos projetos é a aparente velocidade de melhorias como os chamados compiladores "just-in-time" (JIT), a tecnologia "hotspot" da Sun, e ainda compiladores de código nativo. Claro que, compiladores de código nativo eliminarão a perseguida execução multi-plataforma dos programas compilados, mas eles também trarão a velocidade de execução para perto daquela em C e C++. E compilações cruzadas (cross-compiling) em Java deverão ser muito mais fáceis do que em C ou C++. (Em teoria, você apenas recompila, mas essa promessa já foi feita antes em outras linguagens.)Comentários (em inglês)

Summary



Este capítulo tentou lhe dar uma visão geral da programação orientada a objetos e de Java, incluindo o porquê de POO ser diferente, e o porquê de Java em particular ser diferente. Comentários (em inglês)



POO e Java não podem ser para todos. É importante avaliar suas próprias necessidades e decidir se Java satisfará essas necessidades a contento ou se seria melhor para você adotar um outro sistema de programação (inclusive o que você está usando atualmente). Se você souber que suas necessidades serão muito especializadas em um futuro próximo e se você tiver restrições específicas que poderão não ser satisfeitas por Java, então é recomendável que você investigue as alternativas (Em particular, eu recomendo que você verifique em Python; veja www.Python.org). Mesmo se você eventualmente escolher Java como sua linguagem, você pelo menos terá compreendido quais eram as opções e terá uma visão clara das razões pelas quais você tomou aquela direção. Comentários (em inglês)



Você sabe como é a estrutura de um programa procedural: definições de dados e chamadas de funções. Para descobrir o significado de tal programa, você tem que trabalhar um pouco, procurando as chamadas de funções e conceitos de baixo-nível para criar um modelo em sua mente. Esta é a razão pela qual nós precisamos de representações intermediárias quando desenhamos programas procedurais-por si só, estes programas tendem a ser confusos porque os termos e expressões são muito mais orientados para o computador do que para o problema que você está resolvendo. Comentários (em inglês)



Devido ao fato de Java introduzir muitos conceitos novos além do que você encontra em uma linguagem procedural, é natural que você imagine que a main( ) de um programa Java seja muito mais complicada do que a de um programa C equivalente. Aqui você terá uma agradável surpresa: Um programa Java bem-escrito será em geral muito mais simples e muito mais fácil de se compreender do que um programa C equivalente. O que você verá são as definições dos objetos que representam conceitos no espaço do seu problema (ao invés de representações do computador) e mensagens enviadas para esses objetos para representar as atividades naquele espaço. Um dos prazeres da programação orientada a objetos é que, com um programa bem-desenhado é fácil entender o código pela sua simples leitura. Usualmente, há também muito menos código, porque muitos dos seus problemas serão resolvidos pela reutilização de código existente na biblioteca. Comentários (em inglês)




[2] Alguns projetistas de linguagem decidiram que programação orientada a objetos por si só não é adequada para resolver de forma fácil todos os problemas de programação, e defendem a combinação de várias abordagens em uma Linguagem de Programação Multiparadigma. Veja Programação Multiparadigma em Leda por Timothy Budd (Addison-Wesley 1995).



[3] Na verdade isto é um pouco restritivo, já que é concebível que objetos existam em diferentes máquinas e espaços de endereçamento, além de poderem ser armazenados em disco. Nestes casos, a identidade do objeto deve ser determinada por algo distinto do endereço de memória.



[4] Algumas pessoas fazem uma distinção, dizendo que tipo determina a interface, enquanto classe é uma implementação particular daquela interface.



[5] Eu estou agradecido ao meu amigo Scott Meyers por este termo.



[6] Isto é, na verdade, detalhe suficiente para a maioria dos diagramas e você não precisa especificar se está usando agregação ou composição.



[7] Tipos primitivos, que você irá aprender mais tarde, são um caso especial.



[8] Exceto, desafortunadamente, para primitivos. Isto é discutido em detalhes depois neste livro.



[9] O que a Microsoft está dizendo efetivamente é "não tão rápido" com C# e .NET. Muitas pessoas levantaram a questão sobre se programadores VB desejam mudar para qualquer outra coisa, se esta seria Java, C#, ou até VB.NET.


Anterior Próximo Página Inicial Índice Conteúdo