Какие проблемы процедурного программирования ООП решает на практике?

17

Я изучал книгу "C ++ Demysified" . Теперь я начал читать «Объектно-ориентированное программирование в первом выпуске Turbo C ++ (1-е издание)» Роберта Лафора. У меня нет никаких знаний о программировании, которое выходит за рамки этих книг. Эта книга может быть устаревшей, потому что ей 20 лет. У меня есть последнее издание, я использую старое, потому что оно мне нравится, в основном я просто изучаю основные концепции ООП, используемые в C ++, в первом издании книги Лафора.

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

Честно говоря, я публикую свой вопрос, потому что я не понимаю объяснения, данного в этой книге: « Объектно-ориентированное программирование на C ++ (4-е издание)». Я не понимаю эти утверждения, написанные в книге Лафора:

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

Я изучал книгу Джеффа Кента «Расстроенная С ++», мне эта книга очень нравится, в этой книге объясняется в основном процедурное программирование. Я не понимаю, почему процедурное (структурированное) программирование слабое!

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

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

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

Вот мой вопрос:

Какие ограничения процедурного программирования рассматриваются в ООП и как они эффективно устраняют эти ограничения на практике?

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

PS: Крест отправлено с: /programming//q/22510004/3429430

user31782
источник
3
Различие между процедурным программированием и объектно-ориентированным программированием является в некоторой степени вопросом представления и акцента. Большинство языков, которые рекламируют себя как объектно-ориентированные, также являются процедурными - термины рассматривают различные аспекты языка.
Жиль "ТАК - перестань быть злым"
2
Я снова открыл вопрос; давайте посмотрим, как это происходит. Имейте в виду, что любая обработка, которая представляет ООП, является святым Граалем, мессия языков программирования, решающая все проблемы, является бессмыслицей. Есть плюсы и минусы. Вы спрашиваете о плюсах, и это справедливый вопрос; не ожидай большего
Рафаэль
Мысль о разработке (современного) настольного графического интерфейса без ООП и некоторых методов, которые выросли на нем (например, шаблон событий / слушателей), пугает меня. Однако это не значит, что этого нельзя сделать (это, безусловно, можно ). Также, если вы хотите увидеть сбой PP на работе, посмотрите на PHP и сравните API, скажем, с Ruby.
Рафаэль
1
«Независимо от того, насколько хорошо реализован подход структурированного программирования, большие программы становятся чрезмерно сложными ...», что в значительной степени относится и к ООП, но в основном справляется со сложностью лучше, если применяется разработчиками в установленном порядке ... и делает это в значительной степени через более четкие / более четкие границы / ограничения объема, то есть своего рода систему компартментализации ... иначе APIE: абстракция, полиморфизм, наследование, инкапсуляция
vzn

Ответы:

9

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

Объявление типов является наиболее очевидным декларативным ограничением (т. Е. «Докажите, что x - это число с плавающей точкой»). Заставить мутации данных проходить через функцию, известную как предназначенную для этих данных, - это другое. Применение протокола (метод вызова порядка) является другим частично поддерживаемым ограничением, а именно: «конструктор -> другие методы * -> деструктор».

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

тип x1 является подтипом x, t1 является подтипом t

Это один из способов инкапсулировать данные в процедурном языке, чтобы иметь тип t с методами f и g и подкласс t1, который также выполняет:

t_f (t, x, y, z, ...), t_g (t, x, y, ...) t1_f (t1, x, y, z, ...)

Чтобы использовать этот код как есть, вы должны выполнить проверку типа и включить тип t, прежде чем решить, какой тип f вы будете вызывать. Вы можете обойти это так:

тип t {d: данные f: функция g: функция}

Таким образом, вместо этого вы вызываете tf (x, y, z), где проверка типов и переключение, чтобы найти метод, теперь заменены просто наличием указателей на метод хранения каждого экземпляра в явном виде. Теперь, если у вас есть огромное количество функций для каждого типа, это расточительное представление. Тогда вы могли бы использовать другую стратегию, например, указывать t на переменную m, которая содержит все функции-члены. Если эта функция является частью языка, то вы можете позволить компилятору выяснить, как справиться с созданием эффективного представления этого шаблона.

