segunda-feira, 24 de março de 2014

Extension Methods em C#

Introdução

Já alguma vez teve necessidade de adicionar métodos a classes de que não possuía o código-fonte?

Neste artigo vamos ver o que são os Extension Methods e como podemos usá-los no Microsoft .NET C#.

Esta é uma das melhores características que foram introduzidas no .NET, em termos de legibilidade do código. Vamos ver o que são os métodos de extensão, como criá-los e também alguns exemplos de aplicação prática.

Conteúdo

O que são Extension Methods

Os métodos de extensão permitem estender facilmente um tipo (como integer ou string), sem criar um novo tipo derivado, recompilar ou de alguma forma modificar o tipo original.

Os métodos de extensão são um tipo especial de métodos estáticos (static), mas são chamados como se fosse métodos de instância do tipo original. Para o código cliente, escrito em C# ou Visual Basic, não há qualquer diferença aparente entre chamar um método de extensão e os métodos que estão realmente definidos no tipo.

Os Extension Methods estão disponíveis no C# 3.0, a partir da versão 3.5 da Framework .NET e podem ser implementados em qualquer tipo, quer definido na framework quer definido pelo programador.

Uma das desvantagens consiste no facto de que, se criar um método de extensão com o mesmo nome de um método já existente no tipo, o compilador vai ligar a chamada ao método original e não à extensão. Os métodos de extensão só são chamados se não for encontrado nenhum método nativo com o mesmo nome.

Aviso

Se for declarado um método de extensão para o tipo Object, na realidade vai ser criado o método de extensão para todos os tipos da framework.

Como podemos criar Extension Methods

Vamos imaginar que pretendemos criar um método que retorna qualquer string, formatada como um nome pessoal, i.e., a primeira letra de cada palavra em maiúscula.

antónio josé silva -> António José Silva

A abordagem normal consiste em criar uma função e chamá-la sempre que se queira formatar uma string. Também é normal colocar essas funções numa classe utilitária:

public class MyUtilities
{
  public static string ToProperCase(string s)
  {
    if (s == null) 
      return null; 
    else
      return CultureInfo.CurrentUICulture.TextInfo.ToTitleCase (s); 
  }
}

Agora podemos converter uma string através duma chamada à função:

string test = "antónio josé silva";
Console.WriteLine(MyUtilities.ToProperCase(test));

No entanto, podemos estender a própria classe String, para suportar diretamente esta funcionalidade.


O procedimento básico para a criação de um método de extensão é o seguinte:




  1. Criar uma classe public static;


  2. Definir lá o método com as respectivas acções que se querem executar;


  3. Transformar esse método num Extension Method, com a utilização do modificador this.
public static class MyUtilities
{
  public static string ToProperCase(this string s)
  {
    if (s == null) 
      return null; 
    else
      return CultureInfo.CurrentUICulture.TextInfo.ToTitleCase (s); 
  }
}

A única coisa que o separa de qualquer outro método estático, é a palavras-chave this, nos parâmetros do método. Ela informa o compilador que este é um método de extensão para a classe string, e isso é realmente tudo que é necessário para criar um método de extensão.


Agora, podemos chamar o método ToProperCase() de forma directa:

string test = "antónio josé silva";
Console.WriteLine(test.ToProperCase());

Alguns exemplos de Extension Methods

Verifica se uma string é numérica:

