Данная статья посвящена разбору вопроса о том, какому именно объекту ООП должен принадлежать метод, осуществляющий взаимодейстие между несколькими сущностями.
Это распространённая тема для холиваров. Например:
Не используйте ООП. Никогда. Это ошибка.
На эту тему есть много материалов, к примеру: www.youtube.com/watch?v=QM1iUe6IofM
Если ООП все еще кажется вам хорошей идеей, то решите простую задачку.
Есть три объекта: кошка, кормушка и человек. Вам необходимо написать метод, который бы позволял человеку покормить кошку, воспользовавшись кормушкой.
Вопрос: методом какого класса будет являться метод.покормить()?
Просьба привести аргументированный ответ, в соответствии с иерархией классов, и другими лучшими практиками ООП.
Теперь сравните это с функциональной реализацией: у вас есть функция покормитьКошку() принимающая в качестве аргумента ссылку на кошку и кормушку.
Цитата из холивара
Как ответить на данный вопрос?
Вначале давайте рассмотрим пример из физики.
Кейс 1 (из физики): закон Ома
Закон Ома: I=U/R, где I — сила тока, U — напряжение, R — сопротивление.
Несложно заметить, что закон Ома, как и любую другую формулу из трех переменных, можно записать тремя способами: I=U/R, R=U/I, U=IR. Как выработать правило, позволяющее однозначно определить единственную форму записи? Очень просто: надо записать с левой стороны производную величину, т.е. ту, которая становится имеющей определённое значение, в зависимости от остальных величин.
I=U/R «Сила тока СТАНОВИТСЯ равной отношению напряжения на концах проводника к сопротивлению проводника» — верно.
U=IR «Напряжение на концах проводника СТАНОВИТСЯ равным его сопротивлению, умноженному на силу тока через проводник» — не верно.
R=U/I «Сопротивление проводника СТАНОВИТСЯ равным отношению напряжения на концах к силе тока» — не верно.
Видите — как только мы условились, что производное значение находится слева, остался только один вариант.
Вот так же, мы поступим и в ООП. Условимся, что метод принадлежит тому, кто воздействует:
Кто_действует.Метод(Объект_воздействия);
Кейс 2: Кто взял Измаил?
Следовательно, при ответе на вопрос «Кто взял Измаил?» с точки зрения объектно ориентированного программирования, и с позиции «кто воздействует?», правильный ответ будет Суворов.ВзятьКрепость(Измаил, Турки):boolean. Все другие варианты, такие как: Турки.Про***тьКрепость(Измаил, Суворову), Измаил.СменаСобственника(Турки, Суворов), АбстрактнаяКрепость.БытьЗахваченной(Исмаил, Суворов, Турки) и т.д. — все эти варианты не верны.
Кейс 3: Человек, кормушка и кошка
«Покорми кошку!» с сайта corchaosis.ru
Человек насыпал еды в кормушку. Это метод:
Человек.НасыпатьЕдыКошке(ПакетЕды,Кормушка)
. При выполнении метода, в глобальной переменной ПакетЕды количество еды уменьшается, а в глобальной переменной Кормушка появляется.
А как же кошка? А кошка существует ДО вызова метода НасыпатьЕдыКошке, как условие его вызова. Если кошка на даче, то и метод НасыпатьЕдыКошке вызываться не будет.
Кейс 4: Игрок в DOOM, шотган и монстр
Допустим, что вопрос попадания в монстра не имеет градаций: либо попал, либо не попал.
Правильная реализация. Всё начинается с метода Игрока:
Игрок (либо, Игрок_1 в многопользовательской игре).Выстрел(Монстр_1)
Внутри реализации метода Выстрел, мы видим, что текущее оружие игрока — шотган.
Следовательно, вызываем метод вложенного объекта:
Игрок_1.Оружие[Игрок_1.Номер_выбранного_оружия].Выстрел(Монстр_1)
Игрок_1.Оружие — это класс TWeapon.
В данном случае, вызывается метод класса TShotgun, который является дочерним к TWeapon.
Итак, имеем: Шотган.Выстрел(Монстр_1)
Шотган, при выполнении данного действия, изменяет внутреннее состояние: для шотгана это количество патронов (а для другого вида оружия могла бы быть, например, и температура). Также, мы определяем силу урона — так, шотган стреляет по 2 патрона, но может выстрелить и один, если в заряде остался только один патрон.
Если бы мы выстрелили из ракетницы, то появился бы новый объект — ракета. Со своим методом Tick, обрабатывающим действия за один тик игрового времени (игровое время изменяется, обычно, в тиках). Но мы выстрелили из оружия, поражающего без задержки, поэтому — знаем количество выстреленных патронов (1 или 2), знаем расстояние (сравнивая Игрок.Position и Монстр_1.Position), и внутри метода класса Шотган, рассчитываем ущерб.
И, наконец, Монстр_1.НанесёноПовреждение(сила_повреждения:Float)
. Теперь, как и перед этим шотган менял внутреннее состояние (кол-во патронов), теперь Монстр_1 меняет внутреннее состояние Монстр_1.Здоровье
, и сценарий поведения (особенно, если Здоровье стало меньше нуля).
Итак, мы видим, что благодаря ООП, мы можем легко добавить новое оружие: достаточно описать его как дочерний класс от TWeapon, определить Выстрел и разместить на карте. Класс Игрок уже умеет подбирать и добавлять в свой игровой набор объекты TWeapon. Всё. Хотя нет, не всё. Если оружие будет дарить монстрам цветочки, заставляя их влюбляться в игрока, то и монстрам следует прописать метод ОтветитьНаПризнаниеВЛюбви:boolean
, а также набор других методов — в зависимости от степени проработки, в этом случае вам может потребоваться и ряд новых ООП объектов и их методов.
Кейс 5: Потрогать руками — это функция или метод
Не только ответ на этот вопрос, но и сам интерфейс взаимодействия, очевидно, находится в зависимости от трогаемого объекта.
Потрогать_руками::дом
— это функция.
Потрогать_руками:: ноутбук — это метод объекта «ноутбук». Вы должны использовать интерфейс, чтобы вызвать методы ноутбука «KeyDown, KeyUp, KeyPressed» передав в эти методы правильные данные: какая кнопка была нажата, в какой момент времени и т.д.
Потрогать_руками::огонь
— это метод объекта «вы». Вам необходим объект «self», изменение состояние которого правильно опишет ожог руки. Огонь состояния не изменит.
Думаю, вы и сами можете продолжить список интерфейсов объектов, которые можно потрогать руками, с целью получения практики использования объектно-ориентированного программирования.
Автор: DragonSoft