Quais problemas de programação processual o OOP resolve na prática?

17

Eu estudei o livro "C ++ Desmistificado" . Agora comecei a ler "Programação Orientada a Objetos no Turbo C ++ primeira edição (1ª edição)", de Robert Lafore. Eu não tenho nenhum conhecimento de programação que esteja além desses livros. Este livro pode estar desatualizado porque tem 20 anos. Eu tenho a edição mais recente, estou usando a antiga porque gosto, principalmente apenas estudando os conceitos básicos de OOP usados ​​em C ++ na primeira edição do livro de Lafore.

O livro de Lafore enfatiza que "OOP" é útil apenas para programas maiores e complexos . Diz-se em todo livro de OOP (também no livro de Lafore) que o paradigma processual é propenso a erros, por exemplo, os dados globais tão facilmente vulneráveis ​​pelas funções. Diz-se que o programador pode cometer erros honestos em linguagens procedurais, por exemplo, criando uma função que corrompe acidentalmente os dados.

Honestamente falando, estou postando minha pergunta porque não estou entendendo a explicação dada neste livro: Programação Orientada a Objetos em C ++ (4ª Edição) Não estou entendendo essas declarações escritas no livro de Lafore:

A programação orientada a objetos foi desenvolvida porque foram descobertas limitações em abordagens anteriores de programação ... À medida que os programas se tornam cada vez maiores e mais complexos, até a abordagem de programação estruturada começa a mostrar sinais de tensão ... .... Analisando as razões para essas falhas revelam que há fragilidades no próprio paradigma processual. Não importa o quão bem a abordagem de programação estruturada seja implementada, os grandes programas se tornam excessivamente complexos ... ... Existem dois problemas relacionados. Primeiro, as funções têm acesso irrestrito aos dados globais. Segundo, funções e dados não relacionados, a base do paradigma processual, fornecem um modelo pobre do mundo real ...

Estudei o livro "C ++ dismistificado" de Jeff Kent, gosto muito deste livro, neste livro é explicada principalmente a programação procedural. Não entendo por que a programação procedural (estruturada) é fraca!

O livro de Lafore explica muito bem o conceito com alguns bons exemplos. Também apreendi uma intuição ao ler o livro de Lafore de que OOP é melhor que a programação procedural, mas estou curioso para saber como exatamente na prática a programação procedural é mais fraca que a OOP.

Eu quero me ver quais são os problemas práticos que alguém enfrentaria na programação processual, como o OOP tornará a programação mais fácil. Acho que receberei minha resposta apenas lendo o livro de Lafore de maneira contemplativa, mas quero ver com meus próprios olhos os problemas no código processual. Quero ver como o código de estilo OOP de um programa remove os erros anteriores que aconteceriam se o o mesmo programa deveria ser escrito usando paradigma processual.

Existem muitos recursos do OOP e eu entendo que não é possível alguém me explicar como todos esses recursos removem os erros anteriores que seriam gerados ao escrever o código no estilo processual.

Então aqui está a minha pergunta:

Quais limitações da programação processual o OOP aborda e como ele remove efetivamente essas limitações na prática?

Em particular, existem exemplos de programas difíceis de projetar usando o paradigma processual, mas que são facilmente projetados usando OOP?

PS: Cross publicado em: /programming//q/22510004/3429430

user31782
fonte
3
A distinção entre programação procedural e programação orientada a objetos é, em certa medida, uma questão de apresentação e ênfase. A maioria das linguagens que se anunciam como orientadas a objetos também é processual - os termos analisam diferentes aspectos da linguagem.
Gilles 'SO- stop be evil'
2
Eu reabri a pergunta agora; Vamos ver como isso acontece. Lembre-se de que qualquer tratamento que apresenta OOP é santo graal, resolve o problema de todos os problemas das linguagens de programação e está disparando bobagens. Há prós e contras. Você está pedindo os profissionais, e essa é uma pergunta justa; não espere mais.
Raphael
O pensamento de projetar uma GUI de desktop (moderna) sem OOP e algumas técnicas que cresceram nela (por exemplo, padrão de evento / ouvinte) me assusta. Não significa que não possa ser feito (certamente pode ), no entanto. Além disso, se você quiser ver falhas de PP no trabalho, consulte o PHP e compare a API com, por exemplo, Ruby.
Raphael
1
"Não importa o quão bem a abordagem de programação estruturada seja implementada, os grandes programas se tornam excessivamente complexos ..." isso também é verdade no OOP, mas basicamente gerencia melhor a complexidade se aplicado da maneira prescrita pelos desenvolvedores ... e em grande parte através de uma melhor / mais nítidas limites de escopo / restrições, ou seja, uma espécie de sistema de compartimentalização ... ou APIE: Abstracção, Polimorfismo, herança, encapsulamento
vzn

Respostas:

9

Em uma linguagem processual, você não pode necessariamente expressar restrições necessárias para provar que o chamador está usando um módulo de uma maneira suportada. Na ausência de uma restrição verificável do compilador, você deve escrever a documentação e esperar que ela seja seguida e usar testes de unidade para demonstrar os usos pretendidos.

Tipos de declaração é a restrição declarativa mais óbvia (por exemplo: "prove que x é um float"). Forçar mutações de dados a passar por uma função conhecida por ser projetada para esses dados é outra. A imposição de protocolo (método de solicitação de chamada) é outra restrição parcialmente suportada, ou seja: "construtor -> outros métodos * -> destruidor".