Но инкапсуляция данных - это признание того, что изменяемое состояние плохое. Объектно-ориентированное решение - скрыть это за методами. В идеале все методы объекта должны иметь четко определенный порядок вызова (т.е. конструктор -> открыть -> [читать | запись] -> закрыть -> уничтожить); который иногда называют «протоколом» (исследование: «Microsoft Singularity»). Но помимо создания и уничтожения, эти требования, как правило, не являются частью системы типов - или хорошо документированы. В этом смысле объекты являются параллельными экземплярами конечных автоматов, которые передаются вызовами методов; так что вы можете иметь несколько экземпляров и использовать их произвольным образом.

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

Сравните вызовы методов Java в многопоточном контексте с процессами Erlang, отправляющими друг другу сообщения (которые ссылаются только на неизменяемые значения).

Неограниченная объектная ориентация в сочетании с параллелизмом является проблемой из-за блокировки. Существуют различные методы: от программной транзакционной памяти (т. Е. Транзакций ACID в объекте памяти, аналогичной базам данных) до использования подхода «разделяемая память посредством обмена данными (неизменяемые данные)» (гибрид функционального программирования).

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

обкрадывать
источник
Вы говорите, что в ОО-языках компилятор может проверить, используются ли методы в установленном порядке или другие ограничения на использование модулей? Почему «инкапсуляция данных [...] признает плохое изменяемое состояние»? Когда вы говорите о полиморфизме, вы предполагаете, что используете язык OO?
Бабу
В ОО наиболее важной возможностью является возможность скрыть структуру данных (т. Е. Требовать, чтобы ссылки были такими, как this.x), чтобы доказать, что все обращения проходят через методы. В языке ОО со статической типизацией вы объявляете еще больше ограничений (в зависимости от типов). В примечании об упорядочении методов просто говорится, что ОО заставляет конструктор вызываться первым, а деструктор последним; что является первым шагом в структурном запрещении плохих заказов вызова. Легкие доказательства - важная цель в языковом дизайне.
Роб
8

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

Некоторые парадигмы программирования развиваются из теоретического видения вычислений. Такой язык, как Лисп, произошел от лямбда-исчисления и идеи мета-циркулярности языков (аналогично рефлексивности в естественном языке). Роговые выражения породили Пролог и ограничение программирования. Семейство Алгол также обязано лямбда-исчислению, но без встроенной рефлексивности.

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

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

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

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

Пример: воссоздание ориентации объекта

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

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

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

Предположим, я хочу создать класс с именем vector, представляющий 2-мерные векторы, с методами, включающими: сложение векторов, размер вектора и параллелизм.

function vectorrec () {  
  function createrec(x,y) { return [x,y] }  
  function xcoordrec(v) { return v[0] }  
  function ycoordrec(v) { return v[1] }  
  function plusrec (u,v) { return [u[0]+v[0], u[1]+v[1]] }  
  function sizerec(v) { return sqrt(v[0]*v[0]+v[1]*v[1]) }  
  function parallelrec(u,v) { return u[0]*v[1]==u[1]*v[0]] }  
  return [createrec, xcoordrec, ycoordrec, plusrec, sizerec, parallelrec]  
  }  

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

[vector, xcoord, ycoord, vplus, vsize, vparallel] = vectorclass ()

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

Мы можем сделать еще одну коллекцию в полярных координатах

function vectorpol () {  
  ...  
  function pluspol (u,v) { ... }  
  function sizepol (v) { return v[0] }  
  ...  
  return [createpol, xcoordpol, ycoordpol, pluspol, sizepol, parallelpol]  
  }  

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

function vector () {  
    ...  
    function plusrec (u,v) { ... }  
    ...  
    function pluspol (u,v) { ... }  
    ...  
    function plus (u,v) { if u[2]='rec' and v[2]='rec'  
                            then return plusrec (u,v) ... }  

    return [ ..., plus, ...]  
    }

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

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

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

Чтобы упростить использование, вместо того, чтобы возвращать список функций, я могу вернуть единственную функцию с именем apply, принимающую в качестве аргумента явно заданное значение и строку, и применить функцию с правильным типом и именем. Это очень похоже на вызов метода для класса OO.

Я остановлюсь здесь, на этой реконструкции объектно-ориентированного объекта.

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

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

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

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

Я считаю, что хорошей книгой для понимания всего этого может быть Abelson & Sussman: структура и интерпретация компьютерных программ .

Babou
источник
8

Немного истории в порядке, я думаю.

Эпоха с середины 1960-х до середины 1970-х годов сегодня известна как «кризис программного обеспечения». Я не могу выразить это лучше, чем Дейкстра в его лекции премии Тьюринга от 1972 года:

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

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

