Eu tenho programado em idiomas orientados a objetos por décadas. A primeira linguagem OO que usei foi C ++ e, em seguida, Smalltalk e, finalmente, .NET e Java.Eu estava entusiasmado para aproveitar os benefícios da herança, encapsulamento e polimorfismo. Os Três Pilares do Paradigma.Eu estava ansioso para ganhar a promessa de reutilização e aproveitar a sabedoria adquirida por aqueles que vieram antes de mim nesta paisagem nova e excitante.Eu não pude conter minha excitação com o pensamento de mapear meus objetos do mundo real em suas Classes e esperar que o mundo inteiro caísse perfeitamente no lugar.Eu não poderia estar mais errado.Herança, o primeiro pilar a cairÀ primeira vista, a herança parece ser o maior benefício do paradigma orientado a objetos. Todos os exemplos simplistas de hierarquias de forma que são exibidos como exemplos para os recém-doutrinados parecem fazer sentido lógico.E reutilizar é a palavra do dia. Não… faça isso o ano e talvez sempre.Eu engoli tudo e corri para o mundo com a minha nova descoberta.Problema da selva do macaco da bananaCom religião em meu coração e problemas para resolver, comecei a construir Hierarquias de Classe e escrever código. E tudo estava certo com o mundo.Nunca vou esquecer aquele dia em que eu estava pronto para lucrar com a promessa de Reutilização herdando de uma classe existente. Este foi o momento que eu estava esperando.Um novo projeto surgiu e eu pensei na classe que eu gostava tanto no meu último projeto.Sem problemas. Reutilize para o resgate. Tudo o que tenho que fazer é pegar essa classe do outro projeto e usá-la.Bem… na verdade… não apenas essa classe. Nós vamos precisar da classe dos pais. Mas… Mas é isso.Ugh ... Espere ... Parece que também vamos precisar dos pais dos pais ... E então ... Vamos precisar de TODOS os pais. Ok… ok… eu cuido disso. Sem problemas.E ótimo. Agora não vai compilar. Por quê?? Ah, entendo… Esse objeto contém esse outro objeto. Então eu vou precisar disso também. Sem problemas.Espere ... eu não preciso apenas desse objeto. Preciso que o pai do objeto e o pai de seus pais, e assim por diante, com todos os objetos contidos e TODOS os pais daquilo que eles contêm junto com os pais deles, pai, pai e mãe.UghHá uma ótima citação de Joe Armstrong, o criador de Erlang:O problema das linguagens orientadas a objetos é que eles têm todo esse ambiente implícito que carregam consigo. Você queria uma banana, mas o que você conseguiu foi um gorila segurando a banana e toda a floresta.Solução de selva de macaco de bananaEu posso domar este problema, não criando hierarquias que são muito profundas. Mas se Herança é a chave para Reutilização, então quaisquer limites que eu coloque nesse mecanismo certamente irão limitar os benefícios da Reutilização. Certo?Certo.Então, o que é um Programador Orientado a Objetos, que teve uma ajuda saudável do Kool-aid?Contenha e Delegue. Mais sobre isso depois.O problema do diamanteMais cedo ou mais tarde, o problema que se segue irá tornar-se feio e, dependendo da língua, cabeça insolúvel.A maioria das linguagens OO não suportam isso, embora isso pareça fazer sentido lógico. O que é tão difícil de suportar isso em idiomas OO?Bem, imagine o seguinte pseudocódigo:Classe PoweredDevice {}O Class Scanner herda do PoweredDevice {  function start () {  }}A impressora de classe é herdada do PoweredDevice {  function start () {  }}A copiadora de classes herda do scanner, impressora {}Observe que tanto a classe Scanner quanto a classe Printer implementam uma função chamada start.Então, qual função de início a classe Copier herda? O Scanner? A impressora? Não pode ser ambos.A solução de diamanteA solução é simples. Não faça isso.Sim está certo. A maioria dos idiomas OO não permite que você faça isso.Mas, mas… e se eu tiver que modelar isso? Eu quero minha reutilização!Então você deve conter e delegar.Classe PoweredDevice {}O Class Scanner herda do PoweredDevice {  function start () {  }}A impressora de classe é herdada do PoweredDevice {  function start () {  }}Copiadora de Classe {  Scanner scanner  Impressora de impressora  function start () {    printer.start ()  }}Observe aqui que a classe Copier agora contém uma instância de uma impressora e de um scanner. Ele delega a função de início para a implementação da classe Impressora. Ele poderia ser facilmente delegado ao Scanner.Este problema é mais uma falha no pilar da herança.O problema da classe base frágilEntão, estou tornando minhas hierarquias superficiais e evitando que elas sejam cíclicas. Não há diamantes para mim.E tudo estava certo com o mundo. Isso é até…Um dia, meu código funciona e no dia seguinte ele pára de funcionar. Aqui está o kicker. Eu não mudei meu código.Bem, talvez seja um erro… Mas espere… Alguma coisa mudou…Mas não estava no meu código. Acontece que a mudança foi na classe que eu herdei.Como poderia uma mudança na classe Base quebrar meu código?É assim…É assim…Imagine a seguinte classe Base (está escrita em Java, mas deve ser fácil de entender se você não conhece Java):import java.util.ArrayList; classe pública Array{  private ArrayList a = novo ArrayList ();   public void add (elemento do objeto)  {    a.add (elemento);  }   public void addAll (elementos do objeto [])  {    para (int i = 0; i

Adeus, Programação Orientada a Objetos

Eu tenho programado em idiomas orientados a objetos por décadas. A primeira linguagem OO que usei foi C ++ e, em seguida, Smalltalk e, finalmente, .NET e Java.

Eu estava entusiasmado para aproveitar os benefícios da herança, encapsulamento e polimorfismo. Os Três Pilares do Paradigma.

Eu estava ansioso para ganhar a promessa de reutilização e aproveitar a sabedoria adquirida por aqueles que vieram antes de mim nesta paisagem nova e excitante.

Eu não pude conter minha excitação com o pensamento de mapear meus objetos do mundo real em suas Classes e esperar que o mundo inteiro caísse perfeitamente no lugar.

Eu não poderia estar mais errado.

Herança, o primeiro pilar a cair

À primeira vista, a herança parece ser o maior benefício do paradigma orientado a objetos. Todos os exemplos simplistas de hierarquias de forma que são exibidos como exemplos para os recém-doutrinados parecem fazer sentido lógico.

E reutilizar é a palavra do dia. Não… faça isso o ano e talvez sempre.

Eu engoli tudo e corri para o mundo com a minha nova descoberta.

Problema da selva do macaco da banana
Com religião em meu coração e problemas para resolver, comecei a construir Hierarquias de Classe e escrever código. E tudo estava certo com o mundo.

Nunca vou esquecer aquele dia em que eu estava pronto para lucrar com a promessa de Reutilização herdando de uma classe existente. Este foi o momento que eu estava esperando.

Um novo projeto surgiu e eu pensei na classe que eu gostava tanto no meu último projeto.

Sem problemas. Reutilize para o resgate. Tudo o que tenho que fazer é pegar essa classe do outro projeto e usá-la.

Bem… na verdade… não apenas essa classe. Nós vamos precisar da classe dos pais. Mas… Mas é isso.

Ugh … Espere … Parece que também vamos precisar dos pais dos pais … E então … Vamos precisar de TODOS os pais. Ok… ok… eu cuido disso. Sem problemas.

E ótimo. Agora não vai compilar. Por quê?? Ah, entendo… Esse objeto contém esse outro objeto. Então eu vou precisar disso também. Sem problemas.

Espere … eu não preciso apenas desse objeto. Preciso que o pai do objeto e o pai de seus pais, e assim por diante, com todos os objetos contidos e TODOS os pais daquilo que eles contêm junto com os pais deles, pai, pai e mãe.

Ugh

Há uma ótima citação de Joe Armstrong, o criador de Erlang:

O problema das linguagens orientadas a objetos é que eles têm todo esse ambiente implícito que carregam consigo. Você queria uma banana, mas o que você conseguiu foi um gorila segurando a banana e toda a floresta.
Solução de selva de macaco de banana
Eu posso domar este problema, não criando hierarquias que são muito profundas. Mas se Herança é a chave para Reutilização, então quaisquer limites que eu coloque nesse mecanismo certamente irão limitar os benefícios da Reutilização. Certo?

Certo.

Então, o que é um Programador Orientado a Objetos, que teve uma ajuda saudável do Kool-aid?

Contenha e Delegue. Mais sobre isso depois.

O problema do diamante
Mais cedo ou mais tarde, o problema que se segue irá tornar-se feio e, dependendo da língua, cabeça insolúvel.

A maioria das linguagens OO não suportam isso, embora isso pareça fazer sentido lógico. O que é tão difícil de suportar isso em idiomas OO?

Bem, imagine o seguinte pseudocódigo:

Classe PoweredDevice {
}
O Class Scanner herda do PoweredDevice {
function start () {
}
}
A impressora de classe é herdada do PoweredDevice {
function start () {
}
}
A copiadora de classes herda do scanner, impressora {
}
Observe que tanto a classe Scanner quanto a classe Printer implementam uma função chamada start.

Então, qual função de início a classe Copier herda? O Scanner? A impressora? Não pode ser ambos.

A solução de diamante
A solução é simples. Não faça isso.

Sim está certo. A maioria dos idiomas OO não permite que você faça isso.

Mas, mas… e se eu tiver que modelar isso? Eu quero minha reutilização!

Então você deve conter e delegar.

Classe PoweredDevice {
}
O Class Scanner herda do PoweredDevice {
function start () {
}
}
A impressora de classe é herdada do PoweredDevice {
function start () {
}
}
Copiadora de Classe {
Scanner scanner
Impressora de impressora
function start () {
printer.start ()
}
}
Observe aqui que a classe Copier agora contém uma instância de uma impressora e de um scanner. Ele delega a função de início para a implementação da classe Impressora. Ele poderia ser facilmente delegado ao Scanner.

Este problema é mais uma falha no pilar da herança.

O problema da classe base frágil
Então, estou tornando minhas hierarquias superficiais e evitando que elas sejam cíclicas. Não há diamantes para mim.

E tudo estava certo com o mundo. Isso é até…

Um dia, meu código funciona e no dia seguinte ele pára de funcionar. Aqui está o kicker. Eu não mudei meu código.

Bem, talvez seja um erro… Mas espere… Alguma coisa mudou…

Mas não estava no meu código. Acontece que a mudança foi na classe que eu herdei.

Como poderia uma mudança na classe Base quebrar meu código?

É assim…

É assim…

Imagine a seguinte classe Base (está escrita em Java, mas deve ser fácil de entender se você não conhece Java):

import java.util.ArrayList;

classe pública Array
{
private ArrayList <Object> a = novo ArrayList <Objeto> ();

public void add (elemento do objeto)
{
a.add (elemento);
}

public void addAll (elementos do objeto [])
{
para (int i = 0; i <elements.length; ++ i)
a.add (elementos [i]); // esta linha vai ser alterada
}
}
IMPORTANTE: observe a linha de código comentada. Esta linha será mudada mais tarde, o que quebrará as coisas.

Esta classe tem 2 funções em sua interface, add () e addAll (). A função add () adicionará um único elemento e addAll () adicionará vários elementos chamando a função add.

E aqui está a classe Derived:

classe pública ArrayCount estende a matriz
{
private int count = 0;

@Sobrepor
public void add (elemento do objeto)
{
super.add (elemento);
++ count;
}

@Sobrepor
public void addAll (elementos do objeto [])
{
super.addAll (elementos);
count + = elements.length;
}
}
A classe ArrayCount é uma especialização da classe Array geral. A única diferença comportamental é que o ArrayCount mantém uma contagem do número de elementos.

Vamos ver essas duas classes em detalhes.

O Array add () adiciona um elemento a um ArrayList local.
O Array addAll () chama o ArrayList local adicionar para cada elemento.

O ArrayCount add () chama o add () de seu pai e, em seguida, incrementa a contagem.
O ArrayCount addAll () chama o addAll () de seu pai e, em seguida, incrementa a contagem pelo número de elementos.

E tudo funciona bem.

Agora para a mudança de ruptura. A linha de código comentada na classe Base é alterada para o seguinte:

  public void addAll (elementos do objeto [])
{
para (int i = 0; i <elements.length; ++ i)
add (elementos [i]); // esta linha foi alterada
}
No que diz respeito ao proprietário da classe Base, ela ainda funciona como anunciada. E todos os testes automatizados ainda passam.

Mas o proprietário não tem conhecimento da classe Derived. E o dono da classe Derived está em um despertar rude.

Agora, ArrayCount addAll () chama o addAll () de seu pai, que internamente chama o add () que foi OVERRIDEN pela classe Derived.

Isso faz com que a contagem seja incrementada toda vez que o add () da classe Derived for chamado e, em seguida, será incrementado novamente pelo número de elementos que foram adicionados ao addAll () da classe derivada.

É contado duas vezes.

Se isso acontecer, o autor da classe Derived deve saber como a classe Base foi implementada. E eles devem ser informados sobre todas as mudanças na classe Base, uma vez que isso poderia quebrar sua classe Derived de maneiras imprevisíveis.

Ugh! Esta enorme rachadura está sempre ameaçando a estabilidade do precioso pilar da herança.

A solução de classe base frágil
Mais uma vez Contenha e Delegue para o resgate.

Usando Contain e Delegate, vamos da programação do White Box para a programação do Black Box. Com a programação da White Box, temos que observar a implementação da classe base.

Com a programação do Black Box, podemos ser completamente ignorantes da implementação, já que não podemos injetar código na classe Base, sobrescrevendo uma de suas funções. Nós só temos que nos preocupar com a Interface.

Esta tendência é perturbadora…

A herança deveria ser uma grande vitória para o Reuse.

Os idiomas orientados a objetos não facilitam a utilização de Contain and Delegate. Eles foram projetados para facilitar a herança.

Se você é como eu, está começando a se perguntar sobre essa herança. Mas, mais importante, isso deve abalar sua confiança no poder da Classificação via Hierarquias.

O problema da hierarquia
Toda vez que eu começo em uma nova empresa, luto com o problema quando estou criando um local para colocar meus documentos da empresa, por exemplo o manual do empregado.

Eu crio uma pasta chamada Documentos e, em seguida, cria uma pasta chamada Empresa?

Ou crio uma pasta chamada Company e, em seguida, crie uma pasta chamada Documents nela?

Ambos funcionam. Mas o que é certo? Qual é melhor?

A idéia das Hierarquias Categóricas era que havia Classes Base (pais) que eram mais gerais e que Classes Derivadas (filhos) eram versões mais especializadas daquelas classes. E ainda mais especializado à medida que descemos a cadeia de herança. (Veja a Hierarquia da Forma acima)

Mas se um pai e uma criança puderem mudar arbitrariamente de lugar, então, claramente, algo está errado com esse modelo.

A solução de hierarquia
O que há de errado é …

Hierarquias categóricas não funcionam.

Então, quais são as boas hierarquias?

Contenção.

Se você olhar para o mundo real, verá hierarquias de Contenção (ou Propriedade Exclusiva) em todos os lugares.

O que você não encontrará é Hierarquias Categóricas. Deixe isso afundar por um momento. O Paradigma Orientado a Objetos foi baseado no mundo real, cheio de Objetos. Mas então ele usa um modelo quebrado, viz. Hierarquias Categóricas, onde não há analogia do mundo real.

Mas o mundo real está cheio de Hierarquias de Contenção. Um ótimo exemplo de uma Hierarquia de Contenção é suas meias. Eles estão em uma gaveta de meia que está contida em uma gaveta em sua cômoda que está contida em seu quarto que está contido em sua casa, etc.

Os diretórios no seu disco rígido são outro exemplo de uma Hierarquia de Contenção. Eles contém arquivos.

Então, como categorizamos então?

Bem, se você pensar nos Documentos da Empresa, praticamente não importa onde eu os coloquei. Eu posso colocá-los em uma pasta de documentos ou uma pasta chamada Stuff.

A maneira como eu categorizo ​​é com tags. Eu marquei o arquivo com as seguintes tags:

Documento
Empresa
Manual
Tags não têm ordem ou hierarquia. (Isso resolve o problema do diamante também)

As tags são análogas às interfaces, pois você pode ter vários tipos associados ao documento.

Mas com tantas rachaduras, parece que o pilar da herança caiu.

Adeus, herança.

Encapsulamento, o segundo pilar a cair

À primeira vista, o encapsulamento parece ser o segundo maior benefício da programação orientada a objetos.

As variáveis ​​de estado do objeto são protegidas do acesso externo, ou seja, elas são encapsuladas no objeto.

Não precisaremos mais nos preocupar com as variáveis ​​globais que estão sendo acessadas por quem sabe quem.

O encapsulamento é seguro para suas variáveis.

Esta coisa de Encapsulamento é INCRÍVEL !!

Viva o encapsulamento …

Isso é até…

O problema de referência
Por uma questão de eficiência, os objetos são passados ​​para as funções NÃO pelo seu valor, mas por referência.

O que isso significa é que as funções não passarão o objeto, mas passarão uma referência ou um ponteiro ao objeto.

Se um objeto é passado por referência a um construtor de objeto, o construtor pode colocar essa referência de objeto em uma variável particular que é protegida pelo encapsulamento.

Mas o objeto passado não é seguro!

Por que não? Como alguma outra parte do código tem um ponteiro para o objeto, viz. o código que chamou o construtor. Ele DEVE ter uma referência ao Objeto ou não poderia passá-lo para o Construtor?

A solução de referência
O construtor terá que clonar o passado no objeto. E não um clone superficial, mas um clone profundo, ou seja, cada objeto que está contido no objeto passado e todos os objetos nesses objetos e assim por diante.

Tanto por eficiência.

E aqui está o kicker. Nem todos os objetos podem ser clonados. Alguns possuem recursos do Sistema Operacional associados a eles, tornando a clonagem inútil na melhor das hipóteses ou na pior das hipóteses impossível.

E TODA a única linguagem OO mainstream tem esse problema.

Adeus, encapsulamento.

Polimorfismo, o terceiro pilar a cair

O polimorfismo era o enteado ruivo da Trinity Orientada a Objetos.

É uma espécie de Larry Fine do grupo.

Onde quer que fossem, ele estava lá, mas ele era apenas um personagem de apoio.

Não é que o polimorfismo não seja ótimo, é apenas que você não precisa de uma linguagem orientada a objetos para obter isso.

Interfaces lhe dará isso. E sem toda a bagagem da OO.

E com as Interfaces, não há limite para quantos comportamentos diferentes você pode misturar.

Portanto, sem muita demora, dizemos adeus ao polimorfismo OO e olá ao polimorfismo baseado em interface.

Promessas quebradas

Bem, OO prometeu muito nos primeiros dias. E essas promessas ainda estão sendo feitas para programadores ingênuos sentados em salas de aula, lendo blogs e fazendo cursos online.

Levei anos para perceber como a OO mentiu para mim. Eu também estava de olhos arregalados e inexperiente e confiante.

E eu me queimei.

Adeus, Programação Orientada a Objetos.

Então o que?
Olá, Programação Funcional. Tem sido muito bom trabalhar com você nos últimos anos.

Só para você saber, eu não estou tomando nenhuma das suas promessas pelo valor de face. Eu vou ter que ver para acreditar.

Uma vez queimado, duas vezes tímido e tudo.

Você entende.