Подождите, пожалуйста, выполняется поиск в заданном разделе

Основные принципы разработки классов и объектов

Классы и объекты. Основные понятия и определения. Описание класса. Определение объекта, средства и примеры его описания. Свойства класса и объекта. Способы и правила доступа к членам класса и ограничения на доступ к членам класса. Определение методов класса, примеры программ.

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

Указатели и ссылки. Указатели, основные понятия и определения. Имя указателей, оператор разыменования. Указатели, адреса и переменные. Структура памяти: стековая и динамична. Оператор new и delete, примеры программ

Размещение объектов в динамической памяти. Доступ к членам класса, примеры программ. Динамическое размещение членов класса. Удаление объектов. Указатель this и оператор адрес &. Особенности разработки программ с указателями, примеры программ. Ссылка, основные понятия и определения. Нулевые указатели и нулевые ссылки. Передача аргументов функций как ссылок. Использование swap.

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

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

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

Требования к разработке

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

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

Как и большинство людей, я – лентяй и не люблю много работать. Поэтому я стараюсь писать только тот код, который нужен в данный момент. Это оборачивается тем, что приходится изменять уже написанный код. Конечно, с одной стороны это – минус, но с другой оказывается, что только в редкие моменты можно предусмотреть все, что требуется, сразу, и часто код, написанный "на будущее", приходится переписывать. На самом деле, этот подход я позаимствовал из экстремального программирования (XP – eXtreme Programming), где вся разработка ведется подобным образом. Итак, получаем еще одно требование –код должен быть расположен к изменениям.

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

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

Правила разработки

Далее я сформулирую правила, которых придерживаюсь при написании программ:

  • Главный инструмент разработчика – ручка и бумага
  • Код должен быть понятен
  • Слабая степень связности и сильная степень зацепления
  • Проверка предположений о входных параметрах функций
  • Изменения маленькими шажками
  • Инкапсуляция данных
  • Наследование и агрегирование
  • Корректная инициализация объектов
  • Инкапсуляция изменений
  • Рефакторинг

Главный инструмент разработчика – ручка и бумага

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

Код должен быть понятен

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

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

2) Все идентификаторы в коде должны выражать смысл понятий, стоящих за ними. Например,

class Car
{
public:
void IncreaseSpeed(double acceleration);
};

гораздо понятней, чем

class MyClass
{
public:
void Method1(double);
};

3) Объявление и реализация каждого класса должны находиться в отдельных файлах. То есть в одном *.h или *.cpp файле описывается только один класс. Иногда от одного базового класса наследуется несколько классов, переопределяющих одну единственную функцию, в этом случае велик соблазн поместить все классы в один файл, но я все равно предпочитаю придерживаться данного правила. В крайнем случае можно сделать две пары файлов: одна для базового класса, другая – для производных.

Слабая степень связности и сильная степень зацепления

Это одно из главных правил объектно-ориентированного программирования. По английски оно звучит, как "low coupling, high cohesion".

Связность (coupling) – это взаимная зависимость реализации классов между собой, то есть количество изменений, которые надо внести в классы при изменении другого класса. Слабая связность означает, что изменения, вносимые в один класс повлекут за собой небольшие изменения в другие классы. Например, если в классе есть публичная переменная (public), широко используемая в программе, то изменение типа этой переменной повлечет за собой изменение большой части кода программы. Уменьшить эту связность можно за счет реализации методов доступа (Set... и Get...). В большинстве случаев это позволит изменить только методы доступа и при необходимости добавить новые.

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

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

Проверка предположений о входных параметрах функций

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

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

Изменения маленькими шажками

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

Инкапсуляция данных

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

Наследование и агрегирование

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

Корректная инициализация объектов

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

Инкапсуляция изменений

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

Рефакторинг

Рефакторингом называется внесение изменений в код без изменения его функциональности. Обычно рефакторинг применяется перед реализацией новой функциональности, которая требует изменений в существующем коде. Основное правило: сначала изменить существующий код под новые потребности, проверить его правильность, а потом вносить новые изменения. Такое разделение упрощает проверку правильности работы новой функциональности, разделяя вносимые изменения . Об этом говорит сайт https://intellect.icu .

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

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

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

Методом (или функцией) называется операция, определенная над объектами некоторого класса.

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

Понятие класса является более общим, чем понятие объекта. Объект является экземпляром класса. Класс может рассматриваться как совокупность объектов (подобно тому, как множество есть совокупность элементов). Класс может быть элементарным или подразделяться на подклассы (подобно тому как множество подразделяется на подмножества). Например, класс PERSON содержит подкласс STUDENT, который, в свою очередь, содержит объект John_Smith.

Классы

Ссылочный тип, определенный пользователем (аналогично языкам C++ и Java) •

Единичное наследование классов

Множественное наследование интерфейсов

• Члены (элементы) класса: - константа, поле, метод, оператор, конструктор, деструктор; - свойство, индексатор, событие; - статические и инициализированные члены.

• Доступ к членам класса (public, protected, private(по умолч.),internal,protected internal)

• Инициализация – посредством оператора new

Основные принципы разработки классов и объектов - портал intellect.icu

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

Любой объект является экземпляром (instance) класса. Определение классов и объектов — одна из самых сложных задач объектно-ориентированного проектирования.

Основные принципы разработки классов и объектов - портал intellect.icu

Рис. 2.38. Графическое представление класса

Список литературы

  1. Приемы объектно-ориентированного проектирования. Паттерны проектирования. Гамма Э., Хелм Р., Джонсон Р., Влиссидес Д.
  2. Экстремальное программирование. Бек К.
  3. Язык программирования C++. Страуструп Б.
  4. Рефакторинг: улучшение существующего кода. Фаулер М., Бек К., Брант Д., Робертс Д., Апдайк У.
  5. Эффективное использование STL. Мейерс С.
  6. Наиболее эфективное использование STL. Мейерс С.
подробнее на сайте https://intellect.icu/osnovnye-printsipy-razrabotki-klassov-i-obektov-87

Комментарии (1)

avatar
Admin 22.3.2020 13:31

чтобы еще лучше понять смысл классов интерфейсов и вообще ООП, рекомендуем прочитать UML диаграммы классов
https://intellect.icu/diagramma-klassov-class-diagram-4825

Отношения классов в UML
https://intellect.icu/otnosheniya-klassov-v-uml-4301


avatar

Чтобы оставить комментарий войдите или зарегистрируйтесь



Объектно-ориентированное программирование