Como isso seria programado em não OO? [fechadas]

11

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.

De: http://www.smashcompany.com/technology/object-oriented-programming-is-an-expensive-disaster-which-must-end

// 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)?

Danny Yaroslavski
fonte
44
Você não pode esperar razoavelmente comparar paradigmas de programação em exemplos tão curtos e elaborados. Qualquer pessoa aqui pode criar requisitos de código que fazem com que seu próprio paradigma preferido pareça melhor que o resto, especialmente se eles implementarem outros de forma inadequada. Somente quando você tem um projeto real, grande e em mudança, é possível obter insights sobre pontos fortes e fracos de diferentes paradigmas.
Euphoric
20
Não há nada na programação de OO que determine que esses três métodos sigam juntos na mesma classe; Da mesma forma, não há nada na programação OO que determine que o comportamento deva existir na mesma classe que os dados. Ou seja, com a Programação OO, você pode colocar os dados na mesma classe que o comportamento ou dividi-los em uma entidade / modelo separado. de qualquer forma, o OO não tem realmente nada a dizer sobre como os dados devem se relacionar com um objeto, uma vez que o conceito de um objeto está fundamentalmente preocupado com a modelagem do comportamento , agrupando métodos relacionados à lógica em uma classe.
Ben Cottrell
20
Eu tenho 10 frases nesse discurso retórico de um artigo e desisti. Não preste atenção no homem atrás daquela cortina. Em outras notícias, eu não fazia ideia de que os True Scotsmen eram principalmente programadores de OOP.
Robert Harvey
11
Ainda outro discurso retórico de alguém que escreve código processual em uma linguagem OO, então se pergunta por que o OO não está funcionando para ele.
TheCatWhisperer
11
Embora seja indubitavelmente verdade que OOP é um desastre de erros de design do começo ao fim - e tenho orgulho de fazer parte disso! - este artigo é ilegível, e o exemplo que você dá é basicamente o argumento de que uma hierarquia de classes mal projetada é mal projetada.
Eric Lippert

Respostas:

42

No estilo FP, Productseria uma classe imutável, product.setPricenão mudaria um Productobjeto, mas retornaria um novo objeto, e a increasePricefunção seria uma função "independente". Usando uma sintaxe de aparência semelhante à sua (como C # / Java), uma função equivalente pode ser assim:

 public List increasePrice(List products, int percentage) {
    if (products != null) {
        return products.Select(product => {
                double newPrice = product.getPrice().doubleValue() *
                    (100 + percentage)/100;
                return product.setPrice(newPrice);     
               });
    }
    else return null;
}

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.

Doc Brown
fonte
7
Maneiras de tornar esse "mais FP": 1) Use tipos Talvez / Opcional em vez de anulável para facilitar a gravação de funções totais em vez de funções parciais e use funções auxiliares de ordem superior para abstrair "if (x! = Null)" lógica. 2) Use lentes para definir o aumento do preço de um único produto em termos de aplicação de um aumento percentual no contexto de uma lente no preço do produto. 3) Use aplicação / composição / curry parcial para evitar um lambda explícito para a chamada mapa / seleção.
10243 Jack
6
Tenho que dizer que eu odeio que a idéia de uma coleção possa ser nula em vez de simplesmente vazia por design. Os idiomas funcionais com suporte nativo à tupla / coleção funcionam dessa maneira. Mesmo no OOP, odeio retornar nullonde uma coleção é o tipo de retorno. / rant over
Berin Loritsch
Mas esse pode ser um método estático, como em uma classe de utilitário em linguagens OOP como Java ou C #. Esse código é mais curto em parte porque você pede para passar na lista e não o mantém. O código original também possui uma estrutura de dados e apenas movê-la para fora tornaria o código original mais curto sem uma alteração nos conceitos.
Mark
@ Mark: claro, e acho que o OP já sabe disso. Entendo a pergunta como "como expressar isso de uma maneira funcional", não obrigatória em um idioma que não seja OOP.
Doc Brown
@ Mark FP e OO não se excluem.
Pieter B
17

