Pull to refresh

Comments 90

Ну в смысле методов доски. Доска просто фигуры держит на себе.

Ходы должен производить отдельный объект. А глобально игру (выигрыш, проигрыш) поддерживать другой, запрашивать ничью третий. И не будет никаких супер классов и в шашки можно будет легко сыграть.

Пример так себе. Проблема всех функциональных статей имхо - слишком вырожденные примеры.

Вот именно иллюзия, что можно легко унаследовать шашки от шахмат, и составляет проблему OOD.

Зачем класс доска? Что он делает? Зачем шесть классов фигур?

Ну а как бы вы объектно декомпозировали шахматы?

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

class AbstractFigure

sealed class ChessFigure<TChessMoveData> : AbstractFigure where TChessMoveData : struct, IChessMoveData

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

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

Вы же, наверное, не видели, как я волка-козу-капусту решал на эрланге?

Если вкратце — я просто порождал процессы на все возможные ходы, а потом убивал те, в процессе осуществления которых кого-нибудь съедают. Такой комбинаторный взрыв :)

Хороший способ для простой игры (и, кстати, тупо формально выводимый функциональным проектированием), но беда в том, что для шахмат мы таким способом не дождёмся результата.

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

Шахматная практика показывает, что всё равно по-простому всё не сделать.

У Каспарова была знаменитая партия с Дип Блю, когда Дип Блю выиграл, опередив Каспарова на полхода (то есть ответным Каспаров поставил бы мат, на что и рассчитывал). Ясно, что такое положение дел требует точнейшего расчёта с самого начала, а не просто приблизительной оценки.

Ну да, но ведь не обязательно сразу ставить перед собой цель обыграть Каспарова.

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

Скорее всего — да — не получится.

Но 99% задач в нашем уютном мирке — это обыграть соседа с первым юношеским :)

Причём в чапаева :)

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

Зачем Вам "дженерики"? Каждая фигура — это (m,n)-"конь"; а ещё фигуры отличаются "продолжаемостью": конь, король и пешка просто ходят на (m,n), и всё, а слон, ладья и ферзь двигаются до конца доски. То есть: достаточно одного простого класса и двух полей: пара (m,n)и признак "продолжаемости". При каждой фиксированой позиции автоматически вычисляется набор достижимых полей каждой фигурой. При перемещении фигуры производится пересчёт.

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

Но! Если мы говорим об ИИ и о построении какой-либо игровой стратегии, то вопрос реализации самого игрового движка становится несущественным, и его решение ничего нам не даёт в плане реализации стратегии.

пешка просто ходят на (m,n), и всё

Это верно только для игры без фигур другого цвета.

Я бы начал с чего-то вот такого пожалуй:

public class UnitTest1
{
    [Fact]
    public void Test1()
    {
        ChessGame game = new ChessGame();

        MightyPlayer player = new MightyPlayer(game);

        Move move = player.getNextMove();

        Piece piece = move.getPiece();

        Assert.Equals(Piece.Pawn, piece.getName());
        Assert.Equals(Color.White, piece.getColor());

        Square squareFrom = move.getSquareFrom();

        Assert.Equals(LetterAxis.E, squareFrom.getLetter());
        Assert.Equals(NumericAxis.2, squareFrom.getNumber());

        Square squareTo = move.getSquareTo();

        Assert.Equals(LetterAxis.E, squareTo.getLetter());
        Assert.Equals(NumericAxis.4, squareTo.getNumber());

    }
}

Assert.Equals(LetterAxis.E, squareFrom.getLetter()); Assert.Equals(NumericAxis.2, squareFrom.getNumber());

Как у Вас много всего прямо в коде прописывается. Хотелось бы разделить внутреннюю логику (где есть только числа, координаты) и представление (где есть, например, буквы). А начать хотелось бы с базовой логики, когда есть состояния и есть переходы из одного состояния в другое.

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

