Entendendo alguns conceitos fundamentais

Se empenhe em aprender sempre a fazer o básico bem feito e irá melhorar em todo o resto.
Michael Jordan

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çãomultiparadigma, 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

Há décadas, linguagens e frameworks têm sido desenvolvidos para suportar o desenvolvimento usando OO. Merecem destaque C# (.NET), Java e Python. Recentemente, Javascript também tem dado bom suporte ao paradigma. De qualquer forma, é importante entender que tecnologias surgem e evoluem rapidamente, entretanto, são pouco úteis quando desprezamos conceitos fundamentais.
0
Você concorda?x

Nesse livro, apresento POO utilizando C# como linguagem nos exemplos.

Definindo Orientação a Objetos

Orientação a objetos é um modelo, ou paradigma, para análise, projeto e programação de software. 

POO para mim significa apenas mensagens; retenção, proteção local e ocultação de estado e; late-biding extremo. (Alan Kay)

Sistemas construídos nesse paradigma são constituídos por objetos que se comunicam através da troca de mensagens. Eventualmente, essa troca de mensagens produzirá modificações de estado (dados) nestes objetos, entretanto, esse é apenas um efeito colateral que, em bons designs, terá menos relevância. O indispensável é que, apenas, os objetos tenham condições de continuar interagindo, obviamente sem entrarem em condição inválida.

Para que trocas de mensagens aconteçam, objetos expõem uma “interface pública”, bastando a um outro objeto conhecer esta “interface” para poder estabelecer comunicação. Essa ideia é poderosa pois reduz  acoplamento.

using System;

public class Program
{
    public static void Main()
    {
        var c = new Counter();
        c.Increment();
        c.Increment();
        Console.WriteLine(c.ToString());
    }
}

public class Counter
{
    private int _value;
	
    public int Increment() 
        => ++_value;
	
     public int Reset()
        => (_value = 0);
	
     public override string ToString()
        => $"Counter = {_value}";
}
A boa performance de um programador usando técnicas de orientação a objetos tem relação com sua capacidade em entender a importância de idealizar, do nível abstrato ao concreto, interações através da troca de mensagens. Entretanto, esse entendimento não é fácil de obter e demanda tempo de prática e esforço. Há, por isso, uma infinidade de programadores utilizando linguagens com suporte a orientação a objetos, mas, o código que escrevem não é orientado a objetos e, por isso, são difíceis de manter e evoluir.
1
Concorda?x

POO é estranhamente familiar

Praticamente todas as linguagens tradicionais oferecem algum suporte a estruturas mais complexas de dados que, a primeira vista, parecem com objetos (e classes). C, por exemplo, oferece structs.

typedef struct {
    float Weight;
    int Age;
    float Height;
} Person;

Entretanto, 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, ou melhor, a necessidade 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.

OO é uma forma de “modelar” o mundo

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 .

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.

Para tudo que observamos, identificamos atributos e comportamentos. Uma pessoa, por exemplo, pode ser descrita por atributos como a cor dos olhos, idade, altura, peso, etc. Ao mesmo tempo, identificamos comportamentos como a capacidade de caminhar, falar, respirar, etc.
0
Outros exemplos?x

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.

Em termos simples, um objeto é uma entidade que possui tanto estado 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.

Não é incorreto, aliás, dizer que bons designs orientados a objetos não possuem representações globais de dados.
0
Considerações?x

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 Lazy<Singleton> lazy
         = new Lazy<Singleton>(() => new Singleton());

    public static Singleton Instance
        => lazy.Value;

    private Singleton() { }
}

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.

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.

Considerações importantes sobre o “estado”

“Estado” deve ser 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.

Cuidado! Modelos com predominância de getters setters podem estar disfarçando modelos anêmicos.
1
O que você pensa a respeito?x

Construtores devem definir o “estado inicial válido”