Неудивительно, что это было удивительно плодотворное время в программировании. До середины 1960-х у нас были LISP и AP / L, но «основные» языки были в основном процедурными: FORTRAN, ALGOL, COBOL, PL / I и так далее. С середины 1960-х до середины 1970-х мы получили Logo, Pascal, C, Forth, Smalltalk, Prolog, ML и Modula, и это не считая DSL, таких как SQL и его предшественники.

Это было также время в истории, когда разрабатывались многие ключевые методы для реализации языков программирования. В этот период мы получили анализ LR, анализ потока данных, устранение общих подвыражений и первое признание того, что некоторые проблемы компилятора (например, распределение регистров) были NP-сложными, и пытались решить их как таковые.

Это контекст, в котором возникло ООП. Итак, в начале 1970-х годов ответ на ваш вопрос о том, какие проблемы ООП решает на практике, первый ответ заключается в том, что он, похоже, решает многие проблемы (как современные, так и ожидаемые), с которыми сталкивались программисты в тот период истории. Однако это не время, когда ОО стал мейнстримом. Мы скоро к этому вернемся.

Когда Алан Кей ввел термин «объектно-ориентированный», он имел в виду, что программные системы будут структурированы как биологическая система. У вас будет что-то вроде отдельных ячеек («объектов»), которые взаимодействуют друг с другом, посылая нечто, аналогичное химическим сигналам («сообщениям»). Вы не могли (или, по крайней мере, не хотели бы) заглянуть внутрь клетки; вы будете взаимодействовать с ним только через сигнальные пути. Более того, вы можете иметь более одной ячейки каждого типа, если вам нужно.

Вы можете видеть, что здесь есть несколько важных тем: концепция четко определенного протокола сигнализации (в современной терминологии, интерфейс), концепция сокрытия реализации извне (в современной терминологии, конфиденциальности) и концепция наличие множества «вещей» одного и того же типа, торчащих одновременно (в современной терминологии - создание экземпляров).

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

Объектно-ориентированное программирование является абстрактным понятием, и абстрактное понятие может быть реализовано по-разному в разных языках программирования. Например, абстрактная концепция «метода» может быть реализована в C с использованием указателей функций, а также в C ++ с использованием функций-членов и в Smalltalk с использованием методов (что не должно вызывать удивления, поскольку Smalltalk реализует абстрактную концепцию практически напрямую). Это то, что имеют в виду люди, когда указывают (совершенно правильно), что вы можете «делать» ООП на (почти) любом языке.

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

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

Именно в этом контексте возникли объектно-ориентированный анализ и проектирование.

Влияние OOAD, работы «трех амигос» (Буча, Румбара и Якобсона) и других (например, метод Шлаера – Меллора, дизайн, основанный на ответственности, и т. Д.) Не может быть недооценено. По этой причине большинство новых языков, разработанных с начала 1990-х годов (по крайней мере, большинство из тех, о которых вы слышали), имеют объекты в стиле Simula.

Таким образом, ответ на ваш вопрос 1990-х годов заключается в том, что он поддерживает лучшее (на то время) решение для предметно-ориентированного анализа и методологии проектирования.

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

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

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

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

Псевдоним
источник
Кроме того, что я не хотел включать в основное эссе, это то, что графические интерфейсы WIMP и ООП выглядят очень естественным образом. О плохой иерархии наследования можно сказать много плохого, но это одна ситуация (вероятно, ЕДИНСТВЕННАЯ ситуация), в которой, кажется, есть какой-то смысл.
Псевдоним
1
ООП впервые появился в Simula-67 (симуляция) во внутренней организации операционных систем (идея «класса устройств» в Unix - это, по сути, класс, от которого наследуются драйверы). Парнас: «О критериях, которые будут использоваться при разложении систем на модули» , CACM 15:12 (1972), с. 1052-1058, язык Модула Вирта из семидесятых, «абстрактные типы данных» являются предшественниками в одном или другом смысле. Другой.
vonbrand
Это все верно, но я утверждаю, что ООП не рассматривалось как «решение проблемы процедурного программирования» до середины 70-х годов. Определить «ООП» очень сложно. Первоначальное использование этого слова Аланом Кейем не соответствует модели Симулы, и, к сожалению, мир стандартизировал модель Симулы. Некоторые объектные модели имеют интерпретацию, подобную Карри-Говарду, но модели Симулы - нет. Степанов, вероятно, был прав, когда отметил, что наследование необоснованно.
псевдоним
6