Но нет ничего невероятного, то в шахматах, написанных с использованием ООП, будут:

  • примитивный класс хранения состояния игры (доска), просто сохраняет инварианты, к примеру, не позволяет поставить две фигуры на одну клетку.

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

  • Наверняка есть какие-то вспомогательные абстракции - например, оценка позиции. И наверняка их методики бывают разные, и у них есть состояние (например, кэш предыдущих рассуждений), поэтому они кандидаты на общий интерфейс и реализация в классах.

  • и, допустим, самый корневой класс, принимающий решение за игрока, строящий логику выбора следующего хода. Он через DI принимает предыдущие.

  • ну и так далее, нужна постановка задачи, это всё вольная фантазия, дело не в моих выдумках, а в том...

А в том, что вы сами выдумали себе какой-то свой ужасный ООП, в котором программисты моделируют не объекты предметной области, а объекты материального мира: класс для доски, потом класс для клетки, для белого и чёрного цвета, для пешек и ладей - и теперь с этим успешно боретесь.

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

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

К этому, по крайней мере, есть формальное основание в виде OOD принципа повторного использования кода.

нужна постановка задачи

Чего принципиально не хватает в постановке задачи "написать программу, выигрывающую в шахматы"?

Чего принципиально не хватает в постановке задачи "написать программу, выигрывающую в шахматы"?

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

И такая постановка позволит лучше декомпозировать решение. В том числе и в ООП-парадигме, почему нет.

А если предположить, что наш шахматный движок имеет какую-то степень абстракции и позволит дёшево реализовывать другие игры со схожим флоу, то ООП станет основной практикой. Реализовал, грубо говоря, интерфейс "правила игры" и "оценка позиции" для шашек - и ура, пользуйся, все эти доски и алгоритмы принятия решения будут автоматически переиспользованы.

Короче, ничего ужасного в уместном применении ООП нет. Если с ума не сходить.

Если вы знаете хорошие практики, то вам вообще не нужно проектирование, как таковое - можно сразу начинать с разработки.

Это, кстати, ещё один момент из общеинженерного знания, о котором обычно не пишут ITшные инфоцыгане.

В том числе и в ООП-парадигме, почему нет.

ООП-парадигма слишком уж плохо себя чувствует при совершении действия «fork», а в шахматах придётся форкаться, причем очень интенсивно.

... "написать программу, выигрывающую в шахматы ...

Шахматный движок можно сделать разными способами. Но эти способы не имеют никакого отношения к решению Вашей задачи. Какой способ реализации выбрали, так и его и представили для реализации уже ИИ-автомата. В рамках Вашей задачи было бы уместно обсуждать формат хранения информации о текущих позициях и о последовательности ходов (для оптимального в вычислительном отношении перебора вариантов). Ещё, с точки зрения ИИ, было бы полезно рассмотреть вопрос инвариантов: ведь. понятно, что некоторые шахматные позиции отличаются друг от друга только перестановкой строк и столбцов. Другими словами, здесь нужен переход в конфигурационное пространство, где учитывается только взаимное расположение фигур, а не их абсолютное положение. Думаю, что в профессиональных движках так и сделано.

Шахматный движок можно сделать разными способами. Но эти способы не имеют никакого отношения к решению Вашей задачи

Это и есть решение задачи.

Когда я говорю о программе для игры в шахматы, то, естественно, имею в виду сам код игры, а не интерфейс готового движка.

Зачем мне наследовать шашки от шахмат? Я оставлю доску, оставлю даже фигуры. Просто подменю "контроллер игры". Который будет игнорировать "тип" фигуры и который я действительно унаследую от базового контроллера игры.

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

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

Возьмите функцию, вычисляющую факториал натурального числа. Традиционное императивное программирование требует некоторой законченной вычислительной конструкции. Да, можно, устроить цикл. Да, можно и рекурсию применить. Также можно вспомнить и про формулу Стирлинга (для больших значений входного аргумента). Но, когда мы смотрим на факториал с другой стороны, с точки зрения функционального программирования, мы видим за каждой вычисляемой величиной процесс.

Мало того, что мы буквально всё воспринимаем как функцию, то есть — отображение из одного множества значений в другое, мы и самоё вычисление (выбор) искомого значения воспринимаем как процесс. Можно представить себе нить, в которой последовательно формируются произведения чисел натурального ряда. А ещё и процесс постановки, в том числе, символической (где, собственно, лямбда-исчисление и возникает)!