Também existem benefícios reais (e algumas desvantagens) quando o compilador conhece o padrão. A digitação estática com tipos polimórficos é um pouco problemática quando você emula o encapsulamento de dados de uma linguagem procedural. Por exemplo:

o tipo x1 é um subtipo de x, t1 é um subtipo de t

Esta é uma maneira de encapsular dados em uma linguagem processual para ter um tipo t com os métodos feg, e uma subclasse t1 que faça o mesmo:

t_f (t, x, y, z, ...), t_g (t, x, y, ...) t1_f (t1, x, y, z, ...)

Para usar esse código, é necessário verificar o tipo e ativar o tipo de t antes de decidir o tipo de f que você chamará. Você poderia contornar isso assim:

digite t {d: dados f: função g: função}

Para que você invoque tf (x, y, z), em que uma verificação e uma opção para encontrar o método agora é substituída por apenas cada instância armazenar explicitamente os ponteiros do método. Agora, se você tiver um grande número de funções por tipo, essa é uma representação inútil. Você poderia usar outra estratégia, como apontar para uma variável m que contém todas as funções-membro. Se esse recurso fizer parte do idioma, você poderá deixar o compilador descobrir como lidar com a representação eficiente desse padrão.

Mas o encapsulamento de dados é o reconhecimento de que o estado mutável é ruim. A solução orientada a objetos é ocultá-la atrás dos métodos. Idealmente, todos os métodos em um objeto teriam uma ordem de chamada bem definida (ou seja: construtor -> aberto -> [ler | escrever] -> fechar -> destruir); que às vezes é chamado de 'protocolo' (pesquisa: "Microsoft Singularity"). Mas, além da construção e destruição, esses requisitos geralmente não fazem parte do sistema de tipos - ou são bem documentados. Nesse sentido, objetos são instâncias simultâneas de máquinas de estado que são transferidas por chamadas de método; de modo que você possa ter várias instâncias e usá-las de maneira arbitrariamente intercalada.

Mas, ao reconhecer que o estado compartilhado mutável é ruim, pode-se observar que a orientação a objetos pode criar um problema de simultaneidade porque a estrutura de dados do objeto é um estado mutável ao qual muitos objetos fazem referência. A maioria das linguagens orientadas a objetos é executada no encadeamento do chamador, o que significa que existem condições de corrida nas invocações de métodos; muito menos em seqüências não atômicas de chamadas de função. Como alternativa, todo objeto pode receber mensagens assíncronas em uma fila e atendê-las todas no encadeamento do objeto (com seus métodos particulares) e responder ao chamador enviando mensagens.

Compare as chamadas de método Java em um contexto multithread com os processos Erlang que enviam mensagens (que referenciam apenas valores imutáveis) entre si.

A orientação irrestrita a objetos em combinação com o paralelismo é um problema devido ao bloqueio. Existem técnicas que vão da Memória Transacional de Software (ou seja: transações ACID em objetos de memória semelhantes aos bancos de dados) ao uso de uma abordagem de "compartilhamento de memória através da comunicação (dados imutáveis)" (híbrido de programação funcional).

Na minha opinião, a literatura de Orientação a Objetos concentra-se muito na herança e não o suficiente no protocolo (ordenação de chamada de método verificável, pré-condições, pós-condições, etc.). A entrada que um objeto consome geralmente deve ter uma gramática bem definida, expressável como um tipo.

Roubar
fonte
Você está dizendo que, nas linguagens OO, o compilador pode verificar se os métodos são usados ​​na ordem prescrita ou outras restrições ao uso de módulos? Por que "o reconhecimento de encapsulamento de dados [...] indica que o estado mutável é ruim"? Quando você fala em polimorfismo, está assumindo que está usando uma linguagem OO?
31514 babou
No OO, a capacidade mais importante é poder ocultar a estrutura de dados (ex .: exigir que as referências sejam assim.x) para provar que todos os acessos passam por métodos. Em uma linguagem OO de tipo estatístico, você está declarando ainda mais restrições (com base nos tipos). A observação sobre a ordenação de métodos está apenas dizendo que OO força o construtor a ser chamado primeiro e o destruidor por último; que é o primeiro passo na proibição estrutural de ordens de compra ruins. As provas fáceis são um objetivo importante no design da linguagem.
Rob
8

A programação processual / funcional não é de modo algum mais fraca que a OOP , mesmo sem entrar em argumentos de Turing (minha linguagem tem poder de Turing e pode fazer qualquer outra coisa que faça), o que não significa muito. Na verdade, as técnicas orientadas a objetos foram primeiramente experimentadas em linguagens que não as incorporavam. Nesse sentido, a programação OO é apenas um estilo específico de programação procedural . Mas ajuda a impor disciplinas específicas, como modularidade, abstração e ocultação de informações, essenciais para a compreensão e manutenção do programa.

Alguns paradigmas de programação evoluem da visão teórica da computação. Uma linguagem como Lisp evoluiu do lambda-calculus e da idéia de meta-circularidade das línguas (semelhante à reflexividade na linguagem natural). As cláusulas de Horn criaram o Prolog e a programação de restrições. A família Algol também deve ao cálculo lambda, mas sem reflexividade embutida.

O Lisp é um exemplo interessante, pois foi testado por muita inovação em linguagem de programação, que é rastreável à sua dupla herança genética.

No entanto, os idiomas evoluem, geralmente sob novos nomes. Um fator importante da evolução é a prática de programação. Os usuários identificam práticas de programação que melhoram as propriedades dos programas, como legibilidade, capacidade de manutenção, probabilidade de correção. Em seguida, eles tentam adicionar aos idiomas recursos ou restrições que darão suporte e às vezes reforçam essas práticas, a fim de melhorar a qualidade dos programas.