public static bool IsNumeric(this string s)
{
  if (String.IsNullOrEmpty(s))
    return false;
  return Regex.IsMatch(s, "^[0-9]+$");
}
...
if (s.IsNumeric())
{
...

Verifica se uma string é representa um endereço de e-mail bem formatado:

public static bool IsValidEmail(this string s)
{
  if (String.IsNullOrEmpty(s))
    return false;
  
  s = s.Trim();
  var result = Regex.IsMatch(s, "^(?:[\\w\\!\\#\\$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\`\\{\\|\\}\\~]+\\.)*[\\w\\!\\#\\$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\`\\{\\|\\}\\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\\-](?!\\.)){0,61}[a-zA-Z0-9]?\\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\\[(?:(?:[01]?\\d{1,2}|2[0-4]\\d|25[0-5])\\.){3}(?:[01]?\\d{1,2}|2[0-4]\\d|25[0-5])\\]))$", RegexOptions.IgnoreCase);
  return result;
}
...
if (s.IsValidEmail())
{
...

Estes são apenas alguns exemplos de Extension Methods.


Poderão ver mais exemplos no seguinte endereço: extensionmethod.net

Programação Orientada a Objectos (II)

No artigo anterior - Programação Orientada a Objectos (I), vimos um pequeno resumo do paradigma da POO. Vamos agora aprofundar um pouco os conceitos relacionados com este paradigma.

Conteúdo

    Conceitos Fundamentais

    Passamos a fazer uma breve descrição dos conceitos fundamentais da POO.

    Objecto

    Trata-se de uma entidade composta por atributos (os dados) e comportamentos (conjunto de operações). O objecto é uma entidade autónoma que armazena dados e todas as funcionalidades necessárias à manipulação e processamento desses dados. É uma unidade dinâmica, com “vida”, derivada das acções que pode realizar – antropomorfismo.

    Um objecto tem um estado, correspondente ao conjunto dos valores dos seus atributos, num dado momento. O objecto tem a capacidade de responder a perguntas sobre os seus dados e a realizar operações sobre esses dados, alterando, assim, o seu estado.

    Apesar de existirem muitos objectos iguais, cada um deles tem identidade única. Por exemplo, todas as viaturas de um dado modelo são iguais, mas cada uma delas, por si é única. Devemos pensar num objecto como tendo sempre uma existência física, mesmo que seja apenas uma dada voltagem na memória RAM do computador.

    Finalmente, os objectos comunicam entre si, através de mensagens.

    Classe

    Uma classe é a representação abstracta de um conjunto de objectos, que têm características comuns. A classe representa um conceito e descreve formalmente as características e funcionalidades dos objectos. Podemos pensar na classe como a matriz genética do objecto, ou a planta de um prédio, ou a fórmula química de um medicamento.

    Em termos práticos, uma linguagem orientada por objectos, baseia-se em classes. A elaboração de um programa baseado na POO, não é mais que a criação das classes que o compõem.

    Uma classe deve descrever os dados (normalmente chamados propriedades) e os comportamentos, (normalmente chamados métodos) dos objectos. A classe deve descrever, ainda, a sintaxe das mensagens (normalmente chamadas eventos) que os objectos podem enviar.

    O paradigma da POO faz uma aproximação da resolução dos problemas através do computador, com o funcionamento natural do nosso cérebro. Desde que nascemos, quando observamos a realidade que nos rodeia, vamos classificando os vários objectos. À medida que crescemos e aumentamos a nossa experiência, altera-se a forma como classificamos os objectos, mais que não seja, pela quantidade de novas classes que vamos apreendendo.

    A classificação natural dos objectos é influenciada por diversos factores subjetivos, como a experiência, educação, religião, cultura, ambiente, etc. Assim, podemos ter classes muito semelhantes, com o mesmo nome até, mas que representam conceitos diferentes. Por exemplo, o conceito de mesa é completamente diferente entre um ocidental e um oriental.

    Na POO, também podemos ir construindo uma biblioteca e uma hierarquia de classes, que vai aumentando ao longo do tempo, permitindo-nos evoluir na forma de elaborar os programas.

    A classe tem a definição abstracta do conceito. O objecto é uma realização desse conceito.

    Se pensarmos na classe “SerHumano”, existem 7 biliões de pessoas que partilham as características definidas para esta classe. No entanto são todas distintas entre si. Tal como as pessoas, vários objectos da mesma classe, partilham o mesmo código genético, mas são diferentes entre si. A distinção entre dois objectos da mesma classe, faz-se através do conteúdo dos seus dados.

    Dois objectos da mesma classe, que estejam exactamente no mesmo estado, isto é, que tenham exactamente os mesmos valores para todos os dados, são, por definição, o mesmo objecto.

    Abstração

    A Abstração é considerada como a capacidade de modelar os objectos do mundo real, no problema que o programador esteja a tentar resolver. Neste contexto, podemos considerá-la como um mecanismo pelo qual restringimos o nosso universo de análise e as variáveis e constantes que compõem esse universo, desprezando os dados que não nos interessam.

    Por exemplo, se o programador estiver interessado em controlar dados dos funcionários de uma empresa, não vai criar objectos com todas as características das pessoas (seria possível?), mas apenas as que interessam para o problema: nome, morada, data nascimento, BI, salário, etc.

    Princípio da Abstração

    Em termos práticos, este princípio diz-nos que não necessitamos de conhecer a implementação de um objecto (o seu código), para o utilizar, mas apenas as suas características – Interface.

    Encapsulamento

    O Encapsulamento é essencial à POO. É a base de toda a abordagem da POO; isto porque contribui fundamentalmente para diminuir os malefícios causados pela interferência externa sobre os dados. Deste ponto de vista, o acesso aos dados só pode ser feito pelo próprio objecto, através de rotinas e procedimentos colocados "dentro" do objecto, que são chamadas pelo envio de mensagens.

    Princípio do Encapsulamento

    Em termos práticos, este princípio diz-nos que devemos proteger os dados dos objectos, tornando-os privados, permitindo apenas o acesso indireto aos mesmos, através de métodos públicos.

    Herança

    A Herança é um mecanismo que permite definir uma classe, a partir de outras, acrescentando a si própria as funcionalidades destas, o que permite altos graus de reutilização de código.

    Do ponto de vista prático, consiste num mecanismo simples (mas muito poderoso) para definir novas classes a partir de uma já existente. Assim sendo, dizemos que essas novas classes herdam todos os membros (propriedades+métodos) da classe original; isto torna o mecanismo de herança uma técnica muito eficiente para construir, organizar e reutilizar código.

    A herança possibilita a criação de uma nova classe de modo a que essa classe (denominada subclasse ou classe derivada) herda TODAS as características da classe base (denominada super classe, ou classe primitiva). A classe derivada pode ter propriedades e métodos próprios e/ou pode reimplementar propriedades e métodos da classe base.

    Uma classe pode ser derivada a partir de uma ou de mais do que uma classe – Herança Múltipla. Neste caso, a classe derivada herda todas as características das várias classes base que lhe dão origem. Apesar deste conceito de herança múltipla ser bastante corrente no plano teórico, poucas linguagens de programação o implementam, pelo que, muitas vezes, é necessário fazer algumas adaptações e criar um novo nível de abstração, para se poder implementar.

    Polimorfismo

    O termo Polimorfismo, etimologicamente, quer dizer "várias formas". Todavia, no universo da POO, é definido como sendo código que possui "vários comportamentos" ou que produz "vários comportamentos"; em outras palavras, é uma porção de código que pode ser aplicado a várias classes de objectos.

    O polimorfismo é um conceito relacionado com a teoria dos tipos, segundo o qual, um nome (como a declaração de uma variável) pode referenciar objectos de várias classes diferentes, relacionadas entre si por uma classe base comum (Booch).

    Assim, a implementação dos métodos é determinada pelo tipo do objecto e não pelo tipo da sua declaração (dynamic binding).

    Conceitos Complementares

    Com a evolução das linguagens orientadas a objectos, foram surgindo novas funcionalidades, que deram origem a alguns conceitos complementares aos iniciais, mas também importantes.

    Interface

    Corresponde à definição do nome de um conjunto de operações que caracterizam o comportamento de um elemento (UML). Também podemos pensar num Interface como um “contrato” que um dado objecto deve satisfazer.

    Em termos práticos, a programação de um Interface consiste na definição das Assinaturas (nome + argumentos) dos métodos. Dependendo de cada linguagem, podem existir outras funcionalidades, mas nunca é feita nenhuma implementação (código propriamente dito).

    De um modo geral, as diferentes linguagens orientadas a objectos, apesar de não permitirem herança múltipla de classes, permitem herança múltipla de interfaces. Neste contexto, entende-se que uma classe quando derivada de um interface, herda a obrigatoriedade de implementar os métodos definidos neste.

    Classes Abstractas / Classes Seladas

    As Classes Abstractas são aquelas que não permitem instanciação, ou seja, não permitem a criação de objectos a partir delas, somente permitem derivar outras classes.

    As Classes Seladas são aquelas que não permitem derivar outras classes, só permitem a instanciação de objectos.

    Por uma questão de lógica, uma classe não pode ser, simultaneamente abstracta e selada.

    Membros Abstractos / Membros Virtuais

    Um membro (propriedade ou método) declarado como abstracto numa classe base, obriga a que ele seja implementado nas classes derivadas.

    Um membro declarado como virtual, permite (facultativo) a sua redefinição nas classes derivadas.

    Sobreposição (Override) / Sobrecarga (Overload)

    O override e overload de membros são conceitos relacionados com o polimorfismo.

    Estamos a fazer override, quando criamos uma nova implementação para um dado membro, numa classe derivada.

    Estamos a fazer overload, quando criamos implementações diferentes para o mesmo método, na mesma classe. As linguagens orientadas a objectos permitem fazer isto, variando os argumentos das várias implementações do mesmo método.

    Podemos fazer, simultaneamente, override e overload ao mesmo método.

    Construtores / Destrutores

    O Construtor é um método especial que, se existir, é executado automaticamente sempre que é instanciado um novo objecto. Na implementação deste método, é normal colocar a inicialização dos dados do objecto.

    As classes podem ter, também, um método Destrutor, que é chamado automaticamente, no momento em que o objecto é destruído (apagado da memória do computador). Na implementação deste método, podemos colocar código de validação ou fazer a persistência (guardar permanentemente) dos dados.

    Membros Estáticos / Membros de Instância

    Os membros Estáticos, são partilhados por todas as instâncias da classe. Por outro lado, os membros de instância, são próprios de cada objecto, não sendo, portanto, partilhados.

    Classes Estáticas

    São classes que só possuem membros estáticos. Como tal, não podem ser instanciadas. Estas classes são usadas, normalmente, como um repositório ou biblioteca de funções.

    Componentes de um Objecto

  • DADOS: definem o estado actual do objecto;
  • COMPORTAMENTOS: acções que o objecto pode executar;
  • INTERFACE: tudo aquilo que, num objecto, é visível do exterior. Permite aceder aos dados do objecto e executar as acções disponibilizadas;
  • IMPLEMENTAÇÃO: conjunto de todo o código existente na classe de um objecto.


    Relações entre Objectos / Módulos

    Associação / Agregação / Composição

    A associação representa uma relação directa ou indirecta entre objectos. É uma relação que denota uma conexão semântica entre duas classes (Booch).

    Na POO, associação tem um significado semelhante a união. Numa associação, um objecto é formado a partir da união de dois ou mais objectos (relação do tipo todo-parte). Na POO existem dois tipos de associação: a agregação e a composição.

    Na composição, o todo tem propriedade sobre as partes, isto é, quando um objecto é criado por composição de outros objectos, ele tem o poder de criar ou destruir esses objectos.

    A agregação é a uma forma mais geral de associação. Um objecto criado por agregação não tem o poder de criar ou destruir as partes que o compõem. Tem apenas uma referência (apontador) para os outros objectos.

    Os objectos complexos podem ser criados por herança ou por associação (normalmente composição).

    Coesão (Cohesion)

    A coesão é a medida do grau com que um módulo (método ou classe) implementa uma função ou objectivo único, num sistema.

    Os módulos que implementam muitas funções, dizem-se como tendo Baixa Coesão (Low Cohesion). É desejável que os módulos tenham Alta Coesão (High Cohesion).

    Acoplamento (Coupling)

    O acoplamento é a medida do grau de interdependência entre módulos, num sistema. É desejável que um sistema tenha Baixo Acoplamento (Loose Coupling), o que minimiza o perigo de alterações num módulo implicarem a alteração de outros, para não ocorrerem erros.

    A coesão é uma medida aplicada a um único módulo, enquanto que o acoplamento se aplica a conjuntos de módulos

  • sábado, 22 de março de 2014

    Programação Orientada a Objectos (I)

    Breve História

    A Programação Orientada a Objectos (POO) é um paradigma actual de análise, projecto e desenvolvimento de aplicações informáticas.

    A primeira linguagem de programação a implementar sistematicamente os conceitos de OOP foi a Simula 67. De seguida surgiu a linguagem Smalltalk, criada por Alan Kay, na Xerox, que pode ser considerada a linguagem que popularizou e incentivou a utilização da POO.

    Apesar de ser um paradigma de programação já bastante antigo (final dos anos 60), o seu grande desenvolvimento só se deu a partir da 2ª metade da década de 90, fortemente impulsionado pelo advento da Internet. Actualmente, existe uma grande variedade de linguagens para POO, tendo, inclusivamente, sido feitas extensões a linguagens tradicionais, para suportar objectos (C++, NetCobol, Delphi, Java, C#, Visual Basic, etc.).

    Ideias base

    O uso das primeiras linguagens de POO, introduziu um conceito que até então passava "despercebido" à maioria dos projectistas: a similaridade com o mundo real.

    O mundo real é composto por objectos que interagem entre si: o martelo interage com o prego, o prego com a parede, um semáforo interage com os condutores, etc. Na POO, os objectos de software modelam os objectos do mundo real e interagem entre si através do envio de mensagens. O programador tem a responsabilidade de definir o mundo dos objetos, sendo a sua principal função a especificação das mensagens que cada objecto pode receber e qual a acção que o objecto deve realizar para cada mensagem específica.

    Se consideramos a Orientação a Objectos como um novo paradigma de desenho de software, devemos considerar, também, uma nova maneira de pensar, porque apesar de a escrita do código continuar a ser procedimental, alguns conceitos mudam radicalmente: a estruturação e o modelo computacional.

    Este paradigma permite-nos dividir o problema em unidades autónomas – os objectos – que, para além de armazenarem dados, têm a capacidade de realizar operações sobre esses dados. Assim tornou-se mais fácil simular a realidade (e os problemas que queremos solucionar) através de programas de computador.

    Basicamente, a POO utiliza os mesmos princípios da engenharia de hardware, que projecta novos equipamentos usando os mesmos componentes básicos como resistências, fusíveis, díodos, transístores, chips, etc. Os "objectos" já existentes são utilizados para produzir novos "objectos", tornando essa metodologia mais poderosa que as metodologias tradicionais de desenvolvimento de software.

    Em termos de modelo computacional, podemos dizer que enquanto as metodologias tradicionais utilizam o conceito de um processador, uma memória e dispositivos de I/O para processar, armazenar e exibir as informações, a POO emprega um conceito mais real, mais concreto, que é o de Objecto.
     

    Resumo Paradigma da POO

    • É o paradigma mais utilizado actualmente na programação;
    • Os objectos modelam entidades do mundo real ou entidades abstractas;
    • Os programas simulam as situações e os problemas que queremos resolver;
    • Na POO, tudo são objectos. As características dos objectos são, por sua vez, objectos também;
    • Um programa não é mais do que um conjunto de objectos a interagir (enviar mensagens) entre si.

    Vantagens e Desvantagens

    As principais vantagens da POO são as seguintes:

    • Continuidade no desenvolvimento da solução do problema;
    • Facilidade de manutenção e introdução de alterações e melhorias;
    • Aumento da produtividade;
    • Aumento da robustez;
    • Modularidade;
    • REUTILIZAÇÃO.

    Nos paradigmas anteriores, existia um abismo entre as fases de desenvolvimento dum sistema: AnáliseProjectoProgramação, porque cada uma delas gerava documentação distinta.

    Na POO, padroniza-se um documento que será utilizado por todos os elementos: o Diagrama de Classes. Surgiu entretanto a linguagem UML, que serve para especificar e ajudar a criar este tipo de documentação.

    Como o objecto engloba internamente todos os mecanismos do seu funcionamento, caso haja necessidade de alterá-lo, os componentes (outros objectos) que dependem deste não necessitam de qualquer alteração.

    Uma vez que existe uma colecção de classes de objectos devidamente testados, ao criarmos um novo sistema, basta utilizar estas classes, sem necessidade de alterar o código. Isto aumenta a produtividade na criação de programas e aumenta a qualidade dos mesmos, uma vez que os objectos são testados por vários programadores.

    Os sistemas são extremamente modulares, tornando-se muito facilmente extensíveis. Para além de podermos utilizar os objectos de um dado sistema, noutro, sem qualquer alteração, também podemos criar novos objectos baseados em outros já existentes, que herdam as características dos objectos base.

    As principais desvantagens da POO são as seguintes:

    • A curva de aprendizagem do paradigma da POO é bastante superior ao de outros paradigmas anteriores (Programação Procedimental);
    • Maior complexidade de conceitos e estrutura;
    • Menor desempenho em termos de performance para tarefas simples de computação.