Чтобы встать на функциональную точку зрения, надо представить, что каждый объект — это некоторое отображение. У этого взгляда на вещи ест две стороны. Пассивная часть заключается в том, что объект всегда представляется своим значением. А активная часть заключается в том, что с каждым объектом связано некоторое вычисление, то есть — некоторый программный код, который может быть выполнен.

Ближе всего к этой концепции находятся клеточные автоматы и игра "Жизнь".

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

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

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

Ну да. У него нет стратегии, только тактика в виде элементов контента и их методов. Скажешь ему нарисовать кнопку - он рисует кнопку. Не его дело, зачем.

Можно сказать, что семантика браузера задана чисто операционно.

Ну, можно, конечно, сказать, что элементом стратегии браузера является блокирование всплывающих окон, но это мелочи по отношению к общему объёму его работы.

Что, значит, стратегия? В каком смысле?

В традиционном смысле: общая цель действий программы от старта до финиша. Ни один интерфейс по определению не имеет стратегии.

Тут, скорее, проблема отсутствия старта и финиша: где — старт и где — финиш? А так... Один что-то предъявляет, другой должен без ошибок дойти до... финиша (каждой отдельной процедуры). Как-то так.

Без ошибок дойти до финиша – это тактика. А на уровне спецификации – операционная семантика.

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

  1. Автоматически делать всё, что можно сделать автоматически

  2. Если всё что можно сделать автоматически уже сделано, нужно спросить у пользователя что-то, что позволит сделать автоматически еще какую-нибудь часть работы

То есть, например, библиотекарь заполняет карточки на новые книги. Интерфейс не знает, что за очередная книга лежит перед библиотекарем, а по ней надо заполнить с десяток значений – название, автора и пр. Идеальный интерфейс на этом этапе должен спросить главное – ISBN книги, после чего полезть в базу данных ISBN и скачать всё, что об этой книге известно, заполнив по возможности все поля. Если каких-то полей не хватает, например, не понятно, в какой шкаф на какую полку нужно поставить книгу, интерфейс должен свериться со списком шкафов, выбрать подходящий и спросить, на какой полке есть место для этой книги. Ну и так далее.

Или, например, приходит покупатель в интернет-магазин за продуктами. Идеальный интерфейс должен сперва опознать пользователя (по имени и паролю, например) и найти историю его покупок. Если он всегда покупает молоко, причем предпочитает определенную марку, например, сразу же спросить – добавить молоко такой-то марки в корзину? Если он положил в корзину кочан капусты, сразу же предложить ему картошку, морковку, свеклу, мясо на косточке и рецепт борща. Нет? Тогда ингредиенты для рагу.

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

В ООП берем объект "суп", который состоит из свойств "ингридиенты супа" и методов - способ его приготовления. Берем ФП есть функции "нагреть", "перемешать", "добавить", "вылить в унитаз". Отсюда простой вывод в ООП вы берете объект и пользуетсь. В ФП вы должны знать все, как пользоваться, в каком порядке выполнять действия и так далее. Иными словами, чтобы воспользоваться трудами ФП, вы создаете ООП оболочку над ним и пользуетесь

В вашей программе вся логика фактически находится в методе "сварить" суперкласса "суп". Поэтому с ООП тут не очень.

Более тонкой проблемой является то, что в таком случае надо полностью определиться с рецептом до начала компиляции супа (или, во всяком случае, до начала варки).

суперкласс - это "блюдо" и абстрактный метод "приготовить"; дочерние классы знают как себя приготовить

В задаче приготовления конкретного супа абстрактный класс вам не поможет никак.

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

А зачем? Чем это лучше, чем просто функция cook_soup, которая знает, как приготовить суп?

аха, а данные для этой функции кому оставите? их надо где-то хранить?

Я не понял вопрос. В данные в экземпляре класса «Блюдо» — самозарождаются из осмоса?

Чем cook_soup(ingredients) отличается от Soup.new(ingredients).cook?