O que isso significa é que essas práticas já são possíveis na linguagem de programação mais antiga, mas é preciso compreensão e disciplina para usá-las. Incorporá-los em novas linguagens como conceitos primários com sintaxe específica facilita o uso e a compreensão dessas práticas, principalmente para usuários menos sofisticados (ou seja, a grande maioria). Isso também facilita a vida dos usuários sofisticados.

De alguma forma, é para o design da linguagem o que é um subprograma / função / procedimento para um programa. Depois que o conceito útil é identificado, ele recebe um nome (possivelmente) e uma sintaxe, para que possa ser usado facilmente em todos os programas desenvolvidos com esse idioma. E, quando for bem-sucedido, também será incorporado em idiomas futuros.

Exemplo: recriando a orientação do objeto

Agora tento ilustrar isso em um exemplo (que certamente poderia ser mais polido, com o tempo). O objetivo do exemplo não é mostrar que um programa orientado a objetos pode ser escrito no estilo de programação procedural, possivelmente à custa da lisibilidade e manutenção. Tentarei mostrar que algumas linguagens sem instalações OO podem realmente usar funções de ordem superior e estrutura de dados para criar meios de imitar efetivamente a Orientação a Objetos , a fim de se beneficiar de suas qualidades em relação à organização do programa, incluindo modularidade, abstração e ocultação de informações. .

Como eu disse, o Lisp foi o teste de muita evolução da linguagem, incluindo o paradigma OO (embora o que pudesse ser considerado a primeira linguagem OO fosse o Simula 67, na família Algol). O Lisp é muito simples, e o código para seu intérprete básico é menor que uma página. Mas você pode fazer programação OO no Lisp. Tudo que você precisa é de funções de ordem superior.

Não usarei a sintaxe esotérica do Lisp, mas sim o pseudo-código, para simplificar a apresentação. E considerarei um problema essencial simples: ocultação de informações e modularidade . Definir uma classe de objetos e impedir que o usuário acesse (a maioria) a implementação.

Suponha que eu queira criar uma classe chamada vetor, representando vetores bidimensionais, com métodos que incluem: adição de vetor, tamanho de vetor e paralelismo.

function vectorrec () {  
  function createrec(x,y) { return [x,y] }  
  function xcoordrec(v) { return v[0] }  
  function ycoordrec(v) { return v[1] }  
  function plusrec (u,v) { return [u[0]+v[0], u[1]+v[1]] }  
  function sizerec(v) { return sqrt(v[0]*v[0]+v[1]*v[1]) }  
  function parallelrec(u,v) { return u[0]*v[1]==u[1]*v[0]] }  
  return [createrec, xcoordrec, ycoordrec, plusrec, sizerec, parallelrec]  
  }  

Então eu posso atribuir o vetor criado aos nomes das funções reais a serem usadas.

[vector, xcoord, ycoord, vplus, vsize, vparallel] = vectorclass ()

Por que ser tão complicado? Porque eu posso definir na função construções intermediárias vectorrec que não quero que sejam visíveis para o resto do programa, de modo a preservar a modularidade.

Podemos fazer outra coleção em coordenadas polares

function vectorpol () {  
  ...  
  function pluspol (u,v) { ... }  
  function sizepol (v) { return v[0] }  
  ...  
  return [createpol, xcoordpol, ycoordpol, pluspol, sizepol, parallelpol]  
  }  

Mas eu posso querer usar indiferentemente as duas implementações. Uma maneira de fazer isso é adicionar um componente de tipo a todos os valores e definir todas as funções acima no mesmo ambiente: Então eu posso definir cada uma das funções retornadas para que primeiro teste o tipo de coordenadas e aplique a função específica por isso.

function vector () {  
    ...  
    function plusrec (u,v) { ... }  
    ...  
    function pluspol (u,v) { ... }  
    ...  
    function plus (u,v) { if u[2]='rec' and v[2]='rec'  
                            then return plusrec (u,v) ... }  

    return [ ..., plus, ...]  
    }

O que eu ganhei: as funções específicas permanecem invisíveis (por causa do escopo dos identificadores locais) e o restante do programa pode usar apenas os mais abstratos retornados pela chamada para a classe de vetor.

Uma objeção é que eu poderia definir diretamente cada uma das funções abstratas no programa e deixar dentro da definição das funções dependentes do tipo coordenada. Então estaria escondido também. Isso é verdade, mas o código para cada tipo de coordenada seria cortado em pequenos pedaços espalhados pelo programa, o que é menos redutível e sustentável.

Na verdade, eu nem preciso dar um nome a eles, e eu poderia manter os valores funcionais como anônimos em uma estrutura de dados indexada pelo tipo e uma string que representa o nome da função. Essa estrutura sendo local para o vetor de função seria invisível para o restante do programa.

Para simplificar o uso, em vez de retornar uma lista de funções, posso retornar uma única função chamada apply, tendo como argumento um valor explícito do tipo e uma string, e aplicar a função com o tipo e o nome adequados. Parece muito com chamar um método para uma classe OO.

Vou parar por aqui, nesta reconstrução de uma instalação orientada a objetos.

O que tentei fazer é mostrar que não é muito difícil criar orientação para objetos utilizáveis ​​em uma linguagem suficientemente poderosa, incluindo herança e outros recursos. A metacircularidade do intérprete pode ajudar, mas principalmente em nível sintático, que ainda está longe de ser insignificante.

