Как бы это было запрограммировано в не-OO? [закрыто]

11

Читая ужасную статью о минусах ООП в пользу какой- то другой парадигмы, я натолкнулся на пример, в котором я не могу найти слишком много недостатков.

Я хочу быть открытым для аргументов автора, и хотя я теоретически могу понять их аргументы, в частности, один пример мне трудно представить, как это будет лучше реализовано, скажем, на языке FP.

От: http://www.smashcompany.com/technology/object-oriented-programming-is-an-exорого-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.

Обратите внимание, что я не ищу опровержения за или против аргументов автора для «Есть ли какая-либо рациональная причина, почему эти 3 поведения должны быть связаны с иерархией данных?».

Я специально спрашиваю, как этот пример будет смоделирован / запрограммирован на языке FP (Фактический код, а не теоретически)?

Данни Ярославский
источник
44
Вы не можете разумно ожидать сравнения каких-либо парадигм программирования на таких коротких и надуманных примерах. Любой здесь может предложить требования к коду, которые заставят его собственную предпочтительную парадигму выглядеть лучше, чем остальные, особенно если они неправильно реализуют другую. Только когда у вас есть реальный, большой, изменяющийся проект, вы сможете понять сильные и слабые стороны различных парадигм.
Euphoric
20
В ОО-программировании нет ничего, что требовало бы, чтобы эти 3 метода были вместе в одном классе; Точно так же нет ничего в ОО-программировании, которое предписывает, чтобы поведение существовало в том же классе, что и данные. То есть, с помощью OO-программирования вы можете поместить данные в один класс с поведением или разделить их на отдельный объект / модель. В любом случае ОО не имеет ничего общего с тем, как данные должны относиться к объекту, поскольку концепция объекта в основном связана с моделированием поведения путем группировки логически связанных методов в классе.
Бен Коттрелл
20
Я получил 10 предложений в эту напыщенную статью и сдался. Не обращай внимания на человека за этой занавеской. Из других новостей я понятия не имел, что настоящие шотландцы были в основном программистами ООП.
Роберт Харви
11
Еще одна напыщенная речь от того, кто пишет процедурный код на языке ОО, а затем задается вопросом, почему ОО не работает для него.
TheCatWhisperer
11
Хотя несомненно, что ООП - это катастрофа ошибок проектирования от начала до конца - и я горжусь, что являюсь частью этого! - эта статья не читаема, и пример, который вы приводите, приводит аргумент, что плохо спроектированная иерархия классов плохо спроектирована.
Эрик Липперт

Ответы:

42

В стиле FP Productэто был бы неизменный класс, product.setPriceкоторый не изменял бы Productобъект, а вместо этого возвращал новый объект, а increasePriceфункция была бы «автономной» функцией. Используя похожий синтаксис, похожий на ваш (C # / Java как), эквивалентная функция может выглядеть так:

 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;
}

Как видите, ядро ​​здесь не сильно отличается, за исключением того, что «шаблонный» код из надуманного примера ООП опущен. Однако я не рассматриваю это как доказательство того, что ООП приводит к раздутому коду, только как доказательство того факта, что если создать пример кода, который является достаточно искусственным, то можно доказать что угодно.

Док Браун
источник
7
Способы сделать это «больше FP»: 1) Используйте типы Maybe / Optional вместо Nullability, чтобы упростить написание итоговых функций вместо частичных функций, и используйте вспомогательные функции более высокого порядка для абстрагирования «if (x! = Null)» логика. 2) Используйте линзы для определения возрастающей цены на один продукт с точки зрения применения процентного увеличения в контексте линзы к цене продукта. 3) Используйте частичное приложение / композицию / карри, чтобы избежать явной лямбды для карты / вызова Select.
Джек,
6
Должен сказать, что я ненавижу идею, что коллекция могла бы быть нулевой, а не просто пустой по дизайну. Функциональные языки с собственной поддержкой кортежей / коллекций работают именно так. Даже в ООП я ненавижу возвращаться, nullгде коллекция является типом возврата. / разглагольствования
Берин Лорич
Но это может быть статический метод, как в служебном классе в языках ООП, таких как Java или C #. Этот код короче отчасти потому, что вы просите пройти в список, а не держать его самостоятельно. Исходный код также содержит структуру данных, и простое ее перемещение сделает исходный код короче без изменения концепций.
Марк
@Mark: конечно, и я думаю, что ОП уже знает это. Я понимаю вопрос как «как выразить это функционально», не обязательно на языке, отличном от ООП.
Док Браун
@Mark FP и OO не исключают друг друга.
Питер Б
17

