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

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

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

Разбира се, това е основен мой проблем, който доведе до небрежен, хакерски код, който работи само по начина, по който аз искам, след като изхвърлих всеки основен OOP през прозореца и започнах да попълвам кода си с глобални променливи, препращам декларации за всичко навсякъде и правене на членовете на класа публични.

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

Аз съм самопровъзгласил се за начинаещ в програмирането и приемам, че отнема време, за да се науча как да структурирам програма, но съм почти на края на акъла! Знам, че проблемът ми произтича от факта, че знам малко за ООП. Знам, че искам да напиша независими класове, които се справят с една задача. Но в същото време не разбирам как правилно да предупредя всеки клас за съществуването на другите части на програмата. Това е малко като да знаете какви видове храна трябва да ядете, но да не знаете как да използвате вилица...

Това е моят проблем накратко. И за да продължа с някои по-конкретни въпроси, които имам:

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

  • В предишни процедурни програми, които съм написал на C++, не беше необичайно да има константи променливи или #defines в глобалния обхват, в горната част на файла main.cpp. Например, може би размерите на екрана или друга полезна информация ще бъдат определени в началото на програмата. Какво се случва в OOP? Тази практика трябва ли да се избягва напълно? Или да направя файл MAIN.H и да го #включа във всеки друг хедър в проекта? Нямам идея какво да правя по въпроса...

  • Когато написах първата си средна по размер практически ООП програма, работата ми спря рязко, когато започнах да се опитвам да напиша клас StateMachine. Моето намерение беше класът StateMachine да съдържа всички възможни състояния на екрана, които програмата ще използва. Въпреки това възникна проблем, при който класът ми StateMachine изглежда не беше наясно с някои от другите ми класове State, въпреки че всички те бяха #included. Виждал съм хора да правят декларации за класове напред, необходимо ли е това? Трябва ли да изпращам спам навсякъде с декларации за класове или това мирише на код?

  • И накрая, има ли значение редът на командите за декларация #include и forward?

Знам, че това вероятно е супер основен въпрос, но това е нещо, което ми създаваше много трудности в прехода ми от еднофайлови процедурни 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++ и чисто OO / боклук събрани езици втори. Това създава много солидна основа, избягва няколко разновидности на грешки в дизайна/реализацията и прави много по-лесен преход по-късно. Изучаването на Java / C# / Python първо за да научите C++ след това е определена грешка.   -  person DevSolar    schedule 13.08.2012
comment
@DevSolar Не твърдя, че изучаването на C++ първо ви дава голямо предимство и по-добро разбиране как да пишете ефективен код. Но както OP спомена, той има опит с C++ с писане на процедурни кодове. Той всъщност има проблем(и) с разбирането и/или практическото използване на ООП дизайна. Python/Java/C# може да му даде идея след няколко дни. Нямам много опит, така че може би греша. Това е само моето мнение.   -  person Leri    schedule 13.08.2012


Отговори (3)


Не можете да мислите за ООП като за нормално функционално програмиране с класове. Това е грешен подход и ще ви подведе.

Проектирането на архитектурата на вашето приложение е изключително важно, има цели книги, написани за това. Колкото по-добре проектирате структурата на приложението си, толкова по-лесно ще ви бъде кодирането и толкова по-малко грешки ще имате. Добър съвет е да научите основите на някакъв език за моделиране, за да ви е по-лесно да рисувате и разбирате нещата. UML е чудесен избор за това.

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

Лесно можете да намерите много информация за това в мрежата. Това е накратко. Сега за вашите конкретни въпроси.

  1. Оставете main в глобален обхват. Лично аз дори го оставям в отделен main.cpp файл, за да го поддържам чист, но всъщност няма значение къде го поставяте.

  2. Опитайте се да избягвате глобалните променливи. Ако имате нужда от константи, добре е да ги дефинирате. И да, вашият подход с всички дефиниции в един заглавен файл работи добре. Въпреки това, може да искате да ги разделите по смисъл, а не да включвате файл с 10 000 дефиниции в .cpp, който се нуждае само от една от тях.

  3. Ако трябва да използвате клас, преди да бъде деклариран, ще трябва да го декларирате напред, в противен случай компилаторът няма да разбере, че е там.

  4. Редът на включванията има значение, защото всяко #include основно копира текста на съответния файл. Така че, ако #include вашите class.h във вашите class.cpp, текстът на заглавката ще бъде просто копиран, поставен в изходния файл от препроцесора. Редът на декларирането напред няма особено значение, стига да декларирате нещата, преди да ги използвате за първи път.

person SingerOfTheFall    schedule 13.08.2012

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

  • main() никога не е вътре в клас и винаги е в глобален обхват.

  • Най-важните правила са "постоянство" и "пригодност за поддръжка". Помислете как ще изглеждате на програмата по-късно, след като бъде "завършена" и искате да поправите няколко грешки. Къде ще намерите константи и дефиниции най-лесно?

  • Ако последователността от #includes има значение, вашите заглавни файлове са повредени. Всеки хедър трябва да бъде (минимално) самодостатъчен за нещата, декларирани в него.

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

Останалото наистина може да се намери във всяка добра книга за C++ и наистина трябва да работите с книга, вместо да си проправяте път през уроци и страници с въпроси и отговори.

person DevSolar    schedule 13.08.2012
comment
Знам какво имаш предвид и съм съгласен, но правилото всъщност не трябва да бъде клас за файл, а по-скоро компонент за файл, като името на файла е главният клас (и следователно компонент и класа, споделящ името). Защо това разграничение? Дефиницията на помощен тип (пример: итераторът, който навигира в контейнера) трябва да бъде заедно с подпомогнатия клас (контейнера). +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

добре... Така че основният проблем изглежда е поддържането на OO организация на вашия файл. първо и преди всичко, ако не сте прочели Ръководство за стил на Google C++ силно бих ви посъветвал да го направите. Намерих и този предишен отговор, който е доста ясен и прост

Надявам се това да помогне

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