Как избежать объектно-ориентированных ловушек, мигрировать из C, что сработало для вас?


12

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

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

Я читаю /programming/2688910/learning-to-think-in-the-object-oriented-way и /programming/1157847/learning-object-oriented-thinking , и будет смотреть на некоторые из книг, на которые указывают эти ответы.

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

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

Я думаю о чем-то вроде: «Является ли обычным иметь достаточное количество методов наблюдателей и модификаторов и использовать частные переменные или есть методы для их объединения / сокращения?»

Меня не беспокоит использование C ++ в качестве чистого ОО-языка, если есть веские причины смешивать методы. (Напоминает о причинах использования GOTO, хотя и экономно.)

Спасибо!


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

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

да это оно. Если вы ищете «предпочитаю не-друга не-друга», вы найдете много информации об этом. В конечном итоге все сводится к соблюдению принципа
единой ответственности

Ответы:


10

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


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

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

9

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

После этого приходит слишком много добытчиков / сеттеров. Допустим, у меня есть член данных. Мне действительно нужны эти данные в другом классе? Хорошо, сделай добытчик. Действительно ли мне действительно нужно изменять эти данные в течение срока службы объекта? Тогда сделай сеттер.

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


2
да, среди тех, кто поверхностно понимает инкапсуляцию, довольно популярно делать данные частными, а затем выполнять инкапсуляцию с помощью методов получения и установки.
Джин Бушуев

Java является единственным популярным языком, который все еще нуждается в методах get / set. Любой другой язык поддерживает свойства, поэтому нет никакой синтаксической разницы между открытым элементом данных и свойством. Даже в Java не является грехом иметь общедоступные члены данных, если вы также контролируете всех пользователей класса.
Кевин Клайн

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

До сих пор я думаю, что я смотрю на закрытых членов "своего рода" как локальные переменные в функции ... остальному миру не нужно ничего знать о них, чтобы fn работал ... и если fn изменения, только интерфейс должен быть согласованным. Я не склонен использовать метод получения / установки спама, поэтому я могу быть на правильном пути, на самом деле, глядя на написанный мной буферный класс, открытых элементов данных вообще нет. Благодарность!
Стивен

2

Когда я преодолел эту пропасть, я остановился на следующем подходе:

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

1) простое 1-ое проходное отображение того, как я написал бы программу с нуля в ОО-стиле - что наиболее важно для меня в то время, и это СУБЪЕКТИВНО - состояло в том, чтобы выяснить все базовые классы. Моей целью было инкапсулировать как можно больше в базовые классы. Чистые виртуальные методы для всего возможного в базовых классах.

2) Затем следующим шагом было создание дериваций.

3) последний шаг - в исходном процедурном коде отделить структуры данных от кода наблюдателя / модификатора. Затем используйте скрытие данных и отобразите все общие данные в базовые классы, и в подклассы пошли данные, которые не были общими для всей программы. И такой же подход к процедурному коду наблюдателя / модификатора - вся логика «везде использовалась» вошла в базовые классы. И логика просмотра / изменения, которая действовала только на подмножестве данных, вошла в производные классы.

Это субъективно, но БЫСТРО, если вы хорошо знаете процедурный код и структуры данных. Ошибка в обзоре кода - это когда немного данных или логики не появляется в базовых классах, но используется везде.


2

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


Да! Прежде чем человек становится мастером чего-либо, он учится подражать великим мастерам, которые работали до него. Изучение хорошего кода, реализованного другими, является хорошим способом изучения. Я бы порекомендовал взглянуть на повышение, начиная с простых дизайнов и углубляясь по мере улучшения понимания.
Джин Бушуев

1

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

Одна вещь, которую вы определенно не хотите делать, - это иметь поле, которое необходимо проверить на согласованность в мутаторе, и оставить его открытым.


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

1
Да, я согласен здесь, если вы находитесь на C ++ (о котором упоминался вопрос), но я делаю большую часть своей работы на Java, и, к сожалению, у нас нет структуры.

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

1

Книга «Реализация шаблонов реализации» Кента Бека - отличное обоснование того, как использовать, а не злоупотреблять объектно-ориентированными механизмами.


1

Меня не беспокоит использование C ++ в качестве чистого ОО-языка, если есть веские причины смешивать методы. (Напоминает о причинах использования GOTO, хотя и экономно.)

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

С точки зрения ОО, я думаю, что C ++ на самом деле немного отстает. Например, идея иметь не виртуальные функции - это галочка против этого. У меня были споры с теми, кто не согласен со мной, но не виртуальные участники просто не соответствуют парадигме, насколько я обеспокоен. Полиморфизм является ключевым компонентом ОО, и классы с не виртуальными функциями не являются полиморфными в смысле ОО. Так что, как ОО-язык, я думаю, что C ++ на самом деле довольно слаб по сравнению с такими языками, как Java или Objective-C.

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

Возможность метапрограммирования шаблонов также впечатляет. Взгляните на библиотеку Boost.Units, например. Эта библиотека обеспечивает поддержку типов для размерных величин. Я широко использовал эту библиотеку в инженерной фирме, в которой я сейчас работаю. Это просто обеспечивает гораздо более немедленную обратную связь для одного аспекта возможного программиста или даже ошибки спецификации. Невозможно скомпилировать программу, использующую формулу, в которой обе стороны оператора '=' не являются размерно-эквивалентными без явного приведения. Лично у меня нет опыта работы с любым другим языком, на котором это возможно, и, конечно, не с языком, обладающим мощью и скоростью C ++.

Метапрограммирование - это чисто функциональная парадигма.

На самом деле, я думаю, что вы уже вступили в C ++ с некоторыми неудачными заблуждениями. Других парадигм, кроме ОО, не следует избегать, их нужно НАЧИСЛЯТЬ. Используйте парадигму, которая естественна для аспекта проблемы, над которой вы работаете. Не навязывайте объектам то, что по существу не является проблемой, склонной к объектам. Насколько я понимаю, OO - это еще не половина истории C ++.


Разве ключевое слово virtual не предоставляет функциональность виртуального класса в C ++? Что касается комментария goto, то, что я вел, было то, что я не беспокоюсь о нарушении эмпирических правил, пока я понимаю причины. Я, однако, смотрел дальше в сторону отказа от императивов / процедур в пользу ОО, чем, возможно, было необходимо. Благодарю.
Стивен

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

1

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

@nightcracker

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

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

Доминик Гурто

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

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

wantTheBest

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

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

JPM

Одна вещь, которую вы определенно не хотите делать, - это иметь поле, которое необходимо проверить на согласованность в мутаторе, и оставить его открытым

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

Сумасшедший Эдди

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

Изначально я многое пропустил в ответе Сумасшедшего Эдди, я думаю, потому что я не читал некоторые из упомянутых тем ... как метапрограммирование. Я думаю, что большое сообщение в посте CE состояло в том, что C ++ - это такое сочетание возможностей и стилей, что каждый из них должен использоваться с максимальной отдачей ... включая необходимость, если это имеет смысл.

Итак, еще раз, спасибо всем, кто откликнулся!


0

Самым большим подводным камнем является вера в то, что ООП - это серебряная пуля или «одна идеальная парадигма».

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.