O que estou perguntando especificamente é como esse exemplo seria modelado / programado em uma linguagem FP (código atual, não teoricamente)?

Na 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".

(mapcar (lambda (x) (* x 2)) '(1 2 3))

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:

incPrice :: (Num) -> [Num] -> [Num]  
incPrice _ [] = []  
incPrice percentage (x:xs) = x*percentage : incPrice percentage xs  

(Ou algo assim, faz séculos ...)

Eu quero estar aberto aos argumentos do autor,

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.

AnoE
fonte
4
"abundantemente claro que ele não tem interesse em discutir, ele está em uma cruzada" Tenha um voto positivo para esta jóia.
Euphoric
Você não precisa de uma restrição Num no seu código Haskell? como você pode ligar (*) caso contrário?
jk.
@jk., faz muito tempo que eu fiz Haskell, que era apenas para satisfazer a restrição do OP quanto à resposta que ele estava procurando. ;) Se alguém quiser corrigir meu código, fique à vontade. Mas claro, eu vou mudar para Num.
AnoE
7

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 Productaula 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 CategoryManagerpara acompanhar todos os diferentes tipos de categorias ou essa responsabilidade pertence à Categoryclasse 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 Productclasse, na Categoryclasse, na DiscountRuleclasse, na CategoryManagerclasse ou precisamos de algo novo? É assim que terminamos com coisas assim DiscountRuleProductCategoryFactoryBuilder.

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 mapPricesno exemplo a seguir do Scala:

def mapPrices(f: Int => Int)(products: Traversable[Product]): Traversable[Product] =
  products map {x => x.copy(price = f(x.price))}

def increasePrice(percentage: Int)(price: Int): Int =
  price * (percentage + 100) / 100

mapPrices(increasePrice(25))(products)

Provavelmente eu poderia adicionar outras funções relacionadas a preços aqui decreasePrice, como applyBulkDiscount, 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 productsmembro 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.

Karl Bielefeldt
fonte
2

Simplesmente separar os dados e a função como o autor estava aludindo poderia ter esta aparência em F # ("uma linguagem FP").

module Product =

    type Product = {
        Price : decimal
        ... // other properties not mentioned
    }

    let increasePrice ( percentage : int ) ( product : Product ) : Product =
        let newPrice = ... // calculate

        { product with Price = newPrice }

Você pode executar um aumento de preço em uma lista de produtos dessa maneira.

let percentage = 10
let products : Product list = ...  // load?

products
|> List.map (Product.increasePrice percentage)

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 returnna 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õe mappara 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).

Kasey Speakman
fonte
1

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.

export class Product extends Object {
    name: string;
    price: number;
    category: string;
}

products: Product[] = [
    new Product( { name: "Tablet", "price": 20.99, category: 'Electronics' } ),
    new Product( { name: "Phone", "price": 500.00, category: 'Electronics' } ),
    new Product( { name: "Car", "price": 13500.00, category: 'Auto' } )
];

// find all electronics and double their price
let newProducts = products
    .filter( ( product: Product ) => product.category === 'Electronics' )
    .map( ( product: Product ) => {
        product.price *= 2;
        return product;
    } );

console.log( newProducts );

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.

Conor Mancone
fonte
2
Este não é realmente um exemplo de POO, no sentido clássico. Na verdadeira POO, os dados seriam combinados com o comportamento; aqui, você separou os dois. Não é necessariamente uma coisa ruim (eu realmente acho isso mais limpo), mas não é o que eu chamaria de POO clássica.
Robert Harvey
0

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:

class SimpleProductManager extends ProductManager {
    ...
}

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:

public void increasePrice(int number) {
    if (products != null) {
        for (Product product : products) {
            double newPrice = product.getPrice() + number;
            product.setPrice(newPrice);
        }
    }
}

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.

Fis
fonte