Мои пять копеек. А что если парадигмы это такая же абстракция как и язык программирования. Если у нас так много языков, значит есть свои задачи. ООП это про контекст выполнения инструкций. Функциональщина это последовательность инструкций. Есть подходы где рулит функциональность, контекст у всей программы общий (змейка шахматы и ТД). Есть ООП, где важна изоляция данных, в плане читаемости. Можно конечно передавать в функции сам контекст (в виде структуры например ) но тогда чем это отличается от ООП с this, self etc.

Мимо вообще не прогер

На мой взгляд, это разумная мысль, которая перекликается с написанным в статье.

ООП, где важна изоляция данных

ООП декларирует изоляцию, но не гарантирует и не обеспечивает её.

При всём уважении к автору, я ничего не понял.

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

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

А где они ООП-языки? SmallTalk? Simula? Oberon?

ООП это любые языки где есть мутируемые сущности для которых верно

not equal (a, b) => not (a == b)

Если два объекта могут иметь одинаковые свойства, но всё равно не равны (из-за уникальной идентичности), то это сущности (ООП), а не value object.

Также добавим

• поведение упаковано вместе с данными;

• вызов идёт через позднее связывание (или хотя бы таблицу), т.е. метод выбирается по типу объекта

Извиняюсь, формула не верная.

Объект имеет идентичность значит, что может существовать два объекта одинаковых по содержимому, но разной идентичностью, то есть разных.

Итак, ООП:

  1. Идентичность объектов

  2. Данные и методы в одном объекте

  3. Позднее связывание, то есть runtime выбор метода

Остальное (более строгая инкапсуляция, более строгое определение полиморфизма, наследование) это необязательно.

Даже 2й пункт не обязателен на примере rust.

Минимум в ООП:

  1. Моделируются объекты долгим жизненным циклом, но сохраняющим идентичность. И тут четкая граница с ФП.

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

Кстати, из первого утверждения прямо следует что объекты передаются по ссылке, ссылка и является идентичностью, различимостью даже если содержание одинаково. А структуры передаются по значению.

В случае rust используется идентичность ссылки на структуру и система владения.

Полиморфизм никак не связан с ООП.

Хорошо.

Динамическая диспетчеризация, позднее связывание. То есть один из вариантов полиморфизма, обеспечивающий совместимость с объектом заранее неизвестного типа.

Без неё не бывает ООП.

Без неё нельзя запечатать библиотеки, чтобы не менять их при добавлении нового функционала.

Этого в любом динамическом языке наливают в неограниченном количестве. И если на то пошло, то впервые это появилось в Лиспе.

В отношении же equals соглашусь с Вами.

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

Но полиморфизм, позднее связывание и динамическая диспетчеризация (которая является прямым следствием позднего связывания) есть в Лиспе ещё с тех пор, когда никакого ООП не было. Да и в каком-нибудь Питоне это всё работает совершенно независимо от ООП.

Голова пухнет уже

... А значит и сущностей тоже нет

"Нет в мире совершенства." (с)

Любой список и есть сущность. Представьте себе, например, правостороннее дерево: Вы берёте, каждый раз очередной элемент (левый лист), и всегда что-то ещё остаётся (правое поддерево):

(Олег (знает (Python)))

При этом, частичное поддерево выражает собою целое понятие:

(Олег (знает (*)))

Дерево показывает структуру объекта и, вместе с этим, его идентичность. Расставьте иначе скобки — и Вы получите совершенно другой объект.

И кстати, дихотомия: два способа использования проверенных библиотек, а значит закрытых для изменений

• «Функциональное» (ADT-, pattern-matching-, data-oriented) проектирование

– данные закрыты (все возможные случаи надо сразу описать в библиотеке),

– функции открыты (легко писать новые обработки, но старые ранее проверенные функции менять запрещено).

• «Объектно-ориентированное» (подтипный полиморфизм, сообщения)

– данные открыты (можно подсовывать объекты неизвестных заранее классов и так расширять ранее проверенный функционал),

– набор сообщений должен быть согласован (интерфейс/протокол известен).

Это — знаменитая «Expression Problem»

Полиморфизм никак не связан с ООП.

Что же Вы понимаете под полиморфизомом?

Применение одной операции (функции) к данным разных типов.

