Leyendo un artículo mordaz sobre las desventajas de OOP a favor de algún otro paradigma, me he encontrado con un ejemplo con el que no puedo encontrar demasiadas fallas.
Quiero estar abierto a los argumentos del autor, y aunque teóricamente puedo entender sus puntos, un ejemplo en particular me está costando mucho imaginar cómo se implementaría mejor, por ejemplo, en un lenguaje 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.
Tenga en cuenta que no estoy buscando una refutación a favor o en contra de los argumentos del escritor para "¿Hay alguna razón racional por la que estos 3 comportamientos deberían estar vinculados a la jerarquía de datos?".
Lo que pregunto específicamente es cómo se modelaría / programaría este ejemplo en un lenguaje FP (código real, no teóricamente).
object-oriented
functional-programming
Danny Yaroslavski
fuente
fuente
Respuestas:
En el estilo FP,
Product
sería una clase inmutable,product.setPrice
no mutaría unProduct
objeto, sino que devolvería un nuevo objeto, y laincreasePrice
función sería una función "independiente". Usando una sintaxis similar a la suya (C # / Java like), una función equivalente podría verse así:Como puede ver, el núcleo no es realmente diferente aquí, excepto que se omite el código "repetitivo" del ejemplo artificial de OOP. Sin embargo, no veo esto como evidencia de que la POO conduzca a un código hinchado, solo como evidencia del hecho de que si se construye un ejemplo de código que sea suficientemente artificial, es posible probar cualquier cosa.
fuente
null
donde una colección es el tipo de retorno. / despotricarEn lenguaje "a" FP? Si alguno es suficiente, entonces elijo Emacs lisp. Tiene el concepto de tipos (tipo de, tipo de), pero solo los incorporados. Por lo tanto, su ejemplo se reduce a "¿cómo multiplica cada elemento de una lista por algo y devuelve una nueva lista".
Ahí tienes. Otros lenguajes serán similares, con la diferencia de que obtendrá el beneficio de los tipos explícitos con la semántica de "coincidencia" funcional habitual. Echa un vistazo a Haskell:
(O algo así, han pasado siglos ...)
¿Por qué? Traté de leer el artículo; Tuve que rendirme después de una página y escaneé rápidamente el resto.
El problema del artículo no es que esté en contra de la POO. Tampoco estoy ciegamente "pro OOP". He programado con paradigmas lógicos, funcionales y OOP, a menudo en el mismo lenguaje cuando sea posible, y con frecuencia sin ninguno de los tres, puramente imperativo o incluso a nivel de ensamblador. Yo no decir que ninguno de esos paradigmas es mucho superiour al otro en todos los aspectos. ¿Diría que me gusta más el lenguaje X que el Y? ¡Claro que si! Pero de eso no se trata ese artículo.
El problema del artículo es que usa una gran cantidad de herramientas retóricas (falacias) desde la primera hasta la última oración. Es completamente inútil incluso comenzar a describir todos los errores que contiene. El autor deja muy claro que no tiene ningún interés en la discusión, está en una cruzada. ¿Entonces, para qué molestarse?
Al final del día, todas esas cosas son solo herramientas para hacer un trabajo. Puede haber trabajos donde OOP es mejor, y puede haber otros trabajos donde FP es mejor, o donde ambos son excesivos. Lo importante es elegir la herramienta adecuada para el trabajo y hacerlo.
fuente
El autor hizo una muy buena observación y luego eligió un ejemplo mediocre para intentar respaldarlo. La queja no es con la implementación de la clase, es con la idea de que la jerarquía de datos está inextricablemente unida a la jerarquía de funciones.
Entonces se deduce que para comprender el punto del autor, no sería útil ver cómo implementaría esta clase individual en un estilo funcional. Tendría que ver cómo diseñaría todo el contexto de datos y funciones en torno a esta clase en un estilo funcional.
Piense en los tipos de datos potenciales involucrados en productos y precios. Para hacer una lluvia de ideas: nombre, código upc, categoría, peso de envío, precio, moneda, código de descuento, regla de descuento.
Esta es la parte fácil del diseño orientado a objetos. Acabamos de hacer una clase para todos los "objetos" anteriores y estamos bien, ¿verdad? ¿Hacer una
Product
clase para combinar algunos de ellos?Pero espere, puede tener colecciones y agregados de algunos de esos tipos: Conjunto [categoría], (código de descuento -> precio), (cantidad -> cantidad de descuento), y así sucesivamente. ¿Dónde encajan esos? ¿Creamos por separado
CategoryManager
para hacer un seguimiento de todos los diferentes tipos de categorías, o esa responsabilidad pertenece a laCategory
clase que ya creamos?Ahora, ¿qué pasa con las funciones que le dan un descuento en el precio si tiene una cierta cantidad de artículos de dos categorías diferentes? ¿Eso va en la
Product
clase, laCategory
clase, laDiscountRule
clase, laCategoryManager
clase o necesitamos algo nuevo? Así es como terminamos con cosas comoDiscountRuleProductCategoryFactoryBuilder
.En el código funcional, su jerarquía de datos es completamente ortogonal a sus funciones. Puede ordenar sus funciones de cualquier manera que tenga sentido semántico. Por ejemplo, puede agrupar todas las funciones que cambian los precios de los productos, en cuyo caso tendría sentido factorizar la funcionalidad común como
mapPrices
en el siguiente ejemplo de Scala:Probablemente podría agregar otras funciones relacionadas con el precio aquí
decreasePrice
, comoapplyBulkDiscount
, etc.Debido a que también usamos una colección de
Products
, la versión OOP debe incluir métodos para administrar esa colección, pero no quería que este módulo se tratara de la selección de productos, quería que se tratara de los precios. El acoplamiento de datos de función también lo obligó a tirar la placa de control de la gestión de la colección.Puede intentar resolver esto colocando al
products
miembro en una clase separada, pero luego termina con clases muy estrechamente acopladas. Los programadores de OO piensan que el acoplamiento de datos de funciones es muy natural e incluso beneficioso, pero hay un alto costo asociado con la pérdida de flexibilidad. Cada vez que crea una función, debe asignarla a una sola clase. Cada vez que desee usar una función, debe encontrar una manera de llevar sus datos acoplados al punto de uso. Esas restricciones son enormes.fuente
Simplemente separando los datos y la función como el autor aludía podría verse así en F # ("un lenguaje FP").
Puede realizar un aumento de precio en una lista de productos de esta manera.
Nota: Si no está familiarizado con FP, cada función devuelve un valor. Procedente de un lenguaje similar a C, puede tratar la última declaración en una función como si tuviera un
return
frente.Incluí algunas anotaciones de tipo, pero deberían ser innecesarias. getter / setter son innecesarios aquí ya que el módulo no posee los datos. Posee la estructura de los datos y las operaciones disponibles. Esto también se puede ver con
List
, lo que exponemap
a ejecutar una función en cada elemento de la lista y devuelve el resultado en una nueva lista.Tenga en cuenta que el módulo Producto no tiene que saber nada sobre el bucle, ya que esa responsabilidad recae en el módulo Lista (que creó la necesidad de bucle).
fuente
Permítanme presentar esto con el hecho de que no soy un experto en programación funcional. Soy más una persona OOP. Entonces, aunque estoy bastante seguro de que lo siguiente es cómo lograrías el mismo tipo de funcionalidad con FP, podría estar equivocado.
Esto está en mecanografiado (de ahí todas las anotaciones de tipo). El mecanografiado (como javascript) es un lenguaje multidominio.
En detalle (y de nuevo, no es un experto en FP), lo que hay que entender es que no hay mucho comportamiento predefinido. No existe un método de "aumento de precio" que aplique un aumento de precio en toda la lista, porque, por supuesto, esto no es POO: no hay una clase en la que definir dicho comportamiento. En lugar de crear un objeto que almacene una lista de productos, simplemente cree una variedad de productos. A continuación, puede utilizar procedimientos FP estándar para manipular esta matriz de la forma que desee: filtrar para seleccionar elementos particulares, asignar para ajustar elementos internos, etc. Termina con un control más detallado sobre su lista de productos sin tener que estar limitado por el API que le proporciona SimpleProductManager. Esto puede ser considerado una ventaja por algunos. También es cierto que no No tiene que preocuparse por ningún equipaje asociado con la clase ProductManager. Finalmente, no hay que preocuparse por "SetProducts" o "GetProducts", porque no hay ningún objeto que oculte sus productos: en su lugar, solo tiene la lista de productos con los que está trabajando. Nuevamente, esto puede ser una ventaja o desventaja dependiendo de las circunstancias / persona con la que está hablando. Además, obviamente no hay una jerarquía de clases (que es de lo que se estaba quejando) porque no hay clases en primer lugar. Esto puede ser una ventaja o desventaja dependiendo de las circunstancias / persona con la que está hablando. Además, obviamente no hay una jerarquía de clases (que es de lo que se estaba quejando) porque no hay clases en primer lugar. Esto puede ser una ventaja o desventaja dependiendo de las circunstancias / persona con la que está hablando. Además, obviamente no hay una jerarquía de clases (que es de lo que se estaba quejando) porque no hay clases en primer lugar.
No me tomé el tiempo de leer toda su diatriba. Utilizo prácticas de FP cuando es conveniente, pero definitivamente soy más del tipo OOP. Así que pensé que desde que respondí tu pregunta, también haría algunos breves comentarios sobre sus opiniones. Creo que este es un ejemplo muy artificial que destaca los "inconvenientes" de la POO. En este caso en particular, para la funcionalidad que se muestra, OOP probablemente está sobre-asesinado, y FP probablemente sería una mejor opción. Por otra parte, si esto fuera por algo así como un carrito de compras, proteger su lista de productos y limitar el acceso a ella es (creo) un objetivo muy importante del programa, y FP no tiene forma de hacer cumplir tales cosas. De nuevo, puede ser que no soy un experto en FP, pero habiendo implementado carritos de compras para sistemas de comercio electrónico, preferiría usar OOP que FP.
Personalmente, me cuesta tomar en serio a cualquiera que tenga un argumento tan fuerte para "X es simplemente terrible. Siempre usa Y". La programación tiene una variedad de herramientas y paradigmas porque hay una gran variedad de problemas para resolver. FP tiene su lugar, OOP tiene su lugar, y nadie será un gran programador si no puede entender los inconvenientes y las ventajas de todas nuestras herramientas y cuándo usarlas.
** nota: Obviamente hay una clase en mi ejemplo: la clase de Producto. En este caso, aunque es simplemente un contenedor de datos tonto: no creo que mi uso viole los principios de FP. Es más útil para la verificación de tipos.
** nota: no recuerdo la parte superior de mi cabeza y no verifiqué si la forma en que utilicé la función de mapa modificaría los productos en el lugar, es decir, ¿doblé inadvertidamente el precio de los productos en los productos originales? formación. Obviamente, ese es el tipo de efecto secundario que FP intenta evitar, y con un poco más de código ciertamente podría asegurarme de que no suceda.
fuente
No me parece que SimpleProductManager sea hijo (se extienda o herede) de algo.
Es solo la implementación de la interfaz ProductManager, que es básicamente un contrato que define qué acciones (comportamientos) debe realizar el objeto.
Si fuera un niño (o mejor dicho, una clase heredada o una clase que extiende la funcionalidad de otra clase) se escribiría como:
Básicamente, el autor dice:
Cuando tenemos algún objeto cuyo comportamiento es: setProducts, raisePrice, getProducts. Y no nos importa si el objeto también tiene otro comportamiento o cómo se implementa el comportamiento.
La clase SimpleProductManager lo implementa. Básicamente, ejecuta acciones.
También se le puede llamar PercentagePriceIncreaser ya que su comportamiento principal es aumentar el precio en algún valor porcentual.
Pero también podemos implementar otra clase: ValuePriceIncreaser cuyo comportamiento será:
Desde el punto de vista externo, nada ha cambiado, la interfaz es la misma, todavía tiene los mismos tres métodos pero el comportamiento es diferente.
Como no existen interfaces en FP, sería difícil de implementar. En C, por ejemplo, podemos mantener punteros a las funciones y llamar al apropiado según nuestras necesidades. Al final, en OOP funciona de manera muy muy similar, pero es "automatizado" por el compilador.
fuente