Antes da POO, os membros da estrutura de dados eram públicos?

44

Quando uma estrutura de dados (por exemplo, uma fila) é implementada usando uma linguagem OOP, alguns membros da estrutura de dados precisam ser privados (por exemplo, o número de itens na fila).

Uma fila também pode ser implementada em uma linguagem processual usando um structe um conjunto de funções que operam no struct. No entanto, em uma linguagem processual, você não pode tornar os membros de um structparticular. Os membros de uma estrutura de dados implementados em uma linguagem processual foram deixados em público ou houve algum truque para torná-los privados?

Christopher
fonte
75
"alguns membros da estrutura de dados precisam ser privados" Há uma grande diferença entre "provavelmente deveria ser" e "precisa ser". Me dê uma linguagem OO e garanto que posso criar uma fila que funcione perfeitamente, mesmo com todos os seus membros e métodos sendo públicos, desde que você não abuse dessa liberdade.
8bittree
48
Para dar uma idéia diferente do que a @ 8bittree disse, ter tudo público é bom se as pessoas que usam seu código forem disciplinadas o suficiente para manter a interface que você definiu. A construção do membro privado surgiu por causa de pessoas que não conseguiam manter o nariz fora de onde não pertencem.
Blrfl
20
Você quis dizer "antes do encapsulamento se tornar popular"? O encapsulamento era bastante popular antes das línguas OO se tornarem populares.
Frank Hileman
6
@FrankHileman Eu acho que é realmente o cerne da questão: OP quer saber se encapsulamento existia em linguagens procedurais, antes Simula / Smalltalk / C ++
dcorking
18
Lamento antecipadamente se isso parecer condescendente, não pretendo que seja. Você precisa aprender outras línguas. Linguagens de programação não são para máquinas rodarem, são para programadores pensarem . Eles necessariamente moldam a maneira como você pensa. Você simplesmente não teria essa pergunta se tivesse passado algum tempo significativo trabalhando com JavaScript / Python / Ocaml / Clojure, mesmo se você tivesse Java o dia todo em seu trabalho diário. Além de um projeto de código-fonte aberto em C ++ em que trabalho (na maioria das vezes em C), não uso uma linguagem com modificadores de acesso desde a faculdade e não os sinto falta.
Jared Smith

Respostas:

139

OOP não inventou o encapsulamento e não é sinônimo de encapsulamento. Muitas linguagens OOP não possuem modificadores de acesso no estilo C ++ / Java. Muitas linguagens não OOP têm várias técnicas disponíveis para oferecer encapsulamento.

Uma abordagem clássica para encapsulamento é o fechamento , conforme usado na programação funcional . Isso é significativamente mais antigo que o OOP, mas é de certa forma equivalente. Por exemplo, em JavaScript, podemos criar um objeto como este:

function Adder(x) {
  this.add = function add(y) {
    return x + y;
  }
}

var plus2 = new Adder(2);
plus2.add(7);  //=> 9

O plus2objeto acima não possui nenhum membro que permita acesso direto x- é totalmente encapsulado. O add()método é um fechamento sobre a xvariável.

A linguagem C suporta alguns tipos de encapsulamento através de seu mecanismo de arquivo de cabeçalho , particularmente a técnica de ponteiro opaco . Em C, é possível declarar um nome de estrutura sem definir seus membros. Nesse ponto, nenhuma variável do tipo dessa estrutura pode ser usada, mas podemos usar ponteiros para essa estrutura livremente (porque o tamanho de um ponteiro de estrutura é conhecido em tempo de compilação). Por exemplo, considere este arquivo de cabeçalho:

#ifndef ADDER_H
#define ADDER_H

typedef struct AdderImpl *Adder;

Adder Adder_new(int x);
void Adder_free(Adder self);
int Adder_add(Adder self, int y);

#endif

Agora podemos escrever código que usa essa interface do Adder, sem ter acesso aos seus campos, por exemplo:

Adder plus2 = Adder_new(2);
if (!plus2) abort();
printf("%d\n", Adder_add(plus2, 7));  /* => 9 */
Adder_free(plus2);

E aqui estariam os detalhes de implementação totalmente encapsulados:

#include "adder.h"

struct AdderImpl { int x; };

Adder Adder_new(int x) {
  Adder self = malloc(sizeof *self);
  if (!self) return NULL;
  self->x = x;
  return self;
}

void Adder_free(Adder self) {
  free(self);
}

int Adder_add(Adder self, int y) {
  return self->x + y;
}

