Três são os conceitos básicos de programação a orientação a objeto: Herança, polimorfismo e encapsulamento. Neste artigo vamos ver como aplicar o encapsulamento no seu código em C!
A programação orientada a objeto nos concede muitas vantagens. O encapsulamento nos permite que só exista uma forma de acessar nossos dados, os famosos “getters”, e uma forma de alterá-los, os famosos “setters”. Não existe acesso direto ao atributo! Isto nos dá segurança pois podemos fazer validações antes de alguém acessar ou alterar o nosso dado.
Outra vantagem do encapsulamento é que os detalhes internos de implementação são escondidos do resto do código. Desta forma eles podem ser alterados dependendo de plataforma ou situação sem que o resto do software sofra alterações.
Exemplo de Encapsulamento
Vamos imaginar a classe Usuario, o Usuario possui um nome, um login e uma senha. Além destes atributos ele também possui o método “autenticar” que retorna verdadeiro se a combinação de login e senha for verdadeira. Os getters e setters não são colocados no diagrama de classe para fins de visibilidade. Mas também considere a existência dos métodos getNome, getLogin, getSenha, setNome, setLogin e setSenha.
No nosso caso a classe Usuario em C consiste de dois arquivos: um .h e um .c. No caso, usuario.c e usuario.h.
Segundo as boas praticas da orientação a objeto os atributos de uma classe são internos a ela e só ela os conhece. Ou seja, a estrutura de dados com os atributos Nome, Login e Senha só pode ser conhecida por quem a utiliza, no nosso caso o arquivo usuario.c:
#include "usuario.h"
#include
#define TAM_MAX_SENHA 10
struct usuario{
char* nome;
char* login;
char* senha;
};
struct usuario* usuario_new(void)
{
struct usuario* usr;
usr = malloc(sizeof(struct usuario));
return usr;
}
int usuario_del(struct usuario* usr)
{
free(usr);
return 1;
}
int usuario_autentica(struct usuario* usr, char* senha)
{
if (strcmp(usr->senha, senha, TAM_MAX_SENHA))
return 1;
else
return 0;
}
char* usuario_getSenha(struct usuario* usr)
{
return usr->senha;
}
char* usuario_getLogin(struct usuario* usr)
{
return usr->login;
}
char* usuario_getNome(struct usuario* usr)
{
return usr->nome;
}
int usuario_setSenha(struct usuario* usr, char* senha)
{
usr->senha = senha;
return 1;
}
int usuario_setLogin(struct usuario* usr,char* login)
{
usr->login = login;
return 1;
}
int usuario_setNome(struct usuario* usr, char* nome)
{
usr->nome = nome;
return 1;
}
Porém os objetos do tipo “Usuario” devem poder ser utilizados por todos que o desejarem. Por isso uma declaração da nossa estrutura de dados é feita no nosso arquivo usuario.h, porém é declarado como uma estrutura opaca, ninguém sabe o que realmente tem dentro dela a não ser nosso .c.
Arquivo usuario.h:
struct usuario; /* retorna um objeto do tipo usuario */ struct usuario* usuario_new(void); /* deleta o objeto do tipo usuario */ int usuario_del(struct usuario* usr); /* verifica se a senha eh igual */ int usuario_autentica(struct usuario* usr,char* senha); /* retorna a senha do usuario */ char* usuario_getSenha(struct usuario* usr); /* retorna o login do usuario */ char* usuario_getLogin(struct usuario* usr); /* retorna o nome do usuario */ char* usuario_getNome(struct usuario* usr); /* seta a senha do usuario */ int usuario_setSenha(struct usuario* usr, char* senha); /* seta o login do usuario */ int usuario_setLogin(struct usuario* usr,char* login); /* seta o nome do usuario */ int usuario_setNome(struct usuario* usr, char* nome);
Para testar vamos utilizar este main.c:
#include
#include
#include "usuario.h"
int main()
{
struct usuario* usr;
usr = usuario_new();
usuario_setSenha(usr, "testando");
printf("Senha: %s\n",usuario_getSenha(usr));
return 0;
}
É impossível um arquivo que não o usuario.c acesse os atributos e métodos desta classe e esses acessos são controlados pelos nossos getters e setters via usuario.c. Se adicionarmos esta linha ao arquivo main.c veremos um erro:
/* A linha abaixo ocasiona em erro de compilacao, o main nao conhece os atributos de usuario */
printf("Senha: %s\n",usr->senha);
Se formos desenvolver para um sistema embarcado Bare metal e não quisermos utilizar alocação dinâmica de memória podemos simplesmente mudar a definição da nossa struct para algo do tipo:
struct usuario{
char nome[TAM_MAX_NOME];
char login[TAM_MAX_LOGIN];
char senha[TAM_MAX_SENHA];
};
e reescrever nossos métodos no arquivo usuario.c, eliminando os métodos de alocação e liberação de memória.
Observação: Diversas verificações no arquivo usuario.c foram deixadas de lado para fins didáticos. Verificações como se o objeto é nulo antes de dar um get ou set, ou ainda se o malloc/free foram realizados com sucesso.