Os primeiros usuários de orientação a objetos experimentaram os conceitos dessa maneira. E isso geralmente acontece com muitas melhorias nas linguagens de programação. Obviamente, a análise teórica também tem um papel e ajudou a entender ou refinar esses conceitos.

Mas a ideia de que idiomas que não possuem recursos de OO estão fadados ao fracasso em alguns projetos é simplesmente injustificada. Se necessário, eles podem imitar a implementação desses recursos com bastante eficiência. Muitas linguagens têm o poder sintático e semântico de orientar objetos de maneira bastante eficaz, mesmo quando não está embutido. E isso é mais do que um argumento de Turing.

OOP não trata das limitações de outras linguagens, mas suporta ou aplica metodologias de programação que ajudam a escrever programas melhores, ajudando usuários menos experientes a seguir boas práticas que programadores mais avançados estão usando e desenvolvendo sem esse suporte.

Acredito que um bom livro para entender tudo isso possa ser Abelson & Sussman: estrutura e interpretação de programas de computador .

babou
fonte
8

Um pouco de história está em ordem, eu acho.

A era entre meados da década de 1960 e meados da década de 1970 é hoje conhecida como "crise de software". Não posso dizer melhor do que Dijkstra em sua palestra sobre o prêmio Turing de 1972:

A principal causa da crise do software é que as máquinas se tornaram várias ordens de magnitude mais poderosas! Para ser franco: enquanto não houvesse máquinas, a programação não era problema; quando tínhamos alguns computadores fracos, a programação se tornou um problema moderado, e agora temos computadores gigantes, a programação se tornou um problema igualmente gigantesco.

Essa foi a época dos primeiros computadores de 32 bits, dos primeiros multiprocessadores verdadeiros e dos primeiros computadores incorporados, e ficou claro para os pesquisadores que esses seriam importantes para a programação no futuro. Era uma época na história em que a demanda do cliente ultrapassava a capacidade do programador pela primeira vez.

Sem surpresa, foi um período notavelmente fértil na pesquisa de programação. Antes de meados da década de 1960, tínhamos LISP e AP / L, mas as "principais" linguagens eram fundamentalmente processuais: FORTRAN, ALGOL, COBOL, PL / I e assim por diante. De meados da década de 1960 a meados da década de 1970, obtivemos Logo, Pascal, C, Forth, Smalltalk, Prolog, ML e Modula, e isso não inclui DSLs como SQL e seus predecessores.

Foi também uma época na história em que muitas das principais técnicas para implementar linguagens de programação estavam sendo desenvolvidas. Nesse período, obtivemos análise de LR, análise de fluxo de dados, eliminação de subexpressão comum e o primeiro reconhecimento de que certos problemas do compilador (por exemplo, alocação de registro) eram difíceis para o NP e tentamos resolvê-los como tal.

Este é o contexto em que o POO surgiu. Portanto, no início da década de 1970, responda à sua pergunta sobre quais problemas o OOP resolve na prática, a primeira resposta é que parecia resolver muitos dos problemas (contemporâneos e antecipados) que os programadores enfrentavam naquele período da história. No entanto, este não é o momento em que o OO se tornou popular. Chegaremos a isso em breve.

Quando Alan Kay cunhou o termo "orientação a objetos", a imagem que ele tinha em mente era que os sistemas de software seriam estruturados como sistema biológico. Você teria algo como células individuais ("objetos") que interagem entre si enviando algo análogo a sinais químicos ("mensagens"). Você não podia (ou pelo menos não queria) espiar dentro de uma célula; você só interagia com ele pelos caminhos de sinalização. Além disso, você pode ter mais de um de cada tipo de célula, se necessário.

Você pode ver que existem alguns temas importantes aqui: o conceito de um protocolo de sinalização bem definido (na terminologia moderna, uma interface), o conceito de ocultar a implementação de fora (na terminologia moderna, a privacidade) e o conceito de tendo várias "coisas" do mesmo tipo penduradas ao mesmo tempo (na terminologia moderna, instanciação).

Uma coisa que você pode notar está faltando, e é a herança, e há uma razão para isso.

A programação orientada a objetos é uma noção abstrata, e a noção abstrata pode ser implementada de diferentes maneiras em diferentes linguagens de programação. O conceito abstrato de "método", por exemplo, poderia ser implementado em C usando ponteiros de função, e em C ++ usando funções-membro e em Smalltalk usando métodos (o que não deveria ser surpreendente, uma vez que o Smalltalk implementa o conceito abstrato praticamente diretamente). É isso que as pessoas querem dizer quando apontam (com toda a razão) que você pode "fazer" POO (quase) em qualquer idioma.

A herança, por outro lado, é um recurso concreto da linguagem de programação. A herança pode ser útil para implementar sistemas OOP. Ou pelo menos, esse foi o caso até o início dos anos 90.

O período de meados da década de 80 a meados da década de 90 também foi um período histórico em que as coisas estavam mudando. Durante esse período, tivemos o surgimento de um computador barato e onipresente de 32 bits, para que empresas e muitas residências pudessem se dar ao luxo de colocar um computador quase tão poderoso quanto os mainframes mais modernos do dia em todas as mesas. Foi também o auge de Essa também foi a era da ascensão da GUI moderna e do sistema operacional em rede.

Foi nesse contexto que surgiu a Análise e o Design Orientados a Objetos.

A influência do OOAD, o trabalho dos "três Amigos" (Booch, Rumbar e Jacobson) e outros (por exemplo, o método Shlaer-Mellor, design orientado a responsabilidades, etc.) não pode ser subestimado. É a razão pela qual a maioria das novas linguagens que foram desenvolvidas desde o início dos anos 90 (pelo menos, a maioria das que você já ouviu falar) tem objetos no estilo Simula.