Нет, правда. Строго говоря, ООП не решает проблему; нет ничего, что вы можете сделать с объектно-ориентированной системой, которую вы не могли бы сделать с не объектно-ориентированной системой - на самом деле, вы ничего не можете сделать с этим, что нельзя сделать с помощью машины Тьюринга. В конечном итоге все это превращается в машинный код, и ASM, безусловно, не является объектно-ориентированным.

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

Скажем, я хочу написать карточную игру на Python. Как бы я представлял карты?

Если бы я не знал об ООП, я мог бы сделать это так:

cards=["1S","2S","3S","4S","5S","6S","7S","8S","9S","10S","JS","QS","KS","1H","2H",...,"10C","JC","QC","KC"]

Я бы, вероятно, написал некоторый код для генерации этих карт вместо того, чтобы писать их вручную, но вы понимаете, в чем дело. «1S» представляет 1 из Пиков, «JD» представляет «Бубновый Валет» и так далее. Мне также нужен код для Джокера, но мы просто притворимся, что Джокера пока нет.

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

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

def card_code_to_name(code):
    suit=code[1]

    if suit=="S":
        suit="Spades"
    elif suit=="H"
        suit="Hearts"
    elif suit=="D"
        suit="Diamonds"
    elif suit=="C"
        suit="Clubs"

    value=code[0]

    if value=="J":
        value="Jack"
    elif value="Q":
        value="Queen"
    elif value="K"
        value="King"

    return value+" of "+suit

Немного большой, длинный и неэффективный, но он работает (и очень непитонно, но здесь дело не в этом).

А что если я хочу, чтобы карты могли перемещаться по экрану? Я должен как-то хранить свою позицию. Я мог бы добавить его в конец кода их карты, но это может быть немного громоздким. Вместо этого давайте составим еще один список того, где находится каждая карта:

cardpositions=( (1,1), (2,1), (3,1) ...)

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

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

А что если я захочу хранить еще больше информации о картах? Что если я захочу сохранить, перевернута ли каждая карта? Что, если я хочу какой-то физический движок, а также должен знать скорость карт? Мне нужен еще один список для хранения графики для каждой карты! И для всех этих точек данных мне понадобится отдельный код, чтобы все они были организованы должным образом, чтобы каждая карта как-то отображала все свои данные!

Теперь давайте попробуем это ООП способом.

Вместо списка кодов давайте определим класс Card и составим из него список объектов Card.

class Card:

    def __init__(self,value,suit,pos,sprite,flipped=False):
        self.value=value
        self.suit=suit
        self.pos=pos
        self.sprite=sprite
        self.flipped=flipped

    def __str__(self):
        return self.value+" of "+self.suit

    def flip(self):
        if self.flipped:
            self.flipped=False
            self.sprite=load_card_sprite(value, suit)
        else:
            self.flipped=True
            self.sprite=load_card_back_sprite()

deck=[]
for suit in ("Spades","Hearts","Diamonds","Clubs"):
    for value in ("1","2","3","4","5","6","7","8","9","10","Jack","Queen","King"):
        sprite=load_card_sprite(value, suit)
        thecard=Card(value,suit,(0,0),sprite)
        deck.append(thecard)

Теперь вдруг все гораздо проще. Если я хочу переместить карту, мне не нужно выяснять, где она находится в колоде, а затем использовать ее, чтобы вывести свою позицию из массива позиций. Я просто должен сказать thecard.pos=newpos. Когда я вынимаю карту из основного списка колод, мне не нужно составлять новый список для хранения всех этих других данных; когда объект карты перемещается, все его свойства перемещаются вместе с ним. И если мне нужна карта, которая ведет себя по-разному, когда она переворачивается, мне не нужно изменять функцию переворачивания в моем основном коде, чтобы она обнаруживала эти карты и выполняла такое поведение; Мне просто нужно создать подкласс Card и изменить функцию flip () на подклассе.

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

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

Schilcote
источник
1
Если единственное, что вы убираете из ООП, это «управление памятью», то я не думаю, что вы поняли это очень хорошо. Там есть целая философия дизайна и щедрая бутылка «правильного дизайна»! Кроме того, управление памятью, безусловно, ничего не присуще объектной ориентации (C ++?), Хотя необходимость в ней становится более выраженной.
Рафаэль
Конечно, но это версия с одним предложением. И я использовал этот термин довольно нестандартно. Возможно, было бы лучше сказать «обработка информации», чем «управление памятью».
Schilcote
Существуют ли какие-либо не-ООП языки, которые позволили бы функции принимать указатель на что-то, а также указатель на функцию, первый параметр которой является указателем на вещи такого же рода, и компилятор проверял бы, что функция подходит для переданного указателя?
суперкат
3