Há também a classe de linguagens de programação modulares , que se concentra nas interfaces no nível do módulo. A família de idiomas ML incl. OCaml inclui uma abordagem interessante para módulos chamados functors . OOP ofuscou e modificou amplamente a programação modular, mas muitas vantagens pretendidas do OOP têm mais a ver com modularidade do que com orientação a objetos.

Há também a observação de que classes em linguagens OOP como C ++ ou Java geralmente não são usadas para objetos (no sentido de entidades que resolvem operações através de ligação tardia / envio dinâmico), mas apenas para tipos de dados abstratos (onde definimos uma interface pública que oculta detalhes de implementação interna). O artigo Sobre a compreensão da abstração de dados, revisitado (Cook, 2009) discute essa diferença com mais detalhes.

Mas sim, muitos idiomas não possuem mecanismo de encapsulamento. Nesses idiomas, os membros da estrutura são deixados públicos. No máximo, haveria uma convenção de nomenclatura que desencorajaria o uso. Por exemplo, acho que Pascal não tinha um mecanismo de encapsulamento útil.

amon
fonte
11
Veja o erro em Adder self = malloc(sizeof(Adder));? Há uma razão para escrever indicadores e sizeof(TYPE)geralmente é desaprovada.
Deduplicator
10
Você não pode simplesmente escrever sizeof(*Adder), porque *Addernão é um tipo, assim como *int *não é um tipo. A expressão T t = malloc(sizeof *t)é idiomática e correta. Veja minha edição.
wchargin
4
Pascal tinha variáveis ​​unitárias que não podiam ser vistas de fora dessa unidade. Efetivamente, as variáveis ​​unitárias foram equivalentes às private staticvariáveis ​​em Java. Da mesma forma que C, você poderia usar ponteiros opacos para passar dados no Pascal sem declarar o que era. O MacOS clássico usou muitos ponteiros opacos, pois partes públicas e privadas de um registro (estrutura de dados) podem ser transmitidas juntas. Lembro-me de que o Gerenciador de Janelas fez muito disso, pois partes do Registro de Janelas eram públicas, mas algumas informações internas também foram incluídas.
Michael Shopsin
6
Talvez um exemplo melhor que Pascal seja o Python, que suporta orientação a objetos, mas não encapsulamento, recorrendo a convenções de nomes como _private_membere output_property_, ou técnicas mais avançadas para criar objetos imutáveis.
Mephy
11
Existe uma tendência irritante na literatura OOD de apresentar todos os princípios de design como um princípio de design OO . A literatura OOD (não acadêmica) tende a pintar uma imagem de uma "idade das trevas", onde todo mundo estava fazendo tudo errado, e então os praticantes de OOP traziam a luz. Até onde eu sei, isso deriva principalmente da ignorância. Por exemplo, até onde eu sei, Bob Martin deu uma olhada séria na programação funcional apenas alguns anos atrás.
Derek Elkins
31

Primeiro, ser processual versus orientado a objetos não tem nada a ver com público versus privado. Muitas linguagens orientadas a objetos não têm noção de controle de acesso.

Em segundo lugar, em "C" - que a maioria das pessoas chamaria de processual, e não orientado a objetos, existem muitos truques que você pode usar para tornar efetivamente as coisas privadas. Muito comum é usar ponteiros opacos (por exemplo, void *). Ou - você pode encaminhar declarar um objeto e simplesmente não defini-lo em um arquivo de cabeçalho.

foo.h:

struct queue;
struct queue* makeQueue();
void add2Queue(struct queue* q, int value);
...

foo.c:

struct queue {
    int* head;
    int* head;
};
struct queue* makeQueue() { .... }
void add2Queue(struct queue* q, int value) { ... }

Olhe para o SDK do Windows! Ele usa HANDLE e UINT_PTR, e coisas assim são identificadores genéricos para a memória usada nas APIs - efetivamente tornando as implementações privadas.