Portanto, a resposta da década de 90 à sua pergunta é que ela suporta a melhor solução (na época) para análise orientada a domínio e metodologia de design.

Desde então, porque tínhamos um martelo, aplicamos OOP a praticamente todos os problemas que surgiram desde então. O OOAD e o modelo de objeto usado encorajaram e permitiram o desenvolvimento ágil e orientado a testes, cluster e outros sistemas distribuídos, etc.

As GUIs modernas e quaisquer sistemas operacionais que foram projetados nos últimos 20 anos tendem a fornecer seus serviços como objetos; portanto, qualquer nova linguagem de programação prática precisa, no mínimo, de uma maneira de se conectar aos sistemas que usamos hoje.

Portanto, a resposta moderna é: resolve o problema da interface com o mundo moderno. O mundo moderno é construído no OOP pela mesma razão que o mundo de 1880 foi construído no vapor: nós o compreendemos, podemos controlá-lo e ele faz o trabalho bem o suficiente.

Isso não quer dizer que a pesquisa pare aqui, é claro, mas indica fortemente que qualquer nova tecnologia precisará de OO como um caso limitante. Você não precisa ser OO, mas não pode ser fundamentalmente incompatível com ele.

Pseudônimo
fonte
Um aspecto que eu não queria colocar no ensaio principal é que as GUIs e OOP do WIMP parecem ser um ajuste extremamente natural. Muitas coisas ruins podem ser ditas sobre hierarquias profundas de herança, mas essa é uma situação (provavelmente a ÚNICA) em que parece fazer algum tipo de sentido.
Pseudônimo 28/03
1
OOP apareceu primeiro no Simula-67 (simulação), na organização interna dos sistemas operacionais (a idéia de "classe de dispositivo" no Unix é essencialmente uma classe da qual os drivers herdam). Parnas "Sobre os critérios a serem usados ​​na decomposição de sistemas em módulos" , MCCA 15:12 (1972), pp. 1052-1058, de Wirth Modula linguagem dos anos setenta, 'tipos de dados abstratos' são todos os precursores de uma forma ou o de outros.
vonbrand 10/09/2015
Tudo isso é verdade, mas sustento que a OOP não era vista como uma "solução para um problema de programação processual" até meados dos anos 70. Definir "OOP" é notoriamente difícil. O uso original do termo por Alan Kay não concorda com o modelo de Simula e, infelizmente, o mundo padronizou o modelo de Simula. Alguns modelos de objetos têm uma interpretação do tipo Curry-Howard, mas a de Simula não. Stepanov provavelmente estava correto quando observou que a herança é doentia.
Pseudônimo
6

Nenhuma, realmente. OOP não resolve realmente um problema, estritamente falando; não há nada que você possa fazer com um sistema orientado a objetos que você não poderia fazer com um sistema não orientado a objetos; de fato, não há nada que você possa fazer com algo que não possa ser feito com uma máquina de Turing. Eventualmente, tudo se transforma em código de máquina, e o ASM certamente não é orientado a objetos.

O que o paradigma OOP faz por você é que facilita a organização de variáveis ​​e funções e permite movê-las juntas mais facilmente.

Digamos que eu queira escrever um jogo de cartas em Python. Como eu representaria os cartões?

Se eu não soubesse sobre OOP, poderia fazê-lo desta maneira:

cards=["1S","2S","3S","4S","5S","6S","7S","8S","9S","10S","JS","QS","KS","1H","2H",...,"10C","JC","QC","KC"]

Eu provavelmente escreveria algum código para gerar esses cartões em vez de apenas escrevê-los à mão, mas você entendeu. "1S" representa o 1 de espadas, "JD" representa o valete de ouros e assim por diante. Eu também precisaria de um código para o Coringa, mas vamos fingir que não há Coringa por enquanto.

Agora, quando quero embaralhar o baralho, preciso apenas "embaralhar" a lista. Então, para tirar uma carta do topo do baralho, tiro a entrada da lista, dando-me a corda. Simples.

Agora, se eu quiser descobrir com qual cartão estou trabalhando com a finalidade de exibi-lo para o jogador, precisaria de uma função como esta:

def card_code_to_name(code):
    suit=code[1]

    if suit=="S":
        suit="Spades"
    elif suit=="H"
        suit="Hearts"
    elif suit=="D"
        suit="Diamonds"
    elif suit=="C"
        suit="Clubs"

    value=code[0]

    if value=="J":
        value="Jack"
    elif value="Q":
        value="Queen"
    elif value="K"
        value="King"

    return value+" of "+suit

Um pouco grande, longo e ineficiente, mas funciona (e é muito antitônico, mas isso não vem ao caso aqui).

Agora, e se eu quiser que os cartões possam se mover pela tela? Eu tenho que guardar a posição deles de alguma forma. Eu poderia adicioná-lo ao final do código do cartão, mas isso pode ser um pouco complicado. Em vez disso, vamos fazer outra lista de onde cada cartão está:

cardpositions=( (1,1), (2,1), (3,1) ...)

Depois, escrevo meu código para que o índice da posição de cada cartão na lista seja o mesmo do índice do cartão no baralho.

Ou, pelo menos, deveria ser. A menos que eu cometa um erro. O que eu poderia muito bem, porque meu código terá que ser bastante complexo para lidar com essa configuração. Quando quero embaralhar as cartas, preciso embaralhar as posições na mesma ordem. O que acontece se eu tirar uma carta completamente do baralho? Vou ter que tomar sua posição também e colocá-la em outro lugar.

