Lendo um artigo contundente sobre as desvantagens do POO em favor de outro paradigma , encontrei um exemplo em que não consigo encontrar muita falha.
Quero estar aberto aos argumentos do autor e, embora eu possa entender teoricamente seus argumentos, um exemplo em particular está tendo dificuldade em imaginar como seria melhor implementado em, por exemplo, uma linguagem FP.
// Consider the case where “SimpleProductManager” is a child of
// “ProductManager”:
public class SimpleProductManager implements ProductManager {
private List products;
public List getProducts() {
return products;
}
public void increasePrice(int percentage) {
if (products != null) {
for (Product product : products) {
double newPrice = product.getPrice().doubleValue() *
(100 + percentage)/100;
product.setPrice(newPrice);
}
}
}
public void setProducts(List products) {
this.products = products;
}
}
// There are 3 behaviors here:
getProducts()
increasePrice()
setProducts()
// Is there any rational reason why these 3 behaviors should be linked to
// the fact that in my data hierarchy I want “SimpleProductManager” to be
// a child of “ProductManager”? I can not think of any. I do not want the
// behavior of my code linked together with my definition of my data-type
// hierarchy, and yet in OOP I have no choice: all methods must go inside
// of a class, and the class declaration is also where I declare my
// data-type hierarchy:
public class SimpleProductManager implements ProductManager
// This is a disaster.
Observe que não estou procurando uma refutação a favor ou contra os argumentos do escritor para "Existe alguma razão racional pela qual esses três comportamentos devem estar vinculados à hierarquia de dados?".
O que estou perguntando especificamente é como esse exemplo seria modelado / programado em uma linguagem FP (código atual, não teoricamente)?
object-oriented
functional-programming
Danny Yaroslavski
fonte
fonte
Respostas:
No estilo FP,
Product
seria uma classe imutável,product.setPrice
não mudaria umProduct
objeto, mas retornaria um novo objeto, e aincreasePrice
função seria uma função "independente". Usando uma sintaxe de aparência semelhante à sua (como C # / Java), uma função equivalente pode ser assim:Como você vê, o núcleo não é realmente diferente aqui, exceto o código "clichê" do exemplo artificial de OOP é omitido. No entanto, não vejo isso como evidência de que OOP leva a código inchado, apenas como evidência do fato de que, se alguém construir um exemplo de código suficientemente artificial, é possível provar qualquer coisa.
fonte
null
onde uma coleção é o tipo de retorno. / rant overNa linguagem "a" FP? Se houver, eu escolho o Emacs lisp. Ele tem o conceito de tipos (mais ou menos), mas apenas os incorporados. Portanto, seu exemplo se reduz a "como você multiplica cada item de uma lista por algo e retorna uma nova lista".
Ai está. Outros idiomas serão semelhantes, com a diferença de que você obtém o benefício de tipos explícitos com a semântica funcional de "correspondência" usual. Confira Haskell:
(Ou algo assim, faz séculos ...)
Por quê? Eu tentei ler o artigo; Eu tive que desistir de uma página e rapidamente digitalizei o resto.
O problema do artigo não é que seja contra a OOP. Nem sou cego "pro OOP". Programei com paradigmas lógicos, funcionais e de POO, muitas vezes na mesma linguagem, quando possível, e freqüentemente sem nenhum dos três, puramente imperativos ou mesmo no nível do montador. Eu nunca diria que qualquer um desses paradigmas é imensamente superior ao outro em todos os aspectos. Eu diria que gosto mais da linguagem X do que da Y? Claro que sim! Mas não é disso que trata esse artigo.
O problema do artigo é que ele usa uma abundância de ferramentas retóricas (falácias) da primeira à última frase. É completamente inútil começar a descrever todos os erros que ele contém. O autor deixa bem claro que ele não tem interesse em discutir, ele está em uma cruzada. Então, por que se preocupar?
No final do dia, todas essas coisas são apenas ferramentas para realizar um trabalho. Pode haver empregos em que a POO é melhor e outros em que a PF é melhor ou onde ambos são um exagero. O importante é escolher a ferramenta certa para o trabalho e fazê-lo.
fonte
O autor fez uma observação muito boa e depois escolheu um exemplo sem brilho para tentar fazer o backup. A reclamação não está na implementação da classe, mas na ideia de que a hierarquia de dados está inextricavelmente acoplada à hierarquia de funções.
Segue-se, então, que, para entender o ponto de vista do autor, não ajudaria apenas ver como ele implementaria essa classe única em um estilo funcional. Você precisaria ver como ele projetaria todo o contexto de dados e funções em torno dessa classe em um estilo funcional.
Pense nos possíveis tipos de dados envolvidos nos produtos e nos preços. Para debater alguns: nome, código upc, categoria, peso da remessa, preço, moeda, código de desconto, regra de desconto.
Essa é a parte fácil do design orientado a objetos. Acabamos de fazer uma classe para todos os "objetos" acima e somos bons, certo? Faça uma
Product
aula para combinar alguns deles?Mas espere, você pode ter coleções e agregados de alguns desses tipos: Defina [categoria], (código de desconto -> preço), (quantidade -> valor do desconto) e assim por diante. Onde eles se encaixam? Criamos um separado
CategoryManager
para acompanhar todos os diferentes tipos de categorias ou essa responsabilidade pertence àCategory
classe que já criamos?Agora, e as funções que oferecem um desconto no preço se você tiver uma certa quantidade de itens de duas categorias diferentes? Isso acontece na
Product
classe, naCategory
classe, naDiscountRule
classe, naCategoryManager
classe ou precisamos de algo novo? É assim que terminamos com coisas assimDiscountRuleProductCategoryFactoryBuilder
.No código funcional, sua hierarquia de dados é completamente ortogonal às suas funções. Você pode classificar suas funções da maneira que fizer sentido semântico. Por exemplo, você pode agrupar todas as funções que alteram os preços dos produtos. Nesse caso, faria sentido considerar a funcionalidade comum, como
mapPrices
no exemplo a seguir do Scala:Provavelmente eu poderia adicionar outras funções relacionadas a preços aqui
decreasePrice
, comoapplyBulkDiscount
, etc.Como também usamos uma coleção de
Products
, a versão OOP precisa incluir métodos para gerenciar essa coleção, mas você não queria que este módulo fosse sobre seleção de produtos, mas sobre preços. O acoplamento de dados de função também forçou você a incluir o clichê de gerenciamento de coleção.Você pode tentar resolver isso colocando o
products
membro em uma classe separada, mas depois acaba com classes muito fortemente acopladas. Os programadores de OO acham o acoplamento de dados de função muito natural e até benéfico, mas há um alto custo associado à perda de flexibilidade. Sempre que você cria uma função, deve atribuí-la a uma e apenas uma classe. Sempre que você quiser usar uma função, você deve encontrar uma maneira de obter seus dados acoplados ao ponto de uso. Essas restrições são enormes.fonte
Simplesmente separar os dados e a função como o autor estava aludindo poderia ter esta aparência em F # ("uma linguagem FP").
Você pode executar um aumento de preço em uma lista de produtos dessa maneira.
Nota: Se você não estiver familiarizado com FP, cada função retornará um valor. Vindo de uma linguagem do tipo C, você pode tratar a última instrução em uma função como se tivesse uma
return
na frente.Incluí algumas anotações de tipo, mas elas devem ser desnecessárias. getter / setter são desnecessários aqui, pois o módulo não possui os dados. Possui a estrutura dos dados e as operações disponíveis. Isso também pode ser visto
List
, que expõemap
para executar uma função em todos os elementos da lista e retorna o resultado em uma nova lista.Observe que o módulo Produto não precisa saber nada sobre loop, pois essa responsabilidade permanece com o módulo List (que criou a necessidade de loop).
fonte
Permitam-me que comece com o fato de que eu não sou especialista em programação funcional. Eu sou mais uma pessoa OOP. Portanto, embora eu tenha certeza de que o seguinte é como você realizaria o mesmo tipo de funcionalidade com o FP, eu posso estar errado.
Isso está em Texto datilografado (daí todas as anotações de tipo). Texto datilografado (como javascript) é uma linguagem de vários domínios.
Em detalhes (e novamente, não um especialista em FP), o importante é entender que não há muito comportamento predefinido. Não existe um método de "aumento de preço" que aplique um aumento de preço em toda a lista, porque é claro que isso não é POO: não há classe para definir esse comportamento. Em vez de criar um objeto que armazena uma lista de produtos, você apenas cria uma matriz de produtos. Você pode usar procedimentos FP padrão para manipular essa matriz da maneira que desejar: filtrar para selecionar itens específicos, mapear para ajustar internos, etc. API que o SimpleProductManager fornece a você. Isso pode ser considerado uma vantagem por alguns. Também é verdade que você não não precisa se preocupar com nenhuma bagagem associada à classe ProductManager. Por fim, não há preocupação com "SetProducts" ou "GetProducts", porque não há nenhum objeto que oculte seus produtos: você apenas possui a lista de produtos com os quais está trabalhando. Novamente, isso pode ser uma vantagem ou desvantagem, dependendo das circunstâncias / pessoa com quem você está falando. Além disso, obviamente não há hierarquia de classes (do que ele estava reclamando), porque não existem classes em primeiro lugar. isso pode ser uma vantagem ou desvantagem, dependendo das circunstâncias / pessoa com quem você está falando. Além disso, obviamente não há hierarquia de classes (do que ele estava reclamando), porque não existem classes em primeiro lugar. isso pode ser uma vantagem ou desvantagem, dependendo das circunstâncias / pessoa com quem você está falando. Além disso, obviamente não há hierarquia de classes (do que ele estava reclamando), porque não existem classes em primeiro lugar.
Não tive tempo para ler todo o seu discurso. Uso práticas de FP quando é conveniente, mas definitivamente sou mais do tipo OOP. Então, pensei que, desde que respondesse sua pergunta, também faria alguns breves comentários sobre as opiniões dele. Eu acho que este é um exemplo muito artificial que destaca as "desvantagens" do POO. Nesse caso em particular, para a funcionalidade mostrada, o OOP provavelmente está exagerado e o FP provavelmente seria um ajuste melhor. Por outro lado, se isso fosse algo como um carrinho de compras, proteger sua lista de produtos e limitar o acesso a ela é (acho) um objetivo muito importante do programa, e a FP não tem como impor essas coisas. Novamente, pode ser que eu não seja um especialista em FP, mas tendo implementado carrinhos de compras para sistemas de comércio eletrônico, eu preferiria usar OOP do que FP.
Pessoalmente, tenho dificuldade em levar alguém a sério que faça um argumento tão forte para "X é simplesmente terrível. Sempre use Y". A programação tem uma variedade de ferramentas e paradigmas, porque há uma grande variedade de problemas a serem resolvidos. O FP tem o seu lugar, o OOP tem o seu lugar, e ninguém será um grande programador se não conseguir entender as desvantagens e vantagens de todas as nossas ferramentas e quando usá-las.
** nota: Obviamente, há uma classe no meu exemplo: a classe Product. Neste caso, embora seja simplesmente um contêiner de dados estúpido: não acho que meu uso viole os princípios do FP. É mais um auxiliar para verificação de tipo.
** observação: não me lembro de cima da cabeça e não verifiquei se a maneira como usei a função de mapa modificaria os produtos no local, ou seja, eu inadvertidamente dobrei o preço dos produtos nos produtos originais array. Obviamente, esse é o tipo de efeito colateral que a FP tenta evitar e, com um pouco mais de código, eu certamente poderia garantir que isso não aconteça.
fonte
Não me parece que o SimpleProductManager seja filho (estende ou herda) de alguma coisa.
É apenas a implementação da interface ProductManager, que é basicamente um contrato que define quais ações (comportamentos) o objeto deve executar.
Se fosse uma criança (ou melhor, classe ou classe herdada estendendo outra funcionalidade de classe), ela seria escrita como:
Então, basicamente, o autor diz:
Temos um objeto cujo comportamento é: setProducts, raisePrice, getProducts. E não nos importamos se o objeto também tem outro comportamento ou como o comportamento é implementado.
A classe SimpleProductManager a implementa. Basicamente, ele executa ações.
Também pode ser chamado de PercentagePriceIncreaser, pois seu comportamento principal é aumentar o preço em algum valor percentual.
Mas também podemos implementar outra classe: ValuePriceIncreaser, que se comportará como:
Do ponto de vista externo, nada mudou, a interface é a mesma, ainda tem os mesmos três métodos, mas o comportamento é diferente.
Como não existem interfaces no FP, seria difícil de implementar. Em C, por exemplo, podemos manter ponteiros para funções e chamar um apropriado com base em nossas necessidades. No final, no OOP, ele funciona de maneira muito semelhante, mas é "automatizado" pelo compilador.
fonte