Obrigado pelo POST André, excelente! Sempre procuro artigos relacionados a essas conversões para poder trabalhar a partir de diagramas UML e desenvolvimento em camadas (pensando em desenvolvimento de firmware) Uma dúvida que eu tenho seria a seguinte: Suponha que eu crie a estrutura em um arquivo (.h) typedef struct { int inicio; int fim; } timeTypedef; Teoricamente todos que tiverem essa referência podem então acessá-la. Mas daí eu crio a variável em um Handler, muito comum em microcontroladores (considerando que seja uma camada superior, por exemplo o handler de um timer) timeTypedef tempo[TAM_MAX_TIME]; Teria o mesmo efeito do exemplo que… Leia mais »
Regra do encapsulamento: Evite globais, passe por parâmetros.
Olá Erisson, agradeço.
Primeiramente não teria o mesmo efeito pois todos que incluírem este .h poderão mexer diretamente nos atributos do teu timer. O Lavratti escreveu algo que você pode gostar, não sei se já leu: https://embarcados.com.br/principio-da-unica-responsabilidade-em-firmwares/
O dano a um sistema embarcado com vazamento de memória ou falta de memória pode ser catastrófico. Não esqueça que muitos rodam por anos sem reinicializar.
Além disso alocar memória é uma tarefa “blockante”, o fluxo do seu sistema não fica mais tão determinístico. O que é crucial para sistemas de tempo real.
Abraço
Erisson, Caso você tenha a seguinte declaração: timeTypedef tempo[TAM_MAX_TIME]; Não vai ser utilizado um tipo opaco, e, portanto, vai ser possível acessar todos os membros da estrutura em cada instância criada no array. Caso você queira referenciar (consumir) essas instâncias criadas em outros módulos, eu criaria funções globais que o fizessem, evitando criar variáveis globais e encapsulando as suas implementações. Na minha opinião, não é utilizada com frequência alocação dinâmica de memória em sistemas bare-metal pois na grande maioria dos casos existe pouca memória disponível no sistema (mem < 100kB). Por consequência, os algoritmos de alocação (allocators) acabam comprometendo o… Leia mais »
Oi André, como vai ?
Parabéns pela iniciativa em divulgar a técnica.
Um typedef para “struct usuario*” deixaria mais limpo o código, não ?
No mais, a técnica de ponteiros opacos é a única coisa que consegue resolver vários problemas de programação em C. Realmente recomendada.
Marcelo Barros
Olá Marcelo, vou bem! Muito obrigado. Também gosto muito desta técnica.
Sim um typedef deixaria o código bem mais limpo! Mas um typedef de um ponteiro esconde algumas informações importantes.
Abraço
Sim, eu concordo, numa situação usual. É que aqui o objetivo é mesmo esconder. Nesse caso, o typedef geraria uma espécie de handle para aquilo que você decidiu encapsular. As chamadas sucessivas apenas levam um handle para o recurso, o usuário da API não precisa saber do que se trata. Bom, mas é um detalhe. Até !