E se eu quiser armazenar ainda mais informações sobre os cartões? E se eu quiser armazenar se cada cartão é invertido? E se eu quiser algum tipo de mecanismo de física e precisar conhecer a velocidade das cartas também? Vou precisar de outra lista inteiramente para armazenar os gráficos de cada placa! E para todos esses pontos de dados, precisarei de um código separado para mantê-los todos organizados corretamente, para que cada cartão seja mapeado de alguma forma para todos os dados!

Agora vamos tentar isso da maneira OOP.

Em vez de uma lista de códigos, vamos definir uma classe Card e criar uma lista de objetos Card.

class Card:

    def __init__(self,value,suit,pos,sprite,flipped=False):
        self.value=value
        self.suit=suit
        self.pos=pos
        self.sprite=sprite
        self.flipped=flipped

    def __str__(self):
        return self.value+" of "+self.suit

    def flip(self):
        if self.flipped:
            self.flipped=False
            self.sprite=load_card_sprite(value, suit)
        else:
            self.flipped=True
            self.sprite=load_card_back_sprite()

deck=[]
for suit in ("Spades","Hearts","Diamonds","Clubs"):
    for value in ("1","2","3","4","5","6","7","8","9","10","Jack","Queen","King"):
        sprite=load_card_sprite(value, suit)
        thecard=Card(value,suit,(0,0),sprite)
        deck.append(thecard)

Agora, de repente, tudo é muito mais simples. Se eu quiser mover a carta, não preciso descobrir onde ela está no baralho, então use-a para obter sua posição fora do conjunto de posições. Eu só tenho que dizerthecard.pos=newpos . Quando tiro o cartão da lista do baralho principal, não preciso fazer uma nova lista para armazenar todos os outros dados; quando o objeto do cartão se move, todas as suas propriedades se movem com ele. E se eu quero um cartão que se comporte de maneira diferente quando é invertido, não preciso modificar a função de inversão no meu código principal para que ele detecte esses cartões e faça esse comportamento diferente; Eu só tenho que subclass Card e modificar a função flip () na subclasse.

Mas nada que eu fiz lá não poderia ter sido feito sem OO. É que, com uma linguagem orientada a objetos, a linguagem está fazendo muito trabalho para manter as coisas unidas por você, o que significa que você tem muito menos chance de cometer erros, e seu código é mais curto e fácil de ler e escrever.

Ou, para resumir ainda mais, o OO permite que você escreva programas aparentemente mais simples que executam o mesmo trabalho que os programas mais complexos, ocultando muitas das complexidades comuns de manipulação de dados por trás de um véu de abstração.

Schilcote
fonte
1
Se a única coisa que você tira do OOP é o "gerenciamento de memória", acho que você não o entendeu muito bem. Existe toda uma filosofia de design e uma generosa garrafa de "corrigir por design" lá! Além disso, o gerenciamento de memória certamente não é inerente à orientação a objetos (C ++?), Mesmo que a necessidade seja mais acentuada.
Raphael
Claro, mas essa é a versão de uma frase. E também usei o termo de uma maneira não padronizada. Talvez seja melhor dizer "manipulação de informações" do que "gerenciamento de memória".
30514 Schilcote
Existe alguma linguagem que não seja OOP que permita que uma função leve um ponteiro para alguma coisa, bem como um ponteiro para uma função cujo primeiro parâmetro seja um ponteiro para o mesmo tipo de coisa, e faça com que o compilador valide se a função é apropriada para o ponteiro passado?
Supercat
3

Tendo escrito C incorporado por alguns anos gerenciando coisas como dispositivos, portas seriais e pacotes de comunicação entre as portas seriais, portas de rede e servidores; Eu me encontrei, um engenheiro elétrico treinado, com experiência limitada em programação processual, inventando minhas próprias abstrações do hardware que acabou se manifestando no que mais tarde percebi serem o que as pessoas comuns chamam de 'Programação Orientada a Objetos'.

Quando mudei para o lado do servidor, fui exposto a uma fábrica que configurava a representação do objeto de cada dispositivo na memória na instanciação. Eu não entendi as palavras ou o que estava acontecendo a princípio - eu apenas sabia que fui ao arquivo chamado assim e tal e escrevi o código. Mais tarde, me vi novamente reconhecendo finalmente o valor do POO.

Pessoalmente, acho que essa é a única maneira de ensinar orientação a objetos. Eu tive uma aula de Introdução à OOP (Java) no meu primeiro ano e foi completamente demais. As descrições de OOP baseadas na classificação de gatinho-> gato-> mamífero-> vivo-> coisa ou folha-> galho-> árvore-> jardim são, na minha humilde opinião, metodologias absolutamente ridículas, porque ninguém nunca tentará resolvê-las. problemas, se você pudesse chamá-los de problemas ...

Eu acho que é mais fácil responder à sua pergunta se você a analisar em termos menos absolutos - não 'o que resolve', mas mais da perspectiva de 'aqui está um problema e aqui está como é mais fácil'. No meu caso específico de portas seriais, tínhamos vários #ifdefs em tempo de compilação que adicionavam e removiam código que abria e fechava estaticamente portas seriais. As funções de porta aberta eram chamadas em todo o lugar e podiam estar localizadas em qualquer lugar nas 100 mil linhas de código do sistema operacional que tínhamos, e o IDE não esmaecia o que não estava definido - era necessário rastrear isso manualmente, e carregue na sua cabeça. Inevitavelmente, você poderia ter várias tarefas tentando abrir uma determinada porta serial esperando o dispositivo na outra extremidade e, em seguida, nenhum código que você acabou de escrever funciona, e você não consegue entender o porquê.