Eventualmente, objetos precisam ser inicializados com um “estado mínimo” para serem considerados válidos. Para definição desse estado pode acontecer através de métodos “construtores”. Ou seja, um tipo especial de comportamento que pode ser ativado na criação de novas instâncias de objetos.

public class Person
{
    public Person(string id, string name, Cpf cpf) =>
        (Id, Name, Cpf) = (id, name, cpf);

    public string Id { get; private set; }
    public string Name { get; private set; }
    public Cpf Cpf { get; private set; }
}

Ferramentas importantes para OO

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, essas são quatro das ferramentas mais importantes de OO.

De tempos em tempos, técnicas de design passam a dar mais ou menos importância a cada um desses conceitos individualmente. Atualmente, por exemplo, há uma espécie de entendimento comum que herança geralmente desfavorece bons designs
1
Considerações?x
. Entretanto, independente de adotá-la ou não, é importante entendê-la. Afinal, no mínimo, todos teremos de lidar com código legado dos tempos em que herança era popular.

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.

public class Placing
{
	public Shape Shape {get; set;}
	public Point Position { get; set; }
}

public struct Point
{
	public Point(double x, double y)
		=> (X, Y) = (x, y);
	
	public double X { get; }
	public double Y { get; }
}

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; 
	} 
}

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!

Referências bibliográficas

ALBAHARI, Joseph. C# 9.0 in a Nutshell: the definitive reference. San Francisco, Ca: O’Reilly, 2021.

WEISFELD, Matt. The Object-Oriented Thought Process. 5. ed. San Francisco, Ca: Addison-Wesley, 2019.

Compartilhe este capítulo:

Compartilhe:

Comentários

Participe da construção deste capítulo deixando seu comentário:

Inscrever-se
Notify of
guest
4 Comentários
Oldest
Newest Most Voted
Feedbacks interativos
Ver todos os comentários
Dickson Souza
Dickson Souza
2 anos atrás
Feedback no conteúdo deste capítulo Cuidado! Modelos com predominância de getters e setters podem estar disfarçando modelos anêmicos." Ler mais »

Vejo você tocar com frequência nesse termo. Quando classes que representam o domínio estão anêmicas, em que outro local tem sido colocadas as mudanças de estado das classes? Em controladores no paradigma MVC ou espalhados pelo código em diversos trechos?

Márcio Pereira
Márcio Pereira
2 anos atrás
Feedback no conteúdo deste capítulo A boa performance de um programador usando técnicas de orientação a objetos tem relação com sua capacidade em entender a…" Ler mais »

Talvez um dos mais gritantes exemplos desse fato podem ser observados por ferramentas RAD (Delphi, VB) que surgiram em meados dos anos 90, que permitiam a configuração rápida do estado de componentes através da famosa caixa de propriedades que disponibilizavam inúmeros “get/setters” para atributos e eventos.

Marcelo Amaral
Marcelo Amaral
2 anos atrás

Elemar,
Creio que vale a pena fazer a ressalva sobre Herança: Há uma proposta atual que defende menos heranças por “classe base” e mais uso de Interfaces. Pode não fazer sentido para desenvolvedores menos experientes, mas é uma abordagem que reduz a complexidade no longo prazo.

DONISETTI FERREIRA COSMA
DONISETTI FERREIRA COSMA
2 anos atrás

Excelentes explicações, clara e objetiva.

Elemar Júnior

Fundador e CEO da EximiaCo, atua como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Mentorias

para arquitetos de software

Imersão, em grupo, supervisionada por Elemar Júnior, onde serão discutidos tópicos avançados de arquitetura de software, extraídos de cenários reais.

ElemarJúnior

Fundador e CEO da EximiaCo, atua como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

TECH

&

BIZ

-   Insights e provocações sobre tecnologia e negócios   -   

55 51 9 9942 0609  |  me@elemarjr.com

55 51 9 9942 0609  |  me@elemarjr.com

bullet-1.png

55 51 9 9942 0609  |  me@elemarjr.com

4
0
Quero saber a sua opinião, deixe seu comentáriox
()
x