пятница, 25 декабря 2015 г.

Глава 6. Классы(1 часть)

В основе классов лежат абстрактные типы данных - это набор, включающий данные и выполняемые над ними операции. По сути, АТД - это взаимосвязанные между собой данные и операции с этими данными. Обычно АТД выгоднее использовать, чем примитивные типы, потому что это позволяет нам скрыть реализацию экземпляра, значит мы можем её легко поменять, например, было поле int usersCount, его будет легко изменить на bigint usersCount, если оно обёрнуто в АТД. Интерфейс становится более информативен, например  Pixels fontSize информативнее int fontSize. Также код становится легче оптимизировать и проверять его работу, так как семантические области чётко ограничены и при правильном подборе названий легко читаемы и воспринимаемы. При подборе названия АТД желательно, чтобы в нём не содержалось среды или способа его реализации, например ScoresTable предпочтительнее ScoresFile, так как мы можем захотеть изменить способ хранения счетов с файла на бд например.
Класс - это АТД, который поддерживает наследование и полиморфизм. Самое главное в хорошо спроектированном классе - это его интерфейс. Он должен представлять из себя хорошо ограниченную абстракцию. Публичные методы должны быть хорошо согласованны друг с другом - служить единой цели. Интересный совет - следить за тем, что у метода  есть противоположный ему, то есть, если есть метод добавления элемента, то скорее всего должен быть метод его удаления(но не всегда). Если видим, что какие-то методы имеют отличную от других ответственность, то выносим их в отдельный класс. Нужно постоянно следить за целостностью интерфейса при изменении класса, потому что её легко нарушить.
Вдобавок к хорошей абстракции элементы класса должны иметь правильную инкапсуляцию. Нужно стремиться минимизировать доступность членов классов для других классов - публичных методов не должно быть много, меньше 7. Все данные должны быть закрытыми - открыты только методы. Не стоит делать предположений о клиентах класса. Совсем не стоит нарушать семантическую инкапсуляцию(по сути предыдущее предложение про то же самое). Снижаем связанность с другими классами.
Существуют разные виды отношений между классами, далее идут два из них.
Включение - класс А содержит в качестве одного из своих элементов экземпляр класса B. Использовать это отношение вполне нормально, главное в целом не перебарщивать с количеством членов-данных, их не стоит делать больше 7 штук. Большое их количество чаще всего свидетельствует о нарушении принципа единой ответственности.
Наследование - класс А является более специфическим вариантом класса B. Поскольку наследник является лишь более специфичной версией своего базового класса, то для него должен работать принцип подстановки Лисков - там где используется базовый класс, можно использовать любого его наследника. При наследовании стоит перемещать общие интерфейсы и данные на более высокий уровень иерархии наследование, если при этом они не нарушают абстракцию. Если у класса только один наследник, то что-то в этом мире не так. Не стоит переопределять метод, оставляя его пустым - это нарушает абстракцию и этот код трудно сопровождать. Многоуровневое наследование плохо потому, что тяжело удержать в голове всё дерево или даже его ветвь. В общем и целом к наследованию стоит прибегать довольно редко потому, что оно чаще приводит к дополнительной сложности, чем к избавлению от неё.
Закон Деметры: объект класса А может вызывать любые из собственных методов, если он к тому же владеет экземпляром класса Б, то он может вызывать методы этого экземпляра, но ему не следует вызывать методы объектов, которые включает в себя класс Б. В целом, сотрудничество класса с другими классами должно быть минимизировано.
В отношении конструкторов можно сказать следующее: инициализируем в них все данные-члены и по возможности выполняем полное копирование экземпляров вместо ограниченного( передаём аргументы по значению, а не по ссылке).

Комментариев нет:

Отправить комментарий