Orientação a Objetos no Delphi - Por onde começar (I)

7,565 views
7,630 views

Published on

Published in: Software
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
7,565
On SlideShare
0
From Embeds
0
Number of Embeds
4,035
Actions
Shares
0
Downloads
290
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

Orientação a Objetos no Delphi - Por onde começar (I)

  1. 1. Orientação a Objetos no Delphi: Por onde começar? – Parte I Ryan Bruno C Padilha ryan.padilha@gmail.com http://ryanpadilha.com.br Objetivo deste artigo Este artigo é o primeiro de uma série de três artigos onde iremos desenvolver uma aplicação básica de controle de estoque. A primeira parte da série tem como objetivo fornecer uma introdução ao paradigma orientado a objetos na linguagem Object Pascal, que é a linguagem base do ambiente Delphi. A orientação a objetos, comumente chamada de OO é fundamentada em trazer os objetos da vida real para representar uma entidade na aplicação por meio de um tipo bem definido, ou seja, através de classes. Antes do surgimento da OO, muitos desenvolvedores utilizavam ou ainda utilizam o paradigma estruturado, do qual com o passar do tempo se mostrou pouco flexível, com baixa reutilização de código e inadequado a projetos de software de grande complexidade. 1. Introdução ao Paradigma Orientado a Objeto O Object Pascal é uma linguagem híbrida, pois além da orientação a objetos possui também uma parte da antiga linguagem estruturada - Pascal. Por este motivo não somos forçados a programar cem por cento orientado a objetos no Delphi, como ocorre em outras linguagens de programação como Java e C# .NET – que oferecem suporte somente a este paradigma em questão. Muitas pessoas quando questionadas se programam utilizando o conceito de orientação a objetos no Delphi, respondem afirmativamente, porém levando a conversa mais adiante fica claro que a maioria delas programa no nível do designer, que não exige um conhecimento profundo dos conceitos da OO. É chamado nível do designer a prática de programação que utiliza os componentes visuais do Delphi (VCL – Visual Component Library), necessitando apenas conhecer a sintaxe da linguagem. A diferença aqui é sutil, porém os paradigmas abordados – orientado a objetos e estruturado – são na teoria e prática diferentes. A orientação a objetos é amplamente utilizada na análise, projeto e implementação de projetos de software baseado na composição e interação entre diversas unidades de software conhecidas como objetos. Os objetos nada mais são do que instâncias de uma determinada classe (modelo), podem se relacionar entre si através de associações (agregação / composição); trocar mensagens através de chamadas de métodos (operações); ser objetos especialistas ou mais genéricos através de herança. Resumidamente, as principais características da OO são: as classes, a herança, o polimorfismo, seguidas por abstração e encapsulamento. Para quem está entrando em contato com os conceito da orientação a objetos neste momento, pode achar tudo muito confuso e difícil, e isto é esperado, porém, na realidade é bem mais fácil do que se imagina. Com a utilização constante e um maior entendimento do paradigma OO, será notado que é mais fácil a implementação e principalmente a manutenção
  2. 2. nos projetos de software. Iremos elucidar os principais termos e definições gerais da orientação a objetos. 1.1 Conceitos Essenciais Classe Tipo de dado bem definido através de atributos (variáveis) que armazenam estados (valores); o comportamento das instâncias dessa classe (conjunto de objetos semelhantes) é definido através dos métodos, na forma de procedimentos ou funções, que representam as operações que os mesmos podem realizar. Há dois tipos de classes: 1) Concreta: definida como uma classe que pode ser instanciada diretamente por um objeto; 2) Abstrata: declarada como uma classe que pode apenas ser estendida, ou seja, servindo como modelo ou classe base (superclasse) na hierarquia de classes que estiver inserida, não sendo possível a instanciação direta por um objeto. Uma classe é declarada através do uso da palavra reservada class, vejamos a estrutura básica de uma classe: Type // classe definida como TMinhaClasse // toda classe herda de TObject, sendo sua declaração facultativa TMinhaClasse = class(TObject) private { atributos / métodos privados } protected { atributos / métodos protegidos } public { atributos / métodos públicos } end; // fim da declaração da classe TMinhaClasse Para melhor entendimento segue o exemplo de uma classe TCliente que pode ser implementada no Delphi. Type TCliente = class(TObject) private function Inserir(): Boolean; function Update(): Boolean; protected _codigo: String; _nome: String; _telefone: String; public // propriedades – encapsulamento de atributos property Codigo: String read getCodigo write setCodigo; property Nome: String read getNome write setNome;
  3. 3. property Telefone: String read getNome write setNome; // métodos – podem ser declarados como functions ou procedures function Validar(): Boolean; function Merge(): Boolean; function Delete(): Boolean; function getObject(Id: String): Boolean; procedure getMaxId; end; implementation // implementação dos métodos declarados acima // código completo omitido para não estender demais o tópico Objeto É uma instância de uma classe, ou seja, é uma variável (dinâmica) do tipo definido pela classe. Possuindo a capacidade de armazenar estados através de seus atributos e realizar operações através de seus métodos, definindo seu comportamento. O estado de um objeto pode ser persistido (armazenado) em um banco de dados objeto-relacional. Os objetos quando instanciados através da invocação do método Create (método construtor), são automaticamente alocados na memória Heap, local na memória principal que armazena todos os objetos que serão utilizados na aplicação. Quando um método que utiliza o objeto é finalizado, o objeto fica passível de ser coletado pelo Garbage Collector, através da chamada do método Free (método destrutor). Todos os objetos no Delphi herdam automaticamente da classe TObject, que possui alguns métodos auxiliares tais como os métodos especiais Create e Free, porém podemos criar nossos próprios métodos Create e Free, para inicializar e destruir, respectivamente, conforme necessário. Agora que temos uma classe definida (TCliente), será demonstrado como instanciar objetos. Uma variável do tipo da classe deve ser declarada, conforme abaixo: var Cliente: TCliente; Begin Cliente := TCliente.Create; // instancia o objeto cliente na memória // setando valores aos atributos do objeto ‘Cliente’ Cliente.Codigo := ‘1’; Cliente.Nome := ‘Revista Active Delphi’; Cliente.Telefone := ‘(16) 3024-8713’; // chamando o método Merge da classe TCliente, que irá persistir o estado do objeto if Cliente.Merge() then ShowMessage(‘Cliente Cadastrado com Sucesso!’) Else
  4. 4. ShowMessage(‘Erro ao Gravar o Cliente!’); Cliente.Free; // chamando o método destrutor, objeto ‘Cliente’ desalocado da memória end; Herança É o mecanismo do qual uma subclasse pode estender outra classe (superclasse), herdando os atributos e métodos desta, definindo um novo tipo de classe a partir de outra previamente existente, reutilizando código. Define-se assim um relacionamento de pai/filho entre tipos, o que origina a hierarquia de classes. Há dois tipos de herança: 1) herança de implementação: a classe derivada (subclasse) somente poderá assumir uma classe base na herança; 2) herança de interface: uma ou mais interfaces podem ser referenciadas na herança. Encapsulamento Consiste na separação de aspectos internos e externos de um objeto, escondendo os detalhes de implementação de um objeto. Utilizado amplamente para impedir o acesso direto ao estado de um objeto (seus atributos). Está ligado a implementação, é necessário expor uma interface simples e funcional para um objeto, que são definidos como públicos. O programador somente precisa conhecer a interface do objeto para utilizá-lo, podendo assim alterar a implementação interna de uma classe sem causar problemas as unidades que as usem. O recomendado é fornecer acesso aos atributos da classe sempre através de métodos acessores, que podem operar corretamente com os mesmos, nunca permitir acesso direto aos atributos, pois com isto perdemos o controle sobre o estado do objeto, tornando-o em alguns casos inconsistente. O controle do que está visível ou não, a interface pública de uma classe é definida através dos modificadores de visibilidade que veremos mais adiante. Modificadores de Visibilidade Definem a visibilidade de métodos e atributos dentro de uma classe. O Object Pascal possui três especificadores de acesso básico: Public (+): Métodos e atributos podem ser acessados de fora da classe, a partir de qualquer outra unidade. Subclasses herdam todas as assinaturas. Private (-): Métodos e atributos são somente acessados de dentro da classe, invisível para outras unidades. Os membros de uma superclasse não são herdados pelas suas subclasses. Protected (#): Oferece um nível intermediário de acesso entre o public e private. Os membros de uma superclasse podem ser acessados por membros dessa superclasse, por membros de suas subclasses e por membros de outras classes no mesmo pacote, ou seja, através da hierarquia de classes. O mecanismo em Object Pascal para encapsular os atributos em uma classe é definido como Property (propriedade), sobrecarregando as operações de leitura (read) e escrita (write) executadas quando a propriedade for acessada. Permite através de métodos acessores que se trabalhe diretamente com os atributos, realizando chamadas a métodos. Ao se pensar em
  5. 5. encapsulamento de atributos, os quais devem sempre estar protegidos, a declaração Property desempenha papel fundamental ao proteger o objeto, definindo uma forma padronizada de acesso ao estado do objeto. Para utilizar Property, os atributos devem ser declarados como private ou protected, os métodos como private ou protected, e as propriedades como public. Property NomePropriedade: TipoDado read [MetodoDeLeitura ou campo] // método get write [MetodoDeEscrita ou campo; // método set Na classe TCliente, encapsulamos os atributos do objeto através de Properties, e definimos um método para efetuar a leitura e escrita em cada propriedade, esses métodos são os famosos getters e setters, que são métodos auxiliares que operam sobre os atributos. Polimorfismo É o meio pelo qual duas ou mais classes derivadas de uma mesma superclasse podem executar métodos que contem a mesma assinatura (nomenclatura e lista de parâmetros), porém com comportamento diferentes. Como exemplo temos a superclasse TVeiculo que possui o método acelerar, uma subclasse TCarro que herda de TVeiculo o método acelerar, e outra subclasse TAviao que herda também de TVeiculo o método acelerar; porém o método herdado nas duas subclasses serão re-escritos pois o mecanismo de aceleração de um carro pode ser diferente de um avião. Concluí-se que apesar das subclasses terem a mesma assinatura, o método acelerar, possuem implementações diferentes em cada classe, daí o nome polimorfismo – muitas formas. Encontramos duas formas de implementar o polimorfismo: 1) Sobrecarga: Métodos da mesma classe podem ter o mesmo nome, desde que possuam quantidade ou tipo de parâmetros diferentes, declarados usando a palavra reservada overload. 2) Sobrescrita: Métodos da classe derivada podem ter assinaturas idênticas ao da superclasse, inclusive com parâmetros iguais, declarados usando a palavra reservada override; Type TVeiculo = class(TObject) // superclasse (classe base) protected procedure acelerar(Value: Integer); virtual; end; TCarro = class(TVeiculo) // classe TCarro herda de TVeiculo protected procedure acelerar(Value: Integer); override; // re-escrevendo o método acelerar end; TAviao = class(TVeiculo) // classe TAviao herda de TVeiculo protected
  6. 6. procedure acelerar(Value: Integer); override; overload; procedure acelerar(Value: Integer; Altura: Double); overload; // sobrecarga método end; Abstração É definido características essenciais de um objeto que o distingue de outros objetos quaisquer. Obtemos uma separação do que é essencial para o comportamento do objeto de sua implementação. Na hierarquia de classes, partimos das classes básicas para as específicas. Uma classe abstrata serve apenas como base para classes especializadas ou concretas, não possui implementação e não pode ser instanciada diretamente. Em Object Pascal pode-se definir um método abstrato em uma superclasse e só implementá-lo em subclasses, reforçando o polimorfismo. A definição de métodos abstratos é realizado utilizando a palavra reservada abstract, veja abaixo: Type TClasseAbstrata = class public function Merge(): Boolean; virtual; abstract; end; Associação É o mecanismo pelo qual um objeto utiliza os recursos de outro. Uma das interações mais ricas na orientação a objetos é a composição/agregação, a qual nos permite que um objeto contenha outros objetos criando complexos relacionamentos. Um dos relacionamentos mais instigantes que a composição/agregação provê é a possibilidade de criar um hierarquia de objetos, contendo relações todo-parte, de modo que possamos tratar exatamente da mesma forma objetos individuais, bem como objetos compostos. uses clCliente; // declarando a unit que contem a class TCliente Type TPedido = class(TObject) private { atributos / métodos privados } protected _codigo: String; _cliente: TCliente; // um pedido possui um cliente (*-1), associação public constructor Create; Property Codigo: String read getCodigo write setCodigo; Property Cliente: TCliente read getCliente write setCliente; end;
  7. 7. Interface A declaração de uma Interface define um “contrato” a ser seguido para o desenvolvimento de uma classe. Resumidamente a interface estipula que todas as classes vinculadas a ela deverão implementar todos os métodos declarados pela interface. Com isso obtemos um determinado grau de padronização, aumentando a versatilidade do modelo de classes de um modo geral. A definição de uma interface é realizado utilizando a palavra reservada interface. Type ICalculadora = Interface // nenhuma implementação deve ser definida aqui public function getResult(): Double; procedure setResult(Value: Double); procedure calculate(x, y: Double); property Result: Double read getResult write setResult; end; TCalculadora = class(ICalculadora) protected _resultado: Double public function getResult(): Double; procedure setResult(Value: Double); procedure calculate(x, y: Double); end; implementation // implementação dos métodos definidos na Interface Notação UML A orientação a objetos possui uma forma especial de modelagem via diagramas, tal qual como vemos na área de banco de dados com o DER (Diagrama Entidade-Relacionamento); chamado de UML (Unified Modeling Language – Linguagem de Modelagem Unificada). Seu objetivo é permitir que, através de diagramas simples, toda a lógica de interações entre os objetos que compõe o nosso sistema possa ser representada. Na UML encontramos diversos tipos de diagramas, um para cada finalidade, porém o mais importante deles é o diagrama de classe, do qual modelamos e definimos os relacionamentos entre as classes de uma aplicação. Para a modelagem UML podemos utilizar o software Judy Community (http://jude.change-vision.com/jude-web/product/community.html).
  8. 8. Figura 1 – Modelagem do diagrama de classe simplificado através do Judy. 2. Vantagens e desvantagens da Orientação a Objetos Orientação a objetos é difícil O paradigma orientado a objetos introduz conceitos que estão presentes no mundo real, porém para aqueles que vem da programação estrutural, os conceitos podem não ficar tão claros de imediato. Aproveitar o que a orientação a objetos pode lhe proporcionar por completo poderá ser mais trabalhoso inicialmente, porém oferece uma maior organização de código, que geralmente facilita o trabalho da equipe de desenvolvimento, com uma clara visão sobre as responsabilidades de cada classe dentro do contexto de uma aplicação; uma maior reutilização de código através de heranças e associações entre objetos (sem ficar realizando o famoso ‘CRTL+C’ e ‘CRTL+V’, como de costume no paradigma estrutural – o que está mais propenso a erros), oferecendo um considerável ganho de produtividade. A arquitetura de uma aplicação OO se mostra mais organizada a tornando flexível. A flexibilidade aqui significa realizar alterações futuras de forma fácil. A organização do código- fonte irá definir a qualidade de uma aplicação, permitindo construir abstrações, que com a programação estrutural ficaria muito difícil de implementar e estender. Projetos orientados a objetos são lentos Os projetos de software orientado a objetos tendem a ser mais complexos, pois envolvem uma grande gama de fatores, o que pode aparentar uma certa lentidão ao perceber uma série de heranças e associações entre objetos, quando todos os objetos são instanciados em memória
  9. 9. ao efetuar, por exemplo, uma operação como uma visualização de pedido de venda. Analisando pela lógica, pode ficar evidente que aplicações compostas por várias classes com foco nas unidades que realizam o processo e não no fluxo de execução (paradigma procedural) podem vir a ser mais lentos, porém não foi realizado uma experiência real, medindo o tempo de execução em uma aplicação estruturada e outra orientada a objetos, o que seria interessante para medir o desempenho de cada uma. É valido observar que podemos inicializar objetos na memória por demanda, ou seja, instanciá- los somente quando realmente formos utilizar os mesmos. Esta aparente desvantagem da OO pode ser recompensada por uma maior facilidade de manutenção, reutilização de código e flexibilidade do projeto de software. Analisando por outra visão, OO pode ser mais lenta que o paradigma procedural que por sua vez é mais lento que Assembly. Então, será a linguagem Assembly a real solução para a aparente lentidão na aplicação de software ? A aparente lentidão é nosso real problema e de nossos clientes ? Pense nisso! Modelo Orientado a objetos e bancos de dados relacionais Um dos problemas que a orientação a objetos encontra é a persistência de objetos em banco de dados relacionais, ou seja, em tabelas. Neste tipo de bancos de dados não conseguimos persistir objetos por inteiro, sem que ocorra uma serialização (colocar os atributos e seus respectivos valores de forma que fiquem em série, de forma seqüencial) do mesmo para que cada atributo seja mapeado para uma coluna da tabela no banco de dados relacional. O processo de serialização de objetos quando feito manualmente sem o auxilio de um framework que possua este propósito é custoso e demorado. Então, qual é a melhor forma e com um certo grau de produtividade e automatização poderíamos serializar objetos e efetuar a persistência? O ambiente Delphi a partir da versão 8, intitulado Delphi 8 for .NET introduziu o framework .Net que suporta o OR/M NHibernate (http://nhibernate.org) que é uma framework que realiza o mapeamento de classes de negócio para tabelas relacionais, automatizando assim tarefas repetitivas de forma elegante, nos poupando da preocupação de como serializar e recuperar objetos. A vantagem que um OR/M proporciona é a independência de banco de dados, podendo ser utilizado qualquer banco de dados suportado pelo framework, tornando as classes de negócios independentes e sem praticamente nenhuma sentença SQL declarada no código-fonte, pois o OR/M gerencia as instruções SQL necessárias. Com isso obtemos mais tempo para concentrar esforços no que realmente é importante: as classes de negócio de nossa aplicação! Maior reutilização de código Ao realizar a modelagem de diversas classes, que geralmente interagem entre si através das associações, construímos aplicações completas para um determinado domínio. A criação de diversas instâncias de objetos de uma mesma classe proporciona um grande exemplo de reutilização de código; observamos também que com a mesma modelagem (classe) podemos obter classes mais especialistas através da herança. Uma maior reutilização de código é obtida através da criação de componentes de software e da aplicação de Design Patterns.
  10. 10. Testes unitários (automação de testes) Ao desenvolver um software, antes da entrega para o cliente é necessário realizar uma bateria de testes para verificar a integridade e a qualidade do software do qual estamos oferecendo. O teste unitário é uma modalidade de teste do qual verifica-se a menor unidade do projeto de software, que pode ser identificado como um método, uma classe ou até mesmo um objeto. O teste ocorre de maneira isolada, definindo um conjunto de estímulos (métodos), e dados de entrada e saída associados a cada estímulo implementado pelo próprio desenvolvedor antes ou depois da modelagem do método ou classe. No ambiente Delphi encontramos o framework DUnit (http://dunit.sourceforge.net) que é uma ferramenta open-source para testes de unidade. O principal motivo para utilizar o DUnit integrado com o Delphi é a economia de tempo gasto em cada teste, a qualidade dos testes que podemos executar em nossas unidades de código, promovendo assim um melhor design da aplicação. Ao invés de colocar vários break-points na unit e ir “debugando manualmente” e verificando o valor de cada variável ou expressão, podemos escrever diversos estímulos e verificar se o resultado é o esperado. Figura 2 – Testes unitários sendo executados de forma independente da aplicação principal. Padrões de Projetos (Design Patterns) Os padrões de projetos foram propostos em 1994 por Erich Gamma, John Vlissides, Ralph Johnson e Richard Helm – conhecidos como GoF (Gang of Four). Tem por finalidade descrever soluções customizadas para problemas recorrentes no desenvolvimento de software orientado
  11. 11. a objetos. Com a crescente utilização do paradigma OO, surgiu a necessidade de desenvolver software de maior qualidade – e com isso diversos padrões foram introduzidos e consolidados, com o objetivo de aumentar a produtividade e reuso. Os padrões são organizados em: de criação, estruturais e comportamentais; seu escopo pode abranger uma classe ou objeto. Com a utilização de padrões de projetos não é necessário “re-pensar” em soluções para o mesmo problema recorrente, basta utilizar um padrão que já foi amplamente utilizado por várias empresas de desenvolvimento de software. Além disso definimos um padrão de comunicação que é compartilhado por toda a equipe de desenvolvimento. 3. Conclusão O paradigma orientado a objetos é uma evolução do processo de desenvolvimento estrutural e está conquistando espaço em equipes de desenvolvimento de software, permitindo uma maior organização de código, tornando os projetos de software flexíveis, ou seja, as alterações futuras são realizadas de forma fácil, pois temos uma clara visão sobre as responsabilidades de cada classe e suas interações. É notado um ganho de produtividade considerável através da herança, permitindo o código ser extensível e reutilizável. Através do diagrama de classes que a UML oferece temos uma documentação padronizada e simplificada do projeto de software, e em grandes equipes de desenvolvimento o entendimento de determinada classe pode se tornar tarefa fácil para quem estiver integrando uma nova equipe. A qualidade de software pode ser é facilmente alcançado com a utilização da orientação a objetos através dos vários tópicos mencionados no corpo deste artigo. Esta primeira parte introduziu os conceitos da orientação a objetos no ambiente Delphi. Nos próximos artigos iremos abordar a construção de uma aplicação básica de controle de estoque, colocando em prática todos os conceitos abordados neste artigo. Caso tenha alguma dúvida entre em contato comigo. Será um prazer ajudar. Até a segunda parte da série sobre a orientação a objetos.

×