Как правильно структурировать ООП и многофайловые проекты?

Как начинающий программист, только сейчас изучающий основы ООП, я столкнулся с множеством проблем с базовой структурой включения моих практических программ. Я учился программированию, используя различные письменные и онлайн-ресурсы. Но вот моя проблема (ну, одна из них ...):

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

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

Проще говоря: Мое программирование - беспорядок ... C ++ - мой первый язык программирования, и даже когда я изо всех сил стараюсь проектировать / писать объектно-ориентированным способом, я получаю уродливый беспорядок файлы, # включая все почти в каждый файл, и странное сочетание процедурного и спагетти-кода ООП, которое редко работает!

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

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

  • В многофайловом проекте C ++ нормально / необходимо ли помещать функцию main () в отдельный класс? Или это стандартное решение - оставлять main () в глобальной области видимости?

  • В предыдущих процедурных программах, которые я писал на C ++, нередко было наличие переменных-констант или #defines в глобальной области в верхней части файла main.cpp. Например, размеры экрана или другая полезная информация будут определены в начале программы. Что происходит в ООП? Следует ли полностью избегать этой практики? Или мне сделать файл MAIN.H и # включить его во все остальные заголовки проекта? Я понятия не имею, что с этим делать ...

  • Когда я писал свою первую программу ООП для практики среднего размера, моя работа резко остановилась, когда я начал пытаться написать класс StateMachine. Я хотел, чтобы класс StateMachine содержал все возможные состояния экрана, которые программа могла бы использовать. Однако возникла проблема, когда мой класс StateMachine, казалось, не знал о некоторых других моих классах State, хотя все они были #included. Я раньше видел, как люди делали форвардные объявления классов, нужно ли это? Должен ли я рассылать спамовые объявления классов повсюду или это запах кода?

  • Наконец, имеет ли значение порядок команд #include и прямого объявления?

Я знаю, что это, вероятно, очень простой вопрос, но это то, что доставляло мне очень тяжелые времена при переходе от однофайловых процедурных программ на C ++ для начинающих к многофайловому ООП-программированию. Есть ли какое-то общее правило для структурирования ваших программ, чтобы все просто работало? Я использую охранники включения, так есть ли причина, по которой нельзя просто # включать каждый заголовок в каждый исходный файл? Должен ли я иметь файл common.h, который # включен в каждый файл заголовка для каждого класса?

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


person MrKatSwordfish    schedule 13.08.2012    source источник
comment
Для лучшего понимания ООП, почему бы не попробовать поиграть с Java или C # или даже лучше с python (IMO, это лучший способ попробовать в вашем случае)? C ++, без сомнения, один из самых мощных языков, но это головная боль (по крайней мере для меня) работа с классами на C ++.   -  person Leri    schedule 13.08.2012
comment
@PLB: я искренне рекомендую сначала изучить C ++ и чистый объектно ориентированный объект / мусор Собранные языки вторые. Это создает очень прочную основу, позволяет избежать нескольких разновидностей ошибок дизайна / реализации и значительно упрощает переход в дальнейшем. Изучение Java / C # / Python в первую очередь для изучения C ++ во вторую - это явная ошибка.   -  person DevSolar    schedule 13.08.2012
comment
@DevSolar Я не спорю, что изучение C ++ в первую очередь дает вам большое преимущество и лучшее понимание того, как писать эффективный код. Но, как упомянул OP, у него есть опыт работы с С ++ с написанием процедурных кодов. У него действительно есть проблемы с пониманием и / или практическим использованием конструкций ООП. Python / Java / C # могут дать ему представление за несколько дней. Я не очень опытен, так что, возможно, я ошибаюсь. Это только моё мнение.   -  person Leri    schedule 13.08.2012


Ответы (3)


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

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

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

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

  1. Оставьте main в глобальной области видимости. Лично я даже оставляю его в отдельном main.cpp файле, чтобы он оставался чистым, но на самом деле не имеет значения, куда вы его положите.

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

  3. Если вам нужно использовать класс до его объявления, вам придется объявить его вперед-объявить, иначе компилятор не узнает о его наличии.

  4. Порядок включения имеет значение, потому что каждый #include в основном копирует текст соответствующего файла. Итак, если вы #include свой class.h в своем class.cpp, текст заголовка будет просто скопирован препроцессором в исходный файл. Порядок предварительного объявления на самом деле не имеет значения, если вы пересылаете материал объявления до его первого использования.

person SingerOfTheFall    schedule 13.08.2012

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

  • main () никогда не находится внутри класса и всегда находится в глобальной области видимости.

  • Важнейшие правила - «постоянство» и «ремонтопригодность». Подумайте, как вы будете смотреть на программу позже, когда она "завершится" и вы захотите исправить пару ошибок. Где проще всего найти константы и определения?

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

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

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

person DevSolar    schedule 13.08.2012
comment
Я знаю, что вы имеете в виду, и согласен, но на самом деле правилом должен быть не класс для каждого файла, а скорее компонент для каждого файла, с именем файла, являющимся основным классом (и, следовательно, component и класс, имеющий имя). Почему это различие? Определение вспомогательного типа (пример: итератор, который перемещается по контейнеру) должно быть вместе с вспомогательным классом (контейнером). +1 - person David Rodríguez - dribeas; 13.08.2012
comment
@ DavidRodríguez-dribeas: ACK для итераторов, которые являются неотъемлемой частью класса. Но слишком часто я видел, как вспомогательные классы вырастают далеко за пределы своей первоначальной концепции, используются повсюду (в других пространствах имен, даже умно скрытые за объявлением using namespace) - и чертовски раздражает необходимость отслеживать их, пытаясь понять узнать, что они на самом деле делают ... - person DevSolar; 13.08.2012
comment
Спасибо за совет! Мне всегда интересно учиться по книгам и электронным книгам, и у меня уже есть несколько. Прискорбный побочный эффект самообучения состоит в том, что у вас нет профессоров или коллег, от которых можно было бы рассказывать идеи, когда вы в чем-то застреваете, независимо от того, насколько это просто! Из-за этого я очень ценю проницательность людей на этом сайте. Я обязательно воспользуюсь всем этим советом и снова посмотрю на свои различные ресурсы, чтобы увидеть, смогу ли я лучше понять структуру программы. Спасибо за ваше время! :) - person MrKatSwordfish; 14.08.2012
comment
@MrKatSwordfish: взгляните на доступные источники. Если вы хорошо их понимаете, отметьте, что делает их понятными, и попытайтесь подражать этому. Если у вас есть проблемы с его пониманием, запишите причину и избегайте этого. - person DevSolar; 14.08.2012

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

Я надеюсь это поможет

person MimiEAM    schedule 13.08.2012
comment
Хотя в Руководстве по стилю Google есть много хороших советов, в нем также есть несколько не очень хороших, более подходящих для переобучения Java-разработчиков, которые сильно искажают стиль C ++. Запрещение исключений, случайное разрешение выходных параметров, запрет аргументов по умолчанию, отказ от использования C ++ 11 / несанкционированных библиотек Boost и тому подобное. Это руководство по стилю для внутренней работы Google, а не общая передовая практика C ++. - person DevSolar; 13.08.2012