За несколько лет написав встроенный C, управлял такими вещами, как устройства, последовательные порты и пакеты связи между последовательными портами, сетевыми портами и серверами; Я оказался квалифицированным инженером-электриком с ограниченным опытом процедурного программирования, сочиняя свои собственные абстракции из аппаратного обеспечения, которое в конечном итоге проявилось в том, что я позже осознал, в том, что нормальные люди называют «объектно-ориентированным программированием».

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

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

Я думаю, что легче ответить на ваш вопрос, если вы посмотрите на него в менее абсолютных терминах - не «что это решает», а скорее с точки зрения «вот проблема, а вот как это облегчает». В моем конкретном случае последовательных портов у нас была куча #ifdefs времени компиляции, которая добавляла и удаляла код, который статически открывал и закрывал последовательные порты. Функции открытия портов вызывались повсеместно и могли быть расположены в любом месте в 100-тысячных строках кода ОС, которые у нас были, и среда IDE не выделяла серым цветом то, что не было определено - вам пришлось отслеживать это вручную, и неси это в своей голове. Неизбежно у вас может быть несколько задач, пытающихся открыть данный последовательный порт, ожидающих их устройства на другом конце, и тогда ни один из написанного вами кода не сработает, и вы не можете понять, почему.

Абстракция была, хотя все еще в C, «классом» последовательного порта (ну, просто структурный тип данных), у нас был массив - по одному для каждого последовательного порта - и вместо того, чтобы иметь [эквивалент DMA в последовательных портах] «OpenSerialPortA», «SetBaudRate» и т. Д. Функции, вызываемые непосредственно на аппаратном уровне из задачи, мы вызывали вспомогательную функцию, которой вы передавали все параметры связи (скорость передачи, четность и т. Д.), Которая сначала проверяла массив структуры, чтобы убедиться, что порт уже открыт - если да, то с помощью какой задачи, которую он скажет вам как отладочный printf, чтобы вы могли сразу же перейти к разделу кода, который необходимо отключить, - а если нет, то он приступил к установке все параметры через их функции сборки HAL, и, наконец, открыл порт.

Конечно, есть и опасность для ООП. Когда я, наконец, очистил эту кодовую базу и сделал все аккуратно и чисто - написание новых драйверов для этой линейки продуктов стало, наконец, изнурительной, предсказуемой наукой, мой менеджер EOL разработал продукт именно потому, что ему понадобился на один проект меньше управлять, и он был пограничным съемным менеджером среднего звена. Что отняло у меня много сил / я нашел очень обескураживающим, поэтому я уволился с работы. ЛОЛ.

paIncrease
источник
1
Здравствуй! Это больше похоже на личную историю, чем на ответ на вопрос. Из вашего примера я вижу, что вы переписали какой-то ужасный код в объектно-ориентированном стиле, что сделало его лучше. Но неясно, было ли улучшение во многом с объектной ориентацией или это было просто потому, что вы к тому времени были более опытным программистом. Например, большая часть вашей проблемы, кажется, была из-за того, что код разбросан по тому или иному месту. Это можно было бы решить, написав процедурную библиотеку без каких-либо объектов.
Дэвид Ричерби
2
@DavidRicherby у нас была процедурная библиотека, но это то, что мы не одобряли, это было не просто из-за повсеместного использования кода. Дело в том, что мы сделали это задом наперед. Никто ничего не пытался ООП, это просто произошло естественно.
Увеличение
@DavidRicherby. Можете ли вы привести примеры реализации процедурной библиотеки, чтобы я мог быть уверен, что мы говорим об одном и том же?
Увеличение
2
Спасибо за ваш ответ и +1. Давным- давно другой опытный программист поделился, как ООП сделал свой проект более надежным forum.devshed.com/programming-42/… Я думаю, ООП был разработан очень умно некоторыми профессионалами, которые могли столкнуться с некоторыми проблемами в процедурном подходе.
user31782 10.09.15
2

