Реальность такова, что такие преобразования обычно не будут хорошим объектно-ориентированным кодом. Зачем? Потому что объектно-ориентированный код не просто перемещает функции в методы, а данные в члены.
Вместо этого хороший объект должен отвечать за все свои данные и принимать параметры метода только в тех случаях, когда эти параметры определяют данные, с которыми будут работать.
Это означает, что нет преобразования 1: 1 от процедурных функций и процедурных структур данных к объектно-ориентированным.
Посмотрев вокруг, я не нашел ни одного примера, который бы мне понравился, поэтому просто приведу свои правила рефакторинга для преобразования процедурного кода в ООП.
Первый шаг - просто упаковать каждый модуль как объект. Другими словами, просто создайте объект, который содержит данные и функции. Это ужасно для пуриста, но с чего-то начинать надо. Например, если у вас был модуль BankAccount, теперь у вас будет объект BankAccount.
Очевидно, что функции передавали в них данные от внешних вызовов. Здесь вы ищете, как интернализировать эти данные и сделать их максимально частными. Цель должна состоять в том, чтобы вы получили свои данные в конструкторе (по крайней мере, в начальной точке) и удалили параметры, которые использовались для получения данных вручную, и заменили их ссылками на эти теперь личные данные. Используя объект BankAccount, теперь весь доступ к счету осуществляется через методы объекта, и фактические данные счета были усвоены.
Многие из ваших функций, вероятно, возвращали измененные версии структур данных: прекратите возвращать эти данные напрямую и оставьте эти изменения в частных структурах. Создайте свойства средства доступа, которые возвращают ваши теперь приватные данные, где это необходимо, и пометьте их как «устаревшие» (ваша цель - сделать объект хозяином своих данных и возвращать только результаты , а не внутренние данные). С объектом BankAccount мы больше не возвращаем фактические данные счета, но у нас есть свойства для CurrentBalance и такие методы, как AverageBalance (int days), чтобы просмотреть счет.
Со временем у вас будет набор автономных объектов, которые все еще мало похожи на то, что вы сделали бы, если бы начали с объектов в своем дизайне, но, по крайней мере, вы можете продолжить рефакторинг с новыми объектами. Мой следующий шаг обычно состоит в том, чтобы обнаружить, что объекты, созданные в результате такого рефакторинга, имеют много обязанностей. На данный момент некоторые общие потоки, вероятно, были обнаружены, и вы должны создать объекты для рефакторинга этих общих идей. Если у нас есть BankAccount, у нас, вероятно, есть другие типы учетных записей, и если мы приведем в соответствие методы всех этих типов учетных записей, мы сможем сделать Account в качестве базового класса, который реализует все общие функции, тогда как BackAccount, SavingsAccount и другие реализуют детали.
Как только структура классов начинает обретать форму, настало время почувствовать себя лучше в отношении преобразования. Рефакторинг - это процесс, а не конечная точка, поэтому я обычно нахожу, что структура моего класса продолжает развиваться. Одна из приятных вещей, достигнутых в этом, заключается в том, что ваши данные являются частными и обрабатываются с помощью методов, так что вы можете реорганизовывать внутренние компоненты более свободно по мере продвижения.
Одна вещь, которая делает это правдоподобным, это проведение хороших юнит-тестов. Выполняя процедурные преобразования в ООП, я часто сохраняю старый код как «базовый уровень», чтобы проверить его. То есть, тест может проверять результаты старой процедурной системы. Если вы не совпадаете, это хорошая идея, чтобы выяснить, почему. Я нахожу, что часто есть ошибка ... но иногда ваш новый более чистый код на самом деле делает что-то правильное, что было неправильно в прошлом.
Относительно создания "слишком глубоких" деревьев объектов: это может быть результатом того, что вы слишком одержимы наследованием. Я считаю, что композиты часто являются лучшей идеей, когда вы реализовали интерфейсы для нескольких объектов, а не пытались объединить все эти функции в один родительский объект. Если вы обнаруживаете, что создаете родительские объекты, которые просто представляют собой наборы функций, рассмотрите возможность упрощения путем создания интерфейса для каждого набора функций и реализации этих интерфейсов.