A abstração era, embora ainda em C, uma 'classe' de porta serial (bem, apenas um tipo de dados de estrutura) em que tínhamos uma matriz de-- uma para cada porta serial - e em vez de ter [o equivalente de DMA em portas seriais] "OpenSerialPortA" "SetBaudRate" etc. funções chamadas diretamente no hardware a partir da tarefa, chamamos uma função auxiliar para a qual você passou todos os parâmetros de comunicação (baud, paridade etc.) para, que primeiro checavam a matriz da estrutura para verificar se isso a porta já havia sido aberta - se sim, por qual tarefa, que seria informada como uma depuração printf, para que você pudesse pular imediatamente para a seção de código que precisava desabilitar-- e, caso contrário, prosseguiu para definir todos os parâmetros através de suas funções de montagem HAL e, finalmente, abriu a porta.

Claro, também há perigos para o POO. Quando finalmente limpei a base de código e tornei tudo arrumado e limpo - escrever novos drivers para essa linha de produtos estava finalmente em uma ciência calculável e previsível, meu gerente fez a EOL do produto especificamente porque era um projeto a menos que ele precisaria para gerenciar, e ele era um gerente intermediário removível limítrofe. O que tirou muito de mim / achei muito desanimador, então deixei meu emprego. RI MUITO.

paIncrease
fonte
1
Oi! Isso parece mais uma história pessoal do que uma resposta para a pergunta. Do seu exemplo, vejo que você reescreveu algum código horrível em um estilo orientado a objetos, o que o tornou melhor. Mas não está claro se a melhoria teve muito a ver com orientação a objetos ou se era apenas porque você era um programador mais experiente até então. Por exemplo, boa parte do seu problema parece ter sido do código que está sendo espalhado, quer ou não, pelo local. Isso poderia ter sido resolvido escrevendo uma biblioteca processual, sem objetos.
precisa saber é o seguinte
2
@DavidRicherby, tínhamos a biblioteca de procedimentos, mas foi o que preterimos, não se tratava apenas de códigos em todo o lado. O ponto foi que fizemos isso ao contrário. Ninguém estava tentando fazer nada, apenas aconteceu naturalmente.
paIncrease 9/09/15
@DavidRicherby, você pode dar exemplos de implementações de bibliotecas de procedimentos para garantir que falemos da mesma coisa?
paIncrease 9/09/15
2
Obrigado pela sua resposta e +1. Há muito tempo, outro programador experiente compartilhou como o OOP tornou seu projeto mais confiável. Forums.devshed.com/programming-42/… Acho que o OOP foi projetado de maneira muito inteligente por alguns profissionais que podem ter enfrentado alguns problemas na abordagem processual.
user31782
2

existem muitas alegações e intenções sobre o que / onde a programação OOP tem uma vantagem sobre a programação procedural, inclusive por seus inventores e usuários. mas apenas porque uma tecnologia foi projetada para um determinado propósito por seus projetistas, não garante que terá sucesso nesses objetivos. esse é um entendimento essencial no campo da engenharia de software, datado do famoso ensaio de Brooks "No bullet silver", que ainda é relevante apesar da revolução da codificação OOP. (consulte também o Gartner Hype Cycle para novas tecnologias.)

muitos que usaram os dois também têm opiniões de experiências anedóticas, e isso tem algum valor, mas é conhecido em muitos estudos científicos que a análise autorreferida pode ser imprecisa. parece haver pouca análise quantitativa dessas diferenças ou, se houver, não é tão citada. é surpreendente como muitos cientistas da computação falam com autoridade sobre certos tópicos centrais de seu campo, mas ainda não citam a pesquisa científica para apoiar seus pontos de vista e não percebem que estão transmitindo a sabedoria convencional em seu campo (ainda que generalizada ).

como este é um site / fórum científico , aqui está uma breve tentativa de colocar as numerosas opiniões em pé mais firme e quantificar a diferença real. pode haver outros estudos e espero que outros possam apontá-los se souberem de algum.(pergunta zen: se realmente existe uma grande diferença e tanto esforço maciço no campo comercial de engenharia de software e em outros lugares foi aplicado / investido para concretizá-lo, por que as evidências científicas disso são tão difíceis de encontrar? alguma referência clássica e altamente citada no campo que quantifique definitivamente a diferença?)