Lewis Pringle
fonte
1
Minha amostra demonstrou uma abordagem melhor (C) - usando estruturas declaradas adiante. Para usar a abordagem void *, usaria typedefs: No arquivo .h, digite typedef void * queue fila e, em todos os lugares em que tivemos a fila struct, basta dizer fila; Em seguida, no arquivo .c, renomeie a fila struct para struct queueImpl, e qualquer argumento se torne fila (não é a fila struct *) e a primeira linha de código de cada função se tornará struct queueImpl * qi = (struct queueImpl *) q
Lewis Pringle
7
Hmm. Torna-o privado porque você não pode acessar (ler ou gravar) nenhum campo da 'fila' de qualquer lugar além de sua implementação (arquivo foo.c). O que mais você quis dizer com privado? BTW - isso é verdadeiro tanto para o typedef void * apporach eo (melhor) abordagem struct declarar frente
Lewis Pringle
5
Devo confessar que faz quase 40 anos desde que li o livro no smalltalk-80, mas não me lembro de nenhuma noção de membros de dados públicos ou privados. Eu acho que o CLOS também não tinha essa noção. O objeto Pascal não tinha essa noção. Lembro-me de Simula (provavelmente de onde Stroustrup teve a idéia), e a maioria das linguagens OO desde C ++ a possui. De qualquer forma - concordamos que encapsulamento e dados privados são boas idéias. Até o interlocutor original foi claro nesse ponto. Ele estava apenas perguntando - como os oldies encapsulavam em linguagens pré-C ++.
Lewis Pringle
5
@LewisPringle, não há menção de membros de dados públicos no Smalltalk-80 porque todas as "variáveis ​​de instância" (membros de dados) são privadas, a menos que você use reflexão. Os Smalltalkers da AFAIU escrevem um acessador para todas as variáveis ​​que desejam tornar públicas.
Dcorking 19/07/19
4
@LewisPringle por outro lado, todos os "métodos" Smalltalk (membros de função) são públicos (existem convenções desajeitados para marcá-los privada)
dcorking
13

"Tipos de dados opacos" era um conceito bem conhecido quando me formei em ciência da computação há 30 anos. Não abordamos o POO, pois não era de uso comum na época e a "programação funcional" era considerada mais correta.

O Modula-2 tinha suporte direto para eles, consulte https://www.modula2.org/reference/modules.php .

Já foi explicado por Lewis Pringle como a declaração avançada de uma estrutura pode ser usada em C. Ao contrário do Módulo-2, uma função de fábrica precisava ser fornecida para criar o objeto. ( Os métodos virtuais também eram fáceis de implementar em C , pois o primeiro membro de uma estrutura era um ponteiro para outra estrutura que continha ponteiros de função para os métodos.)

Muitas vezes, a convenção também era usada. Por exemplo, qualquer campo começando com "_" não deve ser acessado fora do arquivo que possuía os dados. Isso foi facilmente aplicado pela criação de ferramentas de verificação personalizadas.