Есть много заявлений и намерений о том, что / где ООП-программирование имеет преимущество перед процедурным программированием, в том числе его изобретателями и пользователями. но только потому , что технология была разработана для определенной цели его конструкторы не гарантирует , что она будет иметь успех в этих целях. это ключевое понимание в области разработки программного обеспечения, датируемое знаменитым эссе Брукса «Нет серебряной пули», которое все еще актуально, несмотря на революцию в коде ООП. (см. также Gartner Hype Cycle для новых технологий.)

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

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

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

  • Сравнение понимания объектно-ориентированных и процедурных программ начинающими программистами, взаимодействующими с компьютерами. Сьюзен Виденбек, Веннила Рамалингам, Сюзела Сарасамма, Синтия Л Корритор (1999)

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

смотрите также:

ВЗН
источник
1
У меня есть уважение к экспериментальным исследованиям. Однако существует проблема установления того, что они решают правильные вопросы. В том, что можно назвать ООП и в способах его использования, слишком много переменных, чтобы одно исследование имело смысл, imho. Как и многие вещи в программировании, ООП была создана экспертами для удовлетворения их собственных потребностей . При обсуждении полезности ООП (которую я не принял в качестве темы ОП, а именно, касается ли она недостатка процедурного программирования), можно спросить: какая функция, для кого, для какой цели? Только тогда полевые исследования становятся полностью осмысленными.
Бабу
1
Предупреждение о анекдоте: если проблема небольшая (например, до 500-1000 строк кода), ООП не имеет значения в моем опыте, это может даже помешать, если беспокоиться о вещах, которые не имеют большого значения. Если проблема велика и имеет какую-то форму «взаимозаменяемых частей», которую, кроме того, необходимо добавить позже (окна в графическом интерфейсе, устройства в операционной системе и т. Д.), Которую обеспечивает дисциплина ООП организации, неизбежна. Вы, конечно, можете программировать ООП без языковой поддержки (см., Например, ядро ​​Linux).
vonbrand
1

Быть осторожен. Прочитайте классику Р. Кинга «Моя кошка является объектно-ориентированной» в «Объектно-ориентированных концепциях, базах данных и приложениях» (Ким и Лоховский, ред.) (ACM, 1989). «Объектно-ориентированный» стал больше модным словом, чем четкой концепцией.

Кроме того, есть много вариаций на тему, мало общего. Существуют языки на основе прототипов (наследование от объектов, классов как таковых нет) и языки на основе классов. Есть языки, которые допускают множественное наследование, другие - нет. У некоторых языков есть идея, подобная интерфейсам Java (может быть принята как форма смягчения множественного наследования). Есть идея миксинов. Наследование может быть довольно строгим (как в C ++, не может реально изменить то, что вы получаете в подклассе), или очень свободно обрабатываться (в Perl подкласс может переопределить почти все). Некоторые языки имеют единый корень для наследования (обычно называемый Object с поведением по умолчанию), другие позволяют программисту создавать несколько деревьев. Некоторые языки настаивают на том, что «все является объектом», другие обрабатывают объекты и не-объекты, некоторые (например, Java) имеют «большинство из них являются объектами, но этих нескольких типов здесь нет». Некоторые языки настаивают на строгой инкапсуляции состояния в объектах, другие делают его необязательным (C ++ 'private, protected, public), другие вообще не имеют инкапсуляции. Если вы покоситесь на такой язык, как Схема, под прямым углом, вы увидите, что ООП встроен без особых усилий (может определять функции, которые возвращают функции, которые инкапсулируют некоторое локальное состояние).

vonbrand
источник
0

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

Мана
источник
Какие проблемы с безопасностью данных присутствуют в процедурном программировании?
user31782
В процедурном программировании нельзя ограничивать использование глобальной переменной. Любая функция может использовать это значение. Однако в ООП я мог бы ограничить использование переменной одним определенным классом или, возможно, только классами, которые его наследуют.
Ман
В процедурном программировании мы также можем ограничить использование глобальной переменной, используя переменную для определенных функций, то есть не объявляя какие-либо данные глобальными.
user31782
Если вы не объявите это глобально, это не глобальная переменная.
Ман
1
«Безопасный» или «Правильный» ничего не значат без спецификации. Эти вещи являются попытками поместить спецификацию в код для достижения этой цели: типы, определения классов, DesignByContract и т. Д. Вы получаете «Безопасность» в том смысле, что можете сделать границы личных данных неприкосновенными; предполагая, что вы должны подчиняться набору инструкций виртуальной машины для выполнения. Ориентация на объекты не будет скрывать внутреннюю память от кого-то, кто может читать память напрямую, а плохой объектный протокол протоколов раздает секреты.
Роб