este trabalho emprega experimental / quantitativo / científico análise e apóia especificamente que a compreensão por programadores iniciantes é aprimorada com métodos de codificação OOP em alguns casos, mas foi inconclusiva em outros casos (em relação ao tamanho do programa). note que esta é apenas uma das muitas / principais reivindicações sobre a superioridade do POO sendo avançada em outras respostas e pelos defensores do POO. o estudo estava possivelmente medindo um elemento psicológico conhecido como "carga cognitiva / sobrecarga", através da compreensão da codificação.

  • Uma comparação da compreensão de programas orientados a objetos e de procedimentos por programadores iniciantes que interagem com computadores. Susan Wiedenbeck, Vennila Ramalingam, Suseela Sarasamma, Cynthia L Corritore (1999)

    Este artigo relata dois experimentos comparando representações mentais e compreensão de programas por iniciantes nos estilos orientado a objetos e procedimentais. Os sujeitos eram programadores iniciantes matriculados em um segundo curso de programação que ensinava o paradigma orientado a objetos ou procedimental. O primeiro experimento comparou as representações mentais e a compreensão de pequenos programas escritos nos estilos procedural e orientado a objetos. O segundo experimento estendeu o estudo a um programa maior, incorporando recursos de linguagem mais avançados. Para os programas curtos, não houve diferença significativa entre os dois grupos em relação ao número total de perguntas respondidas corretamente, mas os sujeitos orientados a objetos foram superiores aos sujeitos processuais ao responder perguntas sobre a função do programa. Isso sugere que as informações das funções estavam mais prontamente disponíveis em suas representações mentais dos programas e apóiam um argumento de que a notação orientada a objetos destaca a função no nível da classe individual. Para o programa longo, um efeito correspondente não foi encontrado. A compreensão dos sujeitos procedimentais foi superior aos sujeitos orientados a objetos em todos os tipos de questões. As dificuldades vivenciadas pelos sujeitos orientados a objetos em responder perguntas em um programa maior sugerem que eles enfrentaram problemas em organizar informações e extrair inferências delas. Sugerimos que esse resultado possa estar relacionado a uma curva de aprendizado mais longa para iniciantes no estilo orientado a objetos, bem como aos recursos do estilo OO e à notação específica da linguagem OO.

Veja também:

vzn
fonte
1
Eu respeito os estudos experimentais. No entanto, existe a questão de verificar se eles abordam as perguntas corretas. Existem muitas variáveis ​​no que pode ser chamado de POO, e de maneiras de usá-lo, para que um único estudo seja significativo. Como muitas coisas na programação, o OOP foi criado por especialistas para atender às suas próprias necessidades . Ao discutir a utilidade da OOP (que não tomei como tópico do OP, que é mais se trata de uma falha na programação processual), pode-se perguntar: qual recurso, para quem, com que finalidade? Então, apenas os estudos de campo se tornam totalmente significativos.
27514
1
Aviso de anedota: Se um problema é pequeno (por exemplo, até cerca de 500-1000 linhas de código), o OOP não faz diferença na minha experiência, ele pode até atrapalhar por ter que se preocupar com coisas que fazem pouca diferença. Se o problema for grande e tiver alguma forma de "partes intercambiáveis", além disso, será possível adicionar posteriormente (janelas em uma GUI, dispositivos em um sistema operacional, ...) a disciplina de OOP da organização fornece é inevitável. Você certamente pode programar OOP sem suporte a idiomas (veja, por exemplo, o kernel do Linux).
vonbrand 10/09/2015
1

Seja cuidadoso. Leia o clássico de R. King "Meu gato é orientado a objetos" em "Conceitos, bancos de dados e aplicativos orientados a objetos" (Kim e Lochovsky, orgs) (ACM, 1989). "Orientado a objetos" tornou-se mais uma palavra da moda do que um conceito claro.

Além disso, existem muitas variações sobre o tema, com pouco em comum. Existem linguagens baseadas em protótipo (a herança é de objetos, não há classes como tais) e linguagens baseadas em classes. Existem idiomas que permitem herança múltipla, outros que não. Algumas linguagens têm uma idéia como as interfaces de Java (podem ser tomadas como uma forma de herança múltipla diluída). Existe a ideia de mixins. A herança pode ser bastante rigorosa (como em C ++, não pode realmente mudar o que você obtém em uma subclasse) ou manipulada de maneira muito liberal (em Perl, a subclasse pode redefinir quase tudo). Alguns idiomas têm uma única raiz para herança (normalmente chamada Objeto, com comportamento padrão), outros permitem que o programador crie várias árvores. Algumas línguas insistem que "tudo é um objeto", outras lidam com objetos e não-objetos, alguns (como Java) têm "a maioria são objetos, mas esses poucos tipos aqui não são". Algumas linguagens insistem no encapsulamento estrito do estado nos objetos, outras o tornam opcional (privado, protegido, público) do C ++, outras não possuem encapsulamento. Se você olhar de soslaio para um idioma como Scheme do ângulo certo, verá OOP incorporado sem nenhum esforço especial (pode definir funções que retornam funções que encapsulam algum estado local).

vonbrand
fonte
0

Para ser conciso, a Programação Orientada a Objetos aborda os problemas de segurança de dados presentes na programação procedural. Isso é feito usando o conceito de encapsular os dados, permitindo que apenas as classes legítimas herdem os dados. Modificadores de acesso facilitam alcançar esse objetivo. Espero que ajude.:)

manu
fonte
Quais são os problemas de segurança de dados presentes na programação processual?
user31782
Na programação procedural, não se pode restringir o uso de uma variável global. Qualquer função poderia usar seu valor. No entanto, no OOP, eu poderia restringir o uso de uma variável a uma determinada classe sozinha ou apenas às classes que a herdam.
manu
Também na programação procedural, podemos restringir o uso da variável global usando a variável para determinadas funções, ou seja, não declarando dados globais.
user31782
Se você não declarar globalmente, não é uma variável global.
manu
1
"Seguro" ou "Correto" não significam nada sem uma especificação. Essas são tentativas de especificar um código para esse objetivo: Tipos, Definições de classe, DesignByContract, etc. Você obtém "Segurança" no sentido de poder tornar invioláveis ​​os limites de dados privados; assumindo que você deve obedecer ao conjunto de instruções da máquina virtual para executar. A Orientação a Objetos não oculta a memória interna de alguém que possa ler a memória diretamente, e um projeto incorreto de protocolo de objetos está revelando segredos por design.
Rob