В принципе, в самой примитивной форме полиморфизм идёт с первого языка высокого уровня - Фортрана, где одним и тем же, например, плюсом, можно складывать вещественные и целые числа. Но в полном виде полиморфизм, конечно, включает возможность самостоятельного написания таких функций. Что тоже, впрочем, применялось очень давно, просто потому, что это удобно.

А блин, снова соврал. Столько разного придумали, что границы понять трудно.

Трайты позволяют сохранить логическую идентичность сущности, при этом ее ссылочная идентичность меняется при каждом "изменении" состояния

Объект имеет идентичность значит, что может существовать два объекта одинаковых по содержимому, но разной идентичностью, то есть разных.

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

Здесь утверждается, что у объекта имеется некоторое содержание/содержимое, но есть и некий идентификатор, который действует отдельно от содержания. Грубо говоря, у каждого объекта есть некоторые данные (поле data или contents) и метод id (возвращающий уникальный идентификатор объекта).

Причём поле id нельзя копировать в другой объект (так как на практике это не поле, а просто машинный адрес).

Да, обычно идентификатор это ссылка на объект. Но в теории это не обязательно.

Ещё раз: объект живёт. Он имеет жизненный цикл: создание, изменение, удаление. Он может меняться (хотя не обязательно). Чтобы когда он меняется мы могли его отличить от других ему нужен идентификатор. И мы можем сказать, что он не какой-то объект, а тот самый.

Итак устроены наблюдаемые в жизни явления.

Но мы можем остановить время. И для неизменяемых штук (боюсь что все термины уже заняты) мы можем описать правила, как в математике. Это ФП.

Ну и есть ещё смешанные штуки. Rust traits это вроде не объекты, но идентичность имеют через принцип единственной мутирующей ссылки (систему владения)

Ну и во всяких эзотерических языках ещё и более странное бывает

Объект имеет идентичность ...

В реальной жизни мы ожидаем, что содержимое полностью определяет идентичность. В программировании, это означает, что, как раз, нельзя создавать два одинаковых по своему содержанию объекта, имеющих один и тот же идентификатор. Более того, система идентификации должна напрямую зависеть от этого содержания, ибо самый развёрнутый (полный и точный) идентификатор объекта — это он сам. Другое дело, что в реальной ситуации всё приходится здорово упрощать. Например, идентификатором может стать обыкновенное число или какая-нибудь составная строка. В этом случае, (не)равенство объектов полностью эквивалентно (не)равенству идентификаторов.

В то же время, есть объекты не как некие сущности (имеющие) соответствие в реальном мире, а как вычислительные объекты (то есть — помеченные фрагменты памяти). Например, цело число, строка, список, кортеж или словарь. Здесь мы просто обязаны следить за уникальностью таких объектов, и мы будем помечать различные фрагменты памяти различными идентификаторами. Мы можем создать два объекта-числа, в которых хранится одно и то же значение.

Нам следует очень аккуратно рассуждать о различных объектах, понимая, что есть объекты-сущности и объекты-реализации. Здесь всегда возникает смешение. Тут и, вправду, много похожего. В том-то и трудность, что мы легко подменяем понятия. Так что, объекту как понятию легко потерять свою идентичность. :-)

Нет. В жизни тоже сколько угодно клонов, одинаковых по содержанию. Но в разном месте / контексте. Но контекст это не свойство объекта, а то куда мы его поместили.

Философией пахнуло

ООП оно такое, да. Чуть копнуть глубже и полезет из всех щелей.

Условно: треугольник равен эквивалентен другому с точностью до смещения и поворота если 3 его стороны равны сторонам другого. а=а', b=b', c=c'

ООП это любые языки где есть мутируемые сущности для которых верно

Не сочтите за троллинг замечание. Просто, давайте будет говорить по-русски. Пусть у нас есть простой список в Python и нет ничего другого. Нам же никто не мешает данные хранить в простом списке, не так ли? Список изменяемый. Но имеется проблема с ссылками: когда мы пытаемся скопировать список, копируются ссылки. Допустим, у мы пользуемся только глубоким копированием. Как тогда интерпретировать Ваше соотношение:

not equal (a, b) => not (a == b)

?

