Читая ужасную статью о минусах ООП в пользу какой- то другой парадигмы, я натолкнулся на пример, в котором я не могу найти слишком много недостатков.
Я хочу быть открытым для аргументов автора, и хотя я теоретически могу понять их аргументы, в частности, один пример мне трудно представить, как это будет лучше реализовано, скажем, на языке 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.
Обратите внимание, что я не ищу опровержения за или против аргументов автора для «Есть ли какая-либо рациональная причина, почему эти 3 поведения должны быть связаны с иерархией данных?».
Я специально спрашиваю, как этот пример будет смоделирован / запрограммирован на языке FP (Фактический код, а не теоретически)?
object-oriented
functional-programming
Данни Ярославский
источник
источник
Ответы:
В стиле FP
Product
это был бы неизменный класс,product.setPrice
который не изменял быProduct
объект, а вместо этого возвращал новый объект, аincreasePrice
функция была бы «автономной» функцией. Используя похожий синтаксис, похожий на ваш (C # / Java как), эквивалентная функция может выглядеть так:Как видите, ядро здесь не сильно отличается, за исключением того, что «шаблонный» код из надуманного примера ООП опущен. Однако я не рассматриваю это как доказательство того, что ООП приводит к раздутому коду, только как доказательство того факта, что если создать пример кода, который является достаточно искусственным, то можно доказать что угодно.
источник
null
где коллекция является типом возврата. / разглагольствованияНа языке "FP"? Если что-то достаточно, я выбираю Emacs lisp. У него есть понятие типов (типа, типа), но только встроенные. Таким образом, ваш пример сводится к тому, «как вы умножаете каждый элемент в списке на что-то и возвращаете новый список».
Вот и ты. Другие языки будут похожи, с той разницей, что вы получаете преимущество явных типов с обычной функциональной "совпадающей" семантикой. Проверьте Haskell:
(Или что-то в этом роде ...)
Зачем? Я пытался прочитать статью; Мне пришлось сдаться после страницы и просто быстро отсканировать остальные.
Проблема статьи не в том, что она против ООП. Я тоже не слепо "про ООП". Я программировал с помощью логических, функциональных и ООП-парадигм, довольно часто, когда это возможно, на одном языке и часто без какой-либо из трех, чисто императивной или даже на уровне ассемблера. Я бы никогда не сказал, что любая из этих парадигм значительно превосходит другую во всех аспектах. Могу ли я утверждать, что мне нравится язык X лучше, чем Y? Конечно я буду! Но это не то, о чем эта статья.
Проблема статьи заключается в том, что он использует множество инструментов риторики (ошибок) от первого до последнего предложения. Совершенно бесполезно даже начинать описывать все ошибки, которые в нем содержатся. Автор совершенно ясно дает понять, что у него нулевой интерес к дискуссии, он в крестовом походе. Так зачем?
В конце концов, все эти вещи - всего лишь инструменты для выполнения работы. Могут быть задания, где ООП лучше, и могут быть другие задания, где ПП лучше, или когда оба излишни. Важно выбрать правильный инструмент для работы и выполнить его.
источник
Автор высказал очень хорошую мысль, а затем выбрал более призрачный пример, чтобы попытаться его подтвердить. Жалоба не на реализацию класса, а на идею, что иерархия данных неразрывно связана с иерархией функций.
Из этого следует, что, чтобы понять точку зрения автора, было бы не просто увидеть, как он реализует этот единственный класс в функциональном стиле. Вы должны увидеть, как он будет проектировать весь контекст данных и функций вокруг этого класса в функциональном стиле.
Подумайте о потенциальных типах данных, связанных с продуктами и ценами. Для мозгового штурма несколько: имя, код upc, категория, вес отправления, цена, валюта, код скидки, правило скидки.
Это легкая часть объектно-ориентированного дизайна. Мы просто создаем класс для всех вышеперечисленных «объектов», и мы хороши, верно? Сделать
Product
класс, чтобы объединить несколько из них вместе?Но подождите, у вас могут быть коллекции и агрегаты некоторых из этих типов: Set [категория], (код скидки -> цена), (количество -> сумма скидки) и так далее. Где они вписываются? Создаем ли мы отдельный,
CategoryManager
чтобы отслеживать все различные категории, или эта ответственность принадлежитCategory
классу, который мы уже создали?Теперь о функциях, которые дают вам ценовую скидку, если у вас есть определенное количество предметов из двух разных категорий? Это относится к
Product
классу,Category
классу,DiscountRule
классу,CategoryManager
классу, или нам нужно что-то новое? Вот как мы в конечном итоге с такими вещами, какDiscountRuleProductCategoryFactoryBuilder
.В функциональном коде ваша иерархия данных полностью ортогональна вашим функциям. Вы можете сортировать свои функции любым способом, который имеет смысл. Например, вы можете сгруппировать все функции, которые изменяют цены продуктов, и в этом случае имеет смысл выделить общие функции, как
mapPrices
в следующем примере Scala:Я мог бы добавить другие функции, связанные с ценой, например
decreasePrice
,applyBulkDiscount
и т. Д.Поскольку мы также используем коллекцию
Products
, версия ООП должна включать методы для управления этой коллекцией, но вы не хотели, чтобы этот модуль был посвящен выбору продукта, вы хотели, чтобы он был о ценах. Связь функции с данными вынудила вас также добавить сюда шаблон управления сбором.Вы можете попытаться решить эту проблему, поместив
products
член в отдельный класс, но тогда вы получите очень тесно связанные классы. ОО-программисты считают связывание функций с данными очень естественным и даже выгодным, но это приводит к высокой стоимости, связанной с потерей гибкости. Каждый раз, когда вы создаете функцию, вы должны назначить ее одному и только одному классу. В любое время, когда вы хотите использовать функцию, вы должны найти способ доставить связанные данные к месту использования. Эти ограничения огромны.источник
Простое разделение данных и функций, на которые ссылался автор, может выглядеть следующим образом в F # («язык FP»).
Таким способом вы можете повысить цену на список товаров.
Примечание. Если вы не знакомы с FP, каждая функция возвращает значение. Исходя из C-подобного языка, вы можете рассматривать последний оператор в функции, как если бы он был
return
перед ним.Я включил некоторые аннотации типов, но они должны быть ненужными. getter / setter здесь не нужны, так как модуль не владеет данными. Он владеет структурой данных и доступными операциями. Это можно увидеть и с помощью
List
, котораяmap
позволяет запускать функцию для каждого элемента в списке и возвращает результат в новом списке.Обратите внимание, что модуль Product не должен ничего знать о зацикливании, поскольку эта ответственность остается за модулем List (который создал необходимость в зацикливании).
источник
Позвольте мне предвосхитить это тем фактом, что я не специалист по функциональному программированию. Я больше ООП человек. Так что, хотя я почти уверен, что ниже приведен пример того, как вы могли бы реализовать такую же функциональность с FP, я могу ошибаться.
Это In Typescript (отсюда и все аннотации типов). Typescript (например, javascript) является многодоменным языком.
Подробно (и опять же, не эксперт по FP), нужно понимать, что здесь не так много предопределенного поведения. Не существует метода «повышения цены», который бы применял повышение цены по всему списку, потому что, конечно, это не ООП: нет класса, в котором можно было бы определить такое поведение. Вместо создания объекта, в котором хранится список товаров, вы просто создаете массив товаров. Затем вы можете использовать стандартные процедуры FP для манипулирования этим массивом любым удобным для вас способом: фильтр для выбора определенных элементов, сопоставление для настройки внутренних элементов и т. Д. Вы получаете более подробный контроль над списком продуктов, не ограничиваясь API, который предоставляет вам SimpleProductManager. Некоторые могут считать это преимуществом. Также верно, что вы не Не нужно беспокоиться о багаже, связанном с классом ProductManager. Наконец, не стоит беспокоиться о «SetProducts» или «GetProducts», потому что нет объектов, скрывающих ваши продукты: вместо этого у вас есть только список продуктов, с которыми вы работаете. Опять же, это может быть преимуществом или недостатком в зависимости от обстоятельств / человека, с которым вы разговариваете. Кроме того, очевидно, что нет иерархии классов (на что он жаловался), потому что в первую очередь нет классов. это может быть преимуществом или недостатком в зависимости от обстоятельств / человека, с которым вы разговариваете. Кроме того, очевидно, что нет иерархии классов (на что он жаловался), потому что в первую очередь нет классов. это может быть преимуществом или недостатком в зависимости от обстоятельств / человека, с которым вы разговариваете. Кроме того, очевидно, что нет иерархии классов (на что он жаловался), потому что в первую очередь нет классов.
Я не нашел время, чтобы прочитать всю его напыщенную речь. Я использую практики FP, когда это удобно, но я определенно больше похож на ООП. Так что я решил, что, так как я ответил на ваш вопрос, я бы также сделал несколько кратких комментариев по поводу его мнения. Я думаю, что это очень надуманный пример, который подчеркивает "недостатки" ООП. В данном конкретном случае для показанной функциональности ООП, вероятно, является чрезмерным, и ФП, вероятно, подойдет лучше. Опять же, если бы это было для чего-то вроде корзины для покупок, защита списка ваших товаров и ограничение доступа к нему - это (я думаю) очень важная цель программы, и у FP нет никакого способа обеспечить такие вещи. Опять же, возможно, я просто не эксперт по FP, но, внедрив корзины для систем электронной коммерции, я бы предпочел использовать ООП, а не FP.
Лично мне трудно воспринимать кого-либо всерьез, который приводит такой веский аргумент в пользу «X просто ужасен. Всегда используйте Y». Программирование имеет множество инструментов и парадигм, потому что существует множество проблем, которые необходимо решить. У FP есть свое место, у OOP есть свое место, и никто не станет великим программистом, если они не смогут понять недостатки и преимущества всех наших инструментов и когда их использовать.
** примечание: очевидно, в моем примере есть один класс: класс Product. В этом случае, хотя это просто тупой контейнер данных: я не думаю, что его использование нарушает принципы FP. Это скорее помощник для проверки типов.
** примечание: я не помню, как на макушке головы, и не проверял, изменит ли я то, как я использовал функцию карты, изменение продуктов на месте, т. е. случайно ли я удвоил цену продуктов в исходных продуктах массив. Это, очевидно, тот побочный эффект, которого FP пытается избежать, и с немного большим количеством кода я, безусловно, смог бы убедиться, что этого не произойдет.
источник
Мне не кажется, что SimpleProductManager является дочерним (расширяет или наследует) чего-то.
Это просто реализация интерфейса ProductManager, который по сути является контрактом, определяющим, какие действия (поведение) должен выполнять объект.
Если это будет дочерний элемент (или, точнее, наследуемый класс или класс, расширяющий функциональность другого класса), он будет записан как:
В общем, автор говорит:
Есть объект, поведение которого: setProducts, incrementPrice, getProducts. И нам все равно, имеет ли объект другое поведение или как это поведение реализовано.
Класс SimpleProductManager реализует это. По сути, он выполняет действия.
Его также можно назвать PercentagePriceIncreaser, так как его основное поведение заключается в увеличении цены на некоторое процентное значение.
Но мы также можем реализовать другой класс: ValuePriceIncreaser, поведение которого будет:
С внешней точки зрения ничего не изменилось, интерфейс тот же, все еще есть те же три метода, но поведение другое.
Поскольку в FP нет такой вещи, как интерфейсы, это было бы трудно реализовать. Например, в C мы можем хранить указатели на функции и вызывать соответствующие в зависимости от наших потребностей. В конце концов, в ООП он работает очень похожим образом, но его «автоматизирует» компилятор.
источник