Я специально спрашиваю, как этот пример будет смоделирован / запрограммирован на языке FP (Фактический код, а не теоретически)?

На языке "FP"? Если что-то достаточно, я выбираю Emacs lisp. У него есть понятие типов (типа, типа), но только встроенные. Таким образом, ваш пример сводится к тому, «как вы умножаете каждый элемент в списке на что-то и возвращаете новый список».

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

Вот и ты. Другие языки будут похожи, с той разницей, что вы получаете преимущество явных типов с обычной функциональной "совпадающей" семантикой. Проверьте Haskell:

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

(Или что-то в этом роде ...)

Я хочу быть открытым для аргументов автора,

Зачем? Я пытался прочитать статью; Мне пришлось сдаться после страницы и просто быстро отсканировать остальные.

Проблема статьи не в том, что она против ООП. Я тоже не слепо "про ООП". Я программировал с помощью логических, функциональных и ООП-парадигм, довольно часто, когда это возможно, на одном языке и часто без какой-либо из трех, чисто императивной или даже на уровне ассемблера. Я бы никогда не сказал, что любая из этих парадигм значительно превосходит другую во всех аспектах. Могу ли я утверждать, что мне нравится язык X лучше, чем Y? Конечно я буду! Но это не то, о чем эта статья.

Проблема статьи заключается в том, что он использует множество инструментов риторики (ошибок) от первого до последнего предложения. Совершенно бесполезно даже начинать описывать все ошибки, которые в нем содержатся. Автор совершенно ясно дает понять, что у него нулевой интерес к дискуссии, он в крестовом походе. Так зачем?

В конце концов, все эти вещи - всего лишь инструменты для выполнения работы. Могут быть задания, где ООП лучше, и могут быть другие задания, где ПП лучше, или когда оба излишни. Важно выбрать правильный инструмент для работы и выполнить его.

Anoe
источник
4
«Совершенно очевидно, что у него нулевой интерес к дискуссии, он в крестовом походе». Возьмите голос за этот драгоценный камень.
Euphoric
Вам не нужно ограничение Num для вашего кода на Haskell? как вы можете позвонить (*) в противном случае?
JK.
@jk., я целую вечность играл на Haskell, просто чтобы удовлетворить ограничение ОП для ответа, который он ищет. ;) Если кто-то хочет исправить мой код, не стесняйтесь. Но конечно, я переключу его на Num.
AnoE
7

Автор высказал очень хорошую мысль, а затем выбрал более призрачный пример, чтобы попытаться его подтвердить. Жалоба не на реализацию класса, а на идею, что иерархия данных неразрывно связана с иерархией функций.

Из этого следует, что, чтобы понять точку зрения автора, было бы не просто увидеть, как он реализует этот единственный класс в функциональном стиле. Вы должны увидеть, как он будет проектировать весь контекст данных и функций вокруг этого класса в функциональном стиле.

Подумайте о потенциальных типах данных, связанных с продуктами и ценами. Для мозгового штурма несколько: имя, код upc, категория, вес отправления, цена, валюта, код скидки, правило скидки.

Это легкая часть объектно-ориентированного дизайна. Мы просто создаем класс для всех вышеперечисленных «объектов», и мы хороши, верно? Сделать Productкласс, чтобы объединить несколько из них вместе?

Но подождите, у вас могут быть коллекции и агрегаты некоторых из этих типов: Set [категория], (код скидки -> цена), (количество -> сумма скидки) и так далее. Где они вписываются? Создаем ли мы отдельный, CategoryManagerчтобы отслеживать все различные категории, или эта ответственность принадлежит Categoryклассу, который мы уже создали?