Если два объекта могут иметь одинаковые свойства, но всё равно не равны (из-за уникальной идентичности), то это сущности (ООП), а не value object.

Вот видите, Вы сразу говорите о каких-то свойствах. Значит, список — это value object?

поведение упаковано вместе с данными

Насколько это принципиально? Будем, например, использовать для хранения данных обыкновенные списки и кортежи. (Есть ещё и словари, которые уже очень близки к объектам.) А для обработки написать ряд функций. Да, это всё будет раздельно. И что? Плохо? А если это хорошо решает задачу?

вызов идёт через позднее связывание (или хотя бы таблицу), т.е. метод выбирается по типу объекта

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

Мой вопрос был немного в другом. Я привык к тому, что, например, язык программирования C++ — это язык, в котором были введены (при сохранении синтаксиса Си) специальные конструкции, обеспечивающие работу с классами. Именно поэтому, такие языки оказываются, лишь, объектно-ориентированными. А есть объектные языки, где объекты (классы) реализованы в собственном смысле этого слова. Я, лишь, наслышан о таких языках программирования. В руках не держал. Даже, жалко, что не держал. Но Вы же понимаете, всё время возникает вопрос: а почему мы пользуемся языками программирования, которые являются, лишь, ориентированными на объекты (классы), а не не работаем с самими объектами (классами)?

Чтобы понять различие, надо представить себе ситуацию, когда при исполнении встречаются объекты различных классов. В обычном ООП-языке мы можем предусмотреть специальный класс, который описывает это взаимодействие. Но при попытке учесть всю комбинаторику взаимодействий, нам придётся соорудить немерено таких классов. Целая таблица маршрутизации! Можно сделать. Но, если у нас есть чистые объекты,то при их встрече такой промежуточный класс будет создаваться автоматически.

Например, это может быть класс Список и класс Пользователь: результат их композиции будет класс СписокПользователей. В приложении каждый класс объектов может быть часть некоторого списка. У нас должна быть возможность составления таких списков и управления ими. Композиция классов даёт некоторые специфические вещи для списка объектов определённого класса.

Но Вы же понимаете, всё время возникает вопрос: а почему мы пользуемся языками программирования, которые являются, лишь, ориентированными на объекты (классы), а не не работаем с самими объектами (классами)?

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

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

Не обязательно. В том же Питоне вы можете чисто процедурно присвоить новое значение имени функции:

def f1():
  return 1
def f2():
  return 2
f2=f1
print (f2())

Особого смысла так делать в процедурном стиле нет, но это ничему не противоречит. Это чисто процедурная программа с поздним связыванием, не использующая механизмы ООП.

Вы приводите пример и (опять же!) примешиваете вопросы реализации. Почему можно присвоить одну функцию другой? Потому что это суть переменные, содержащие указатели или ссылки. Можно представить себе язык программирования, в котором нельзя таким образом присваивать значения (друг другу), зато будут специальные объекты (левосторонние, l-value), которые способны принимать ссылки.

Я имел в виду, что в процедурном программировании Вы явно пишите функцию f1 для типа 1, а функцию f2 для типа 2. Но и здесь Вы можете попытаться добиться некоторой гибкости. Другое дело, а насколько эта гибкость нужна.

Я имел в виду, что в процедурном программировании Вы явно пишите функцию f1 для типа 1, а функцию f2 для типа 2.

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

Да да, смейтесь над тем кто утверждает что он что-то знает в ООП. Границы ООП очень размыты.

Лучше я буду говорить что я ничего не знаю в ООП да и нафиг он мне сдался.

Никто не смеётся. Надо разобраться. Наконец-то.

Я знаю только, что ООП очень сложно внутри, скрывая сложность как айсберг. И мотивация зачем нужно то или иное тоже совсем не очевидно.

Я вот писал статью

https://habr.com/ru/articles/902634/

Но на все вопросы, ответить пожалуй так и не смогу. Там где остальные просто не задумаются.

Да и я скорее функциональщик. Стою в сторонке и посмеиваюсь