Todos os projetos de grande escala em que trabalhei (antes de migrar para C ++ e C #) tinham um sistema instalado para impedir que dados "particulares" fossem acessados ​​pelo código errado. Foi apenas um pouco menos padronizado do que é agora.

Ian
fonte
9

Observe que existem muitos idiomas OO sem a capacidade interna de marcar membros como privados. Isso pode ser feito por convenção, sem a necessidade de o compilador impor a privacidade. Por exemplo, as pessoas costumam prefixar variáveis ​​privadas com um sublinhado.

Existem técnicas para dificultar o acesso a variáveis ​​"privadas", sendo a mais comum a linguagem PIMPL . Isso coloca suas variáveis ​​privadas em uma estrutura separada, com apenas um ponteiro alocado nos seus arquivos de cabeçalho público. Isso significa uma desreferência extra e uma conversão para obter quaisquer variáveis ​​privadas, algo como ((private_impl)(obj->private))->actual_value, o que é irritante, portanto, na prática, raramente é usado.

Karl Bielefeldt
fonte
4

As estruturas de dados não tinham "membros", apenas campos de dados (supondo que fosse um tipo de registro). A visibilidade normalmente era definida para todo o tipo. No entanto, isso pode não ser tão limitador quanto você pensa, porque as funções não faziam parte do registro.

Vamos voltar e ter um pouco de história aqui ...

O paradigma de programação dominante antes da OOP foi chamado de programação estruturada . O principal objetivo inicial disso era evitar o uso de instruções de salto não estruturadas ("goto"). Esse é um paradigma orientado ao fluxo de controle (enquanto o OOP é mais orientado a dados), mas ainda era uma extensão natural dele tentar manter os dados logicamente estruturados exatamente como o código.

Outra superação da programação estruturada foi a ocultação de informações , a idéia de que as implementações da estrutura do código (que provavelmente mudam com bastante frequência) devem ser mantidas separadas da interface (que, idealmente, não mudará tanto). Agora é dogma, mas antigamente, muitas pessoas consideravam melhor para todo desenvolvedor conhecer os detalhes de todo o sistema, então essa foi uma vez uma ideia controversa. A edição original de The Mythical Man Month de Brook, na verdade, argumentou contra a ocultação de informações.

As linguagens de programação posteriores projetadas explicitamente para serem boas linguagens de programação estruturada (por exemplo, Modula-2 e Ada) geralmente incluíam informações ocultas como um conceito fundamental, construídas em torno de algum tipo de conceito de uma facilidade coesa de funções (e quaisquer tipos, constantes e objetos que possam ser necessários). No Modula-2, estes foram chamados "Módulos", no Ada "Pacotes". Muitas linguagens modernas de POO chamam o mesmo conceito de "namespaces". Esses namespaces eram a base organizacional do desenvolvimento nessas linguagens e, para a maioria dos propósitos, podiam ser usados ​​de maneira semelhante às classes OOP (sem suporte real à herança, é claro).

Portanto, no Modula-2 e no Ada (83), você pode declarar qualquer rotina, tipo, constante ou objeto em um espaço para nome como privado ou público, mas se você tiver um tipo de registro, não haverá uma maneira (fácil) de declarar público alguns campos de registro e outros particulares. Todo o seu registro é público ou não.

TED
fonte
Passei bastante tempo trabalhando em Ada. Ocultar seletivamente (de parte de um tipo de dados) era algo que fazíamos o tempo todo; no pacote que contém, você definiria o próprio tipo como privado ou limitado; a interface do pacote exporia funções / procedimentos públicos para obter e / ou definir campos internos. É claro que essas rotinas precisariam usar um parâmetro do tipo privado. Eu não o fiz e agora não considero isso difícil.
David
Além disso, a maioria das linguagens OFAIK OO funciona da mesma maneira, ou seja, myWidget.getFoo () é realmente implementado como getFoo (myWidget). A object.method()invocação é apenas açúcar sintático. IMHO importante - consulte o Princípio de acesso / referência uniforme de Meyer - mas ainda assim apenas açúcar sintático.
David
@ David - Esse foi o argumento da comunidade Ada durante anos durante a era Ada 95. Eu acredito que eles finalmente desistiram e provaram seu próprio argumento, permitindo object.method()como uma forma alternativa method(object, ...) para pessoas que simplesmente não podiam dar o salto conceitual.
TED
0

Em C, você já pode passar ponteiros para tipos declarados, mas não definidos, como outros já disseram, restringindo o acesso a todos os campos.

Você também pode ter funções públicas e privadas, módulo a módulo. As funções declaradas estáticas no arquivo de origem não são visíveis para o exterior, mesmo se você tentar adivinhar o nome delas. Da mesma forma, você pode ter variáveis ​​globais estáticas no nível do arquivo, o que geralmente é uma prática ruim, mas permite o isolamento em módulos.

Provavelmente é importante enfatizar que a restrição de acesso como uma convenção bem padronizada, em vez de uma construção imposta pela linguagem, funciona muito bem (consulte Python). Além disso, restringir o acesso aos campos de objetos apenas protegerá o programador quando houver necessidade de alterar o valor dos dados dentro de um objeto após a criação. O que já é um cheiro de código. Indiscutivelmente, C e, em particular, a constpalavra-chave do C ++ para métodos e argumentos de função são uma ajuda muito maior para o programador do que o bastante pobre do Java final.

Kafein
fonte
O único recurso que C tinha especificamente para ocultar informações eram staticdados e operações globais (o que significava que não foram apresentados ao vinculador para uso em outras compilações). Você pode argumentar plausivelmente qualquer apoio C teve para boas práticas de design de software, além de que era praticamente um truque, e não parte do projeto original da parte traseira idioma em 1972.
TED
0

Se sua definição de Público for a capacidade de acessar a implementação e dados / propriedades por meio de seu próprio código a qualquer momento, a resposta é simplesmente: Sim . No entanto, foi abstraído por diversos meios - dependendo do idioma.

Espero que isso tenha respondido sucintamente à sua pergunta.

RobMac
fonte
-1

Aqui está um contra-exemplo muito simples: em Java, interfaces definem objetos, mas classes não. A classdefine um tipo de dados abstrato, não um objeto.

Portanto, sempre que você usa privateum classem Java, você tem um exemplo de uma estrutura de dados com membros privados que não são orientados a objetos.

Jörg W Mittag
fonte
7
É claro que essa resposta é tecnicamente correta, mas é totalmente incompreensível para quem ainda não sabe como são as ADTs e como são diferentes dos objetos.
amon
1
Eu aprendi algo com esta resposta.
littleO
3
As interfaces não "definem" objetos; eles especificam contratos para operações / comportamentos que os objetos podem executar ou executar. Assim como a herança é geralmente descrita por um relacionamento e a composição por um relacionamento, as interfaces são geralmente descritas por relacionamentos capazes .
code_dredd