Теперь о функциях, которые дают вам ценовую скидку, если у вас есть определенное количество предметов из двух разных категорий? Это относится к Productклассу, Categoryклассу, DiscountRuleклассу, CategoryManagerклассу, или нам нужно что-то новое? Вот как мы в конечном итоге с такими вещами, как DiscountRuleProductCategoryFactoryBuilder.

В функциональном коде ваша иерархия данных полностью ортогональна вашим функциям. Вы можете сортировать свои функции любым способом, который имеет смысл. Например, вы можете сгруппировать все функции, которые изменяют цены продуктов, и в этом случае имеет смысл выделить общие функции, как mapPricesв следующем примере 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)

Я мог бы добавить другие функции, связанные с ценой, например decreasePrice, applyBulkDiscountи т. Д.

Поскольку мы также используем коллекцию Products, версия ООП должна включать методы для управления этой коллекцией, но вы не хотели, чтобы этот модуль был посвящен выбору продукта, вы хотели, чтобы он был о ценах. Связь функции с данными вынудила вас также добавить сюда шаблон управления сбором.

Вы можете попытаться решить эту проблему, поместив productsчлен в отдельный класс, но тогда вы получите очень тесно связанные классы. ОО-программисты считают связывание функций с данными очень естественным и даже выгодным, но это приводит к высокой стоимости, связанной с потерей гибкости. Каждый раз, когда вы создаете функцию, вы должны назначить ее одному и только одному классу. В любое время, когда вы хотите использовать функцию, вы должны найти способ доставить связанные данные к месту использования. Эти ограничения огромны.

Карл Билефельдт
источник
2

Простое разделение данных и функций, на которые ссылался автор, может выглядеть следующим образом в F # («язык 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 }

Таким способом вы можете повысить цену на список товаров.

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

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

Примечание. Если вы не знакомы с FP, каждая функция возвращает значение. Исходя из C-подобного языка, вы можете рассматривать последний оператор в функции, как если бы он был returnперед ним.

Я включил некоторые аннотации типов, но они должны быть ненужными. getter / setter здесь не нужны, так как модуль не владеет данными. Он владеет структурой данных и доступными операциями. Это можно увидеть и с помощью List, которая mapпозволяет запускать функцию для каждого элемента в списке и возвращает результат в новом списке.

Обратите внимание, что модуль Product не должен ничего знать о зацикливании, поскольку эта ответственность остается за модулем List (который создал необходимость в зацикливании).

Кейси Спикман
источник
1

Позвольте мне предвосхитить это тем фактом, что я не специалист по функциональному программированию. Я больше ООП человек. Так что, хотя я почти уверен, что ниже приведен пример того, как вы могли бы реализовать такую ​​же функциональность с FP, я могу ошибаться.

Это In Typescript (отсюда и все аннотации типов). Typescript (например, javascript) является многодоменным языком.

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 );

Подробно (и опять же, не эксперт по FP), нужно понимать, что здесь не так много предопределенного поведения. Не существует метода «повышения цены», который бы применял повышение цены по всему списку, потому что, конечно, это не ООП: нет класса, в котором можно было бы определить такое поведение. Вместо создания объекта, в котором хранится список товаров, вы просто создаете массив товаров. Затем вы можете использовать стандартные процедуры FP для манипулирования этим массивом любым удобным для вас способом: фильтр для выбора определенных элементов, сопоставление для настройки внутренних элементов и т. Д. Вы получаете более подробный контроль над списком продуктов, не ограничиваясь API, который предоставляет вам SimpleProductManager. Некоторые могут считать это преимуществом. Также верно, что вы не Не нужно беспокоиться о багаже, связанном с классом ProductManager. Наконец, не стоит беспокоиться о «SetProducts» или «GetProducts», потому что нет объектов, скрывающих ваши продукты: вместо этого у вас есть только список продуктов, с которыми вы работаете. Опять же, это может быть преимуществом или недостатком в зависимости от обстоятельств / человека, с которым вы разговариваете. Кроме того, очевидно, что нет иерархии классов (на что он жаловался), потому что в первую очередь нет классов. это может быть преимуществом или недостатком в зависимости от обстоятельств / человека, с которым вы разговариваете. Кроме того, очевидно, что нет иерархии классов (на что он жаловался), потому что в первую очередь нет классов. это может быть преимуществом или недостатком в зависимости от обстоятельств / человека, с которым вы разговариваете. Кроме того, очевидно, что нет иерархии классов (на что он жаловался), потому что в первую очередь нет классов.

Я не нашел время, чтобы прочитать всю его напыщенную речь. Я использую практики FP, когда это удобно, но я определенно больше похож на ООП. Так что я решил, что, так как я ответил на ваш вопрос, я бы также сделал несколько кратких комментариев по поводу его мнения. Я думаю, что это очень надуманный пример, который подчеркивает "недостатки" ООП. В данном конкретном случае для показанной функциональности ООП, вероятно, является чрезмерным, и ФП, вероятно, подойдет лучше. Опять же, если бы это было для чего-то вроде корзины для покупок, защита списка ваших товаров и ограничение доступа к нему - это (я думаю) очень важная цель программы, и у FP нет никакого способа обеспечить такие вещи. Опять же, возможно, я просто не эксперт по FP, но, внедрив корзины для систем электронной коммерции, я бы предпочел использовать ООП, а не FP.

Лично мне трудно воспринимать кого-либо всерьез, который приводит такой веский аргумент в пользу «X просто ужасен. Всегда используйте Y». Программирование имеет множество инструментов и парадигм, потому что существует множество проблем, которые необходимо решить. У FP есть свое место, у OOP есть свое место, и никто не станет великим программистом, если они не смогут понять недостатки и преимущества всех наших инструментов и когда их использовать.

** примечание: очевидно, в моем примере есть один класс: класс Product. В этом случае, хотя это просто тупой контейнер данных: я не думаю, что его использование нарушает принципы FP. Это скорее помощник для проверки типов.

** примечание: я не помню, как на макушке головы, и не проверял, изменит ли я то, как я использовал функцию карты, изменение продуктов на месте, т. е. случайно ли я удвоил цену продуктов в исходных продуктах массив. Это, очевидно, тот побочный эффект, которого FP пытается избежать, и с немного большим количеством кода я, безусловно, смог бы убедиться, что этого не произойдет.

Конор Манконе
источник
2
Это на самом деле не пример ООП, в классическом смысле. В истинном ООП данные будут сочетаться с поведением; здесь вы разделили два. Это не обязательно плохо (на самом деле я нахожу это чище), но это не то, что я бы назвал классическим ООП.
Роберт Харви
0

Мне не кажется, что SimpleProductManager является дочерним (расширяет или наследует) чего-то.

Это просто реализация интерфейса ProductManager, который по сути является контрактом, определяющим, какие действия (поведение) должен выполнять объект.

Если это будет дочерний элемент (или, точнее, наследуемый класс или класс, расширяющий функциональность другого класса), он будет записан как:

class SimpleProductManager extends ProductManager {
    ...
}

В общем, автор говорит:

Есть объект, поведение которого: setProducts, incrementPrice, getProducts. И нам все равно, имеет ли объект другое поведение или как это поведение реализовано.

Класс SimpleProductManager реализует это. По сути, он выполняет действия.

Его также можно назвать PercentagePriceIncreaser, так как его основное поведение заключается в увеличении цены на некоторое процентное значение.

Но мы также можем реализовать другой класс: ValuePriceIncreaser, поведение которого будет:

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

С внешней точки зрения ничего не изменилось, интерфейс тот же, все еще есть те же три метода, но поведение другое.

Поскольку в FP нет такой вещи, как интерфейсы, это было бы трудно реализовать. Например, в C мы можем хранить указатели на функции и вызывать соответствующие в зависимости от наших потребностей. В конце концов, в ООП он работает очень похожим образом, но его «автоматизирует» компилятор.

Fis
источник