Мне бы хотелось, что бы решалась задача (например, создание системы). Лучше всего, если задача будет решаться оптимальным способом. Тут ещё вопрос, существует ли этот самый оптимальный способ. Может быть, его нет, и нас всегда поджидает мистическое множество Парето, где куда ни пойди, чего-нибудь обязательно ухудшишь. Нельзя улучшить всё одновременно. Найти бы удачное сочетание подходов.

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

Функционально ориентированное проектирование, основанное на утверждениях, подобных математической логике. Это легковесные утверждения, которые можно привести к высокой степени абстракции.

Старение в ООП моделируется как изменение одной сущности. В ФП как переход из типа "молодой" в тип "красивый" и строгими правилами перехода.

Можно представить себе процесс, в ходе которого постоянно вызываются конструктор и деструктор объекта. Система поддерживает идентичность объекта через шину данных: деструктор производит сохранение объекта в поток, а конструктор считывает его их потока. (Некоторая аналогия с LLM прослеживается...) Птица-фенкис.

Событийная модель?

Ну типа...

Я, конечно, в целом согласен, кроме тут:

И дальше совершенно непонятно, чьим, собственно, методом должна быть стратегия игры.

Как это непонятно? Стратегия — метод объекта «игрок», и у разных игроков будут разные стратегии. Это как раз отлично ложится на ООП.

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

О! С возвращением!

С учётом того, что мы пишем как раз автоматического игрока в шахматы, объект "игрок" в данном случае представляет собой экземпляр суперкласса.

Для автомата нужен алгоритм перебора позиций и выбора наилучшего продолжения. Понятное дело, что нужно как-то описывать доску, положение фигур, текущее состояние. Можно описать "движок" через ООП (а можно и не). Но при чём здесь стратегии?

Шахматный автомат стратегичен, его основное содержание заключается в выборе стратегии игры.

Спасибо :)

С учётом того, что мы пишем как раз автоматического игрока в шахматы, объект «игрок» в данном случае представляет собой экземпляр суперкласса.

Вы мыслите с точки зрения разработчика приложений, я — библиотек. Я создам библиотеку, которая экспортирует игрока, причем разных: абстрактного, натренированного на Алёхине, авантюрного типа Фишера, и так далее.

Ну это просто другая задача.

Владимир, тема очень интересная. Я продвигаю (у себя на работе) концепцию ООА, объектно ориентированного анализа против функционального анализа. С развитием ОО программирования (ООПрг), образовался ОО Подход (ООПдх), который сочетает в себе три этапа, ООА, ОО Проектирование (ООПрк) и ООПрг. В общих словах я понимаю, что такое функциональное программирование и проектирование. Но никогда не сталкивался с методами функционального анализа (может быть только по наитию). Есть ли какие идеи или материалы на этот счёт?

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

На практике это означает, что мы имеем базу, в которой точно уверены, и движемся от неё к более смутно понимаемому решению задачи в целом. На первый взгляд, это противоречит концепции реализации ТЗ. Но в действительности надо вспомнить, что ТЗ на практике обычно пишется исполнителем под свои возможности, а не заказчиком. Поэтому сама концепция анализа требований исходит из некоторой подгонки действительной ситуации под желаемую модель.

Когда я был молод и неопытен, то необходимость решать задачи, не имеющие точной постановки, ставила меня в некоторый тупик, потому что этому практически нигде не учат. Но жизнь постепенно научила определённым подходам.

С тех пор сама проблема хотя бы получила общественное признание и даже некоторые распространённые простейшие решения вроде аджайла.

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

Я не согласен с тем, что объектно-ориентированный подход имеет дело с понятиями реальной жизни. Объекты ООП - это такие же абстракции, как и функции ФП. Просто понятийное пространство выбрано другое.

Если вы применяете ООП, то концентрируетесь на состояниях. Никаких состояний в реальном мире нет, это абстракция, возникающая в нашей голове.

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

Иногда противоречие между этими подходами доходит до максимума. Как, например, волновая функция Шрёдингера прямо постулирует отсутствие состояния. Или в какой-нибудь задаче с неполной информацией у нас может не быть известной функциональной зависимости (например, когда речь идёт о внешнем интерфейсе с человеком). Но чаще всего могут применяться оба подхода.

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

Sign up to leave a comment.

Articles