Этот вопрос несколько не зависит от языка, но не полностью, поскольку объектно-ориентированное программирование (ООП) отличается, например, в Java , которая не имеет функций первого класса, чем в Python .
Другими словами, я чувствую себя менее виноватым за создание ненужных классов в таком языке, как Java, но я чувствую, что может быть лучший способ в менее шаблонных языках, таких как Python.
Моя программа должна выполнить относительно сложную операцию несколько раз. Эта операция требует много «бухгалтерии», должна создавать и удалять временные файлы и т. Д.
Вот почему также необходимо вызывать множество других «подопераций» - складывать все в один огромный метод не очень приятно, модульно, читабельно и т. Д.
Вот такие подходы, которые приходят мне в голову:
1. Создайте класс, который имеет только один открытый метод и сохраняет внутреннее состояние, необходимое для подопераций, в своих переменных экземпляра.
Это будет выглядеть примерно так:
class Thing:
def __init__(self, var1, var2):
self.var1 = var1
self.var2 = var2
self.var3 = []
def the_public_method(self, param1, param2):
self.var4 = param1
self.var5 = param2
self.var6 = param1 + param2 * self.var1
self.__suboperation1()
self.__suboperation2()
self.__suboperation3()
def __suboperation1(self):
# Do something with self.var1, self.var2, self.var6
# Do something with the result and self.var3
# self.var7 = something
# ...
self.__suboperation4()
self.__suboperation5()
# ...
def suboperation2(self):
# Uses self.var1 and self.var3
# ...
# etc.
Проблема, которую я вижу с этим подходом, состоит в том, что состояние этого класса имеет смысл только внутри, и он не может ничего делать со своими экземплярами, кроме как вызывать их единственный открытый метод.
# Make a thing object
thing = Thing(1,2)
# Call the only method you can call
thing.the_public_method(3,4)
# You don't need thing anymore
2. Создайте группу функций без класса и передайте различные внутренние переменные между ними (в качестве аргументов).
Проблема, которую я вижу с этим, состоит в том, что я должен передать много переменных между функциями. Кроме того, функции будут тесно связаны друг с другом, но они не будут сгруппированы вместе.
3. Как 2. но сделайте переменные состояния глобальными вместо их передачи.
Это было бы совсем нехорошо, так как мне приходилось делать операцию более одного раза с другим вводом.
Есть ли четвертый, лучший подход? Если нет, какой из этих подходов будет лучше и почему? Я что-то упускаю?
источник
Ответы:
Вариант 1 является хорошим примером инкапсуляции, используемой правильно. Вы хотите, чтобы внутреннее состояние было скрыто от внешнего кода.
Если это означает, что у вашего класса есть только один публичный метод, пусть будет так. Это будет намного легче поддерживать.
В ООП, если у вас есть класс, который выполняет ровно одну вещь, имеет небольшую открытую поверхность и скрывает все свое внутреннее состояние, то вы (как сказал бы Чарли Шин) ПОБЕДИЛИ .
Вариант 2 страдает от низкой когезии . Это усложнит обслуживание.
Вариант 3, как и вариант 2, страдает от низкой когезии, но гораздо серьезнее!
История показала, что удобство глобальных переменных перевешивается из-за жестокой стоимости обслуживания. Вот почему вы слышите, как старые пукающие, как я, все время разглагольствуют об инкапсуляции.
Вариант победы # 1 .
источник
ThingDoer(var1, var2).do_it()
противdo_thing(var1, var2)
.def do_thing(var1, var2): return _ThingDoer(var1, var2).run()
, чтобы сделать внешний API немного красивее.Я думаю, что # 1 на самом деле плохой вариант.
Давайте рассмотрим вашу функцию:
Какие фрагменты данных использует suboperation1? Собирает ли он данные, используемые suboperation2? Когда вы передаете данные, сохраняя их на себе, я не могу сказать, как связаны части функциональности. Когда я смотрю на себя, некоторые атрибуты взяты из конструктора, некоторые из вызова the_public_method, а некоторые добавляются случайным образом в других местах. На мой взгляд, это беспорядок.
Как насчет № 2? Ну, во-первых, давайте посмотрим на вторую проблему с ним:
Они будут в модуле вместе, поэтому они будут полностью сгруппированы вместе.
На мой взгляд, это хорошо. Это делает явными зависимости данных в вашем алгоритме. Сохраняя их как в глобальных переменных, так и в самом себе, вы скрываете зависимости и заставляете их казаться менее плохими, но они все еще там.
Обычно, когда возникает такая ситуация, это означает, что вы не нашли правильный способ разложить вашу проблему. Вам неудобно разделять на несколько функций, потому что вы пытаетесь разделить их неправильно.
Конечно, не видя вашей реальной функции, трудно угадать, что было бы хорошим предложением. Но вы даете намек на то, с чем имеете дело здесь:
Позвольте мне выбрать пример чего-то, что соответствует вашему описанию, установщик. Установщик должен скопировать кучу файлов, но если вы отмените его частично, вам нужно перемотать весь процесс, включая возврат всех файлов, которые вы заменили. Алгоритм для этого выглядит примерно так:
Теперь умножьте эту логику на необходимость выполнения настроек реестра, значков программ и т. Д., И вы получите беспорядок.
Я думаю, что ваше решение № 1 выглядит так:
Это делает общий алгоритм более понятным, но скрывает способ взаимодействия различных частей.
Подход № 2 выглядит примерно так:
Теперь ясно, как части данных перемещаются между функциями, но это очень неудобно.
Так как должна быть написана эта функция?
Объект FileTransactionLog является менеджером контекста. Когда copy_new_files копирует файл, он делает это через FileTransactionLog, который обрабатывает создание временной копии и отслеживает, какие файлы были скопированы. В случае исключения он копирует исходные файлы обратно, а в случае успеха удаляет временные копии.
Это работает, потому что мы нашли более естественное разложение задачи. Ранее мы смешивали логику о том, как установить приложение, с логикой о том, как восстановить отмененную установку. Теперь журнал транзакций обрабатывает все подробности о временных файлах и бухгалтерии, и функция может сосредоточиться на базовом алгоритме.
Я подозреваю, что ваше дело в одной лодке. Вам нужно извлечь элементы бухгалтерского учета в какой-то объект, чтобы ваша сложная задача могла быть выражена более просто и элегантно.
источник
Поскольку единственным явным недостатком метода 1 является его неоптимальная модель использования, я думаю, что лучшим решением является продвижение инкапсуляции на один шаг дальше: использование класса, но также и предоставление автономной функции, которая только создает объект, вызывает метод и возвращает :
При этом у вас будет наименьший возможный интерфейс к вашему коду, и класс, который вы используете внутри, действительно станет просто деталью реализации вашей публичной функции. Код вызова не должен знать о вашем внутреннем классе.
источник
С одной стороны, вопрос как-то не зависит от языка; но с другой стороны, реализация зависит от языка и его парадигм. В данном случае это Python, который поддерживает несколько парадигм.
Помимо ваших решений, существует также возможность более полного выполнения операций без сохранения состояния, например
Все сводится к
Но -Если ваша кодовая база является ООП, она нарушает согласованность, если вдруг некоторые части написаны в (более) функциональном стиле.
источник
Зачем дизайн, когда я могу быть программированием?
Я предлагаю противоположный взгляд на ответы, которые я прочитал. С этой точки зрения все ответы и, честно говоря, даже сам вопрос сосредоточены на механике кодирования. Но это проблема дизайна.
Да, так как это имеет смысл для вашего дизайна. Это может быть сам по себе класс или часть какого-то другого класса, или его поведение может быть распределено между классами.
Объектно-ориентированный дизайн - это сложность
Задача ОО состоит в том, чтобы успешно создавать и поддерживать большие, сложные системы, инкапсулируя код в терминах самой системы. «Правильный» дизайн говорит, что все в каком-то классе.
ОО-дизайн, естественно, управляет сложностью в первую очередь с помощью целевых классов, которые придерживаются принципа единой ответственности. Эти классы дают структуру и функциональность во всем дыхании и глубине системы, взаимодействуют и интегрируют эти измерения.
Учитывая это, часто говорят, что функции, свисающие с системы - слишком распространенный общий класс полезности - это запах кода, указывающий на недостаточный дизайн. Я склонен согласиться.
источник
Почему бы не сравнить ваши потребности с чем-то, что существует в стандартной библиотеке Python, а затем посмотреть, как это реализовано?
Обратите внимание, что если вам не нужен объект, вы все равно можете определять функции внутри функций. В Python 3 есть новое
nonlocal
объявление, позволяющее вам изменять переменные в вашей родительской функции.Возможно, вам все еще будет полезно иметь несколько простых закрытых классов внутри вашей функции для реализации абстракций и операций по уборке.
источник
nonlocal
нигде не нахожусь в моих установленных в настоящее время библиотеках Python. Возможно, вы можете позаботиться оtextwrap.py
том, чтобы иметьTextWrapper
класс, но такжеdef wrap(text)
функцию, которая просто создаетTextWrapper
экземпляр, вызывает.wrap()
метод и возвращает его. Так что используйте класс, но добавьте несколько удобных функций.