Programação orientada a objetos não é tema novo. O conceito existe, pelo menos, desde a década de 1960. Linguagens como C++ e Smalltalk, ambas com suporte a OO, já tinham grande adoção na década de 1980. Entretanto, é fato que o paradigma ganhou muito em popularidade nas décadas de 1990 e 2000 na “carona” de linguagens como Java e C#.
Um pouco mais sobre C#
C# é uma linguagem de programação, multiparadigma, de tipagem forte, desenvolvida pela Microsoft como parte da plataforma .NET. A sua sintaxe orientada a objetos foi baseada no C++ mas inclui muitas influências de outras linguagens de programação, como Object Pascal e, principalmente, Java. O código fonte é compilado para Common Intermediate Language (CIL) que é interpretado pela máquina virtual Common Language Runtime (CLR). C# é uma das linguagens projetadas para funcionar na Common Language Infrastructure da plataforma .NET Framework.
– Wikipedia
Nesse livro, apresento POO utilizando C# como linguagem nos exemplos.
Procedural vs Orientado a Objetos
O “mundo” é procedural. Todos acordamos, escovamos os tentes, tomamos café e vamos ao trabalho (não necessariamente nessa ordem). Logo, é natural que tenhamos facilidade para escrever e, principalmente, descrever programas de computador como sequências ordenadas de instruções, que produzirão eventos.
O mundo, também, é orientado a objetos. Tudo é objeto! Nós, nossos carros, nossas casas e tudo que conhecemos são objetos! Cada objeto por sua vez tem comportamentos e, algumas vezes, interagem de forma previsível. Logo, também é natural que tenhamos facilidade para criar “modelos” dos objetos do mundo real (e alguns abstratos) em nossos códigos.
Boa parte da “lógica procedural” continua válida quando escrevemos programas orientados a objetos e, de uma forma geral, OO incorpora detalhes à visão de quem está programando. Enquanto a lógica procedural tem analogia comum com “receitas de bolo”, descrevendo apenas “como” as coisas precisam ser feitas, a lógica orientada a objetos inclui “quem”, ou seja, os atores (quem desempenham as ações) e os alvos .
Pilares da Orientação a Objetos
São consideradas adequadas para programação orientada a objetos as linguagens que ofereçam suporte nativo para encapsulamento, herança, composição e polimorfismo. Aliás, esses quatro pilares são fundamentos de OO.
O que é um objeto?
Antes de mergulharmos nas vantagens da programação orientada a objetos é importante que dediquemos algum tempo respondendo uma questão fundamental: Afinal de contas, o que é um objeto?
Responder essa questão é, ao mesmo tempo, fácil e difícil. A dificuldade reside no fato de que o entendimento de qualquer paradigma não é algo trivial. Entretanto, há certa facilidade pelo fato de nós, seres humanos, entendermos empiricamente o mundo – pessoas, animais, coisas – a partir de objetos.
Em temos simples, um objeto é uma entidade que possui tanto dados como comportamento. Aliás, é essa combinação que diferencia a orientação a objetos de outros paradigmas.
Na programação procedural, e até mesmo em abordagens funcionais, o código fica isolado em funções e procedimentos. Enquanto isso, os dados ficam “armazenados” em estruturas separadas para serem manipuladas por essas funções e procedimentos.
Abordagens de desenvolvimento não orientadas a objetos tendem a manter dados com visibilidade global, amplamente acessíveis e modificáveis. Na prática, isso dificulta o controle e a gestão dos dados.
Singleton é um anti-pattern
Em 1994, Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides lançaram “Design Patterns” – um clássico da programação orientada objetos. No livro, os autores apresentaram uma série de padrões de programação, organizados em uma espécie de catálogo, que se converteram em uma espécie de “língua franca” entre desenvolvedores.
Um dos padrões ganhou, na época, grande popularidade: Singleton.
O Singleton é um padrão de projeto criacional, que garante que apenas um objeto desse tipo exista e forneça um único ponto de acesso a ele para qualquer outro código. Atualmente, esse padrão é desestimulado em função de criar dados com visibilidade global.
public sealed class Singleton { private static readonly<Singleton> lazy = new Lazy<Singleton>(() => new Singleton()); public static Singleton Instance => lazy.Value; private Singleton() { } }
POO é uma evolução
Programação orientada a objetos apresenta evoluções para conceitos que emergiram, também, em abordagens procedurais.
Praticamente todas as linguagens tradicionais oferecem algum suporte a estruturas mais complexas de dados que tem muitas das características observadas em objetos (e classes). C, por exemplo, oferece structs.
typedef struct { float Weight; int Age; float Height; } Person;
Reforçamos, entretanto, que objetos são muito mais do que arranjos de valores tipos primitivos, como int e string. No paradigma OO, esses valores são atributos, mas objetos também expõe comportamentos através de métodos. Em um objeto, métodos são utilizados para executar operações nos dados.
var calculator = new Calculator(); calculator.Add(10f); calculator.Sub(5f); System.Console.WriteLine(calculator.CurrentResult); public class Calculator { public float CurrentResult { get; private set; } public void Add(float value) { CurrentResult += value; } public void Sub(float value) { CurrentResult -= value; } public void Mul(float value) { CurrentResult *= value; } public void Div(float value) { CurrentResult /= value; } }
Outra diferença marcante das tradicionais estruturas de dados é a possibilidade de controlar o acesso aos membros que compõe os objetos, tanto atributos quanto métodos.
Data/Behavior hiding
A capacidade de um objeto “esconder” dados e, também comportamentos, é conhecido como hiding. Trata-se de um aspecto importante do encapsulamento.
O que EXATAMENTE é um objeto?
Já sabemos que um objeto é um “encapsulamento” de dados e comportamentos.
Objetos são os blocos de construção de programas construídos usando o paradigma orientado a objetos. Esses programas, em execução, podem ser entendidos como coleções de objetos interagindo.
O conjunto de todos os dados “armazenados” em um objeto é designado como seu “estado”. Na terminologia empregada em OO, os diversos dados de um objeto são atributos.
Os comportamentos representam as operações que um objeto consegue desempenhar. Em linguagens procedurais, comportamentos são definidos em procedimentos, funções ou subrotinas. Em programas OO, os diversos comportamentos de um objeto são métodos. Os métodos de um objeto são “invocados” através de mensagens.
Idealmente o “estado” é modificado através de “comportamentos”
Idealmente, o estado (conjunto de valores dos atributos) de uma entidade devem ser acessados ou modificados apenas através de comportamentos. Isso impede, ou pelo menos deveria impedir, que objetos fiquem com “estado inválido”.
Em modelagens mais simples, acesso e mudança no valor de atributos ocorre através de métodos getters e setters.
public class Person { private string _name; public string GetName() => _name; public void SetName(string value) { _name = value; } }
Em .NET, getters e setters são considerados “propriedades” de um atributo e podem ser expostos com uma notação mais expressiva.
public class Person { private string _name; public string Name { get => _name; set { _name = value; } } }
Aliás, já há algum tempo, linguagem C# oferece um “açúcar sintático” para propriedades simples (sem código associado a atribuição de valores).
public class Person { public string Name {get; set;} }
Em algumas fontes getters são indicadas como accessors e setters como mutators.
Açúcar sintático
Em ciência da computação, um açúcar sintático é uma sintaxe dentro da linguagem de programação que tem por finalidade tornar suas construções mais fáceis de serem lidas e expressas. Ela faz com que o uso da linguagem se torne “mais doce” para o uso humano, permitindo que suas funcionalidades sejam expressas mais claramente, mais concisamente ou, ainda, como um estilo alternativo preferido por alguns.
O que é uma classe?
De maneira simples, uma classe “projeto” para um objeto. Toda vez que criamos (instanciamos) um objeto, usamos uma classe como “ponto de partida” para indicar como o objeto deverá ser estruturado.
Resgatando pilares
Encapsulamento
Uma das ideias mais poderosas de OO é que objetos não devem revelar todos seus atributos ou comportamentos (métodos). Aliás, um objeto deverá revelar apenas aqueles métodos que devam funcionar como “interface de comunicação” para interações. Dessa forma, detalhes que não sejam pertinentes devem ser ocultados.
Enquanto o encapsulamento determina que objetos contem tanto atributos quanto comportamentos, designa-se hiding a prática de manter “privados” atributos e comportamentos que não forem relevantes externamente.
O conjunto de atributos e métodos públicos (visíveis) de um objeto constituem sua “interface”.
Herança
Muitas vezes, diferentes classes de objetos apresentam características, ou seja, atributos e comportamentos comuns. Nesses casos, uma alternativa é criar novas classes “herdando” essas características de outras.
A classe que “fornece” as características para outras é conhecida como super classe, ou classe base. Por outro lado, as classes que “herdam” as características são designadas como sub classes, classes filhas ou, ainda, classes derivadas.
Is-a
Um “truque” para determinar se herança faz sentido é tentar estabelecer relação “é um” entre uma classe derivada e uma classe base.
Por exemplo, Circle é um Shape. Assim como Rectangle é um Shape.
Polimorfismo
Polimorfismo é uma palavra de origem grega que significa, literalmente, muitas formas.
Embora o conceito de polimorfismo tenha relação bem próxima com o de herança, é adequadamente entendido como, de forma independente, uma importante feature de OO.
O conceito de polimorfismo se aplica, por exemplo, em uma hierarquia de heranças, todas as classes derivadas herdam a interface pública de uma classe base. Entretanto, cada classe base deve ter o poder de “redefinir” a forma como responder a uma mesma mensagem.
public abstract class Shape { public abstract double Area { get; } public override string ToString() => $"Area { Area }"; } public class Square : Shape { public double Side {get; set;} public override double Area { get => Side * Side; } } public class Circle : Shape { public double Radius {get; set;} public override double Area { get => Radius * Radius * System.Math.PI; } }
Por exemplo, considere uma classe base Shape que possua uma propriedade Area. O valor dessa propriedade deverá ser determinado de acordo com a especialização (derivação). Ou seja, embora todo Shape possua uma propriedade Area, cada especialização deverá prover uma “fórmula” diferente.
Composição
É natural assumir que objetos possam “conter” outros objetos. Um carro tem um motor, rodas, etc. Objetos de uma determinada classe podem, eventualmente, possuir referências para outros objetos em seus atributos.
Has-a
Um “truque” para determinar se composição faz sentido é tentar estabelecer relação “tem um” entre uma classe derivada e uma classe base.
Por exemplo, Car tem um Engine.
Antes de avançar…
Programação orientada a objetos (POO), hoje em dia é conhecimento fundamental para qualquer desenvolvedor de software. Entretanto, não é a única alternativa para desenvolvimento. Nos últimos anos, o paradigma funcional tem ganho muito espaço e relevância a ponto de influenciar, inclusive, linguagens “orientadas a objeto” por natureza.
Caso você não seja familiar a ideia de programação funcional, recomendo que busque informações. Também recomendo que você busque entender features importantes de .NET, como LINQ, que são profundamente influenciadas por ideias funcionais.
Mais do que tudo, codifique!
ótimo artigo! Reuniu conceitos nada simples de explicar com maestria. Ótimo.
Excelente artigo. Muito didático!
Cara o conteúdo está ficando ótimo. Quero deixar minha contribuição, embora eu tenho certeza que você irá abordar esses assuntos em algum momento. Adicionar tópicos sobre acoplamento, coesão, alguns outros tópicos abordados nos princípios GRASP e SOLID. Muito obrigado por compartilhar seus conhecimentos conosco.
Não considero herança desfavorece bons designs, mas sim heranças baseadas em dados em vez de heranças baseadas em comportamento e em vários casos composição é uma caminho melhor.