Данная статья посвящена разбору вопроса о том, какому именно объекту ООП должен принадлежать метод, осуществляющий взаимодейстие между несколькими сущностями.

Это распространённая тема для холиваров. Например:
Не используйте ООП. Никогда. Это ошибка.

На эту тему есть много материалов, к примеру: 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», изменение состояние которого правильно опишет ожог руки. Огонь состояния не изменит.

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