Я просто проектирую свое приложение, и я не уверен, правильно ли я понимаю SOLID и OOP. Классы должны делать одну вещь и делать это хорошо, но, с другой стороны, они должны представлять реальные объекты, с которыми мы работаем.
В моем случае я выполняю извлечение признаков из набора данных, а затем делаю анализ машинного обучения. Я предполагаю, что я мог бы создать три класса
- FeatureExtractor
- DataSet
- анализатор
Но класс FeatureExtractor ничего не представляет, он делает то, что делает его скорее рутиной, чем классом. Он будет иметь только одну функцию, которая будет использоваться: extract_features ()
Правильно ли создавать классы, которые не представляют одну вещь, а делают одну?
РЕДАКТИРОВАТЬ: не уверен, если это имеет значение, но я использую Python
И если extract_features () будет выглядеть так: стоит ли создавать специальный класс для хранения этого метода?
def extract_features(df):
extr = PhrasesExtractor()
extr.build_vocabulary(df["Text"].tolist())
sent = SentimentAnalyser()
sent.load()
df = add_features(df, extr.features)
df = mark_features(df, extr.extract_features)
df = drop_infrequent_features(df)
df = another_processing1(df)
df = another_processing2(df)
df = another_processing3(df)
df = set_sentiment(df, sent.get_sentiment)
return df
источник
Ответы:
Да, это вообще хороший подход.
Нет, это ИМХО распространенное недоразумение. Хороший доступ новичка к ООП часто «начинается с объектов, представляющих вещи из реального мира» , это правда.
Однако не стоит останавливаться на этом !
Классы могут (и должны) использоваться для структурирования вашей программы различными способами. Моделирование объектов из реального мира является одним из аспектов этого, но не единственным. Создание модулей или компонентов для конкретной задачи - еще один разумный пример использования классов. «Экстрактор функций», вероятно, является таким модулем, и даже если он содержит только один публичный метод
extract_features()
, я был бы удивлен, если бы он также не содержал много частных методов и, возможно, некоторое общее состояние. Таким образом, наличие классаFeatureExtractor
представит естественное местоположение для этих частных методов.Примечание: в таких языках, как Python, которые поддерживают концепцию отдельного модуля,
FeatureExtractor
для этого также можно использовать модуль , но в контексте этого вопроса ИМХО это незначительная разница.Кроме того, «экстрактор признаков» можно представить как «человека или бота, который извлекает функции». Это абстрактная «вещь», может быть, не та, которую вы найдете в реальном мире, но само название является полезной абстракцией, которая дает каждому представление о том, какова ответственность этого класса. Поэтому я не согласен с тем, что этот класс не «представляет что-либо».
источник
extract_features
? Я просто предположил, что это публичные функции из другого места. Справедливо, я согласен, что если они являются частными, то, вероятно, они должны войти в модуль (но все же: не в класс, если они не разделяют состояние) вместе сextract_features
. (Тем не менее, вы, конечно, могли бы объявить их локально внутри этой функции.)Док Браун очень точен: классы не должны представлять объекты реального мира. Они просто должны быть полезными . Классы - это в основном просто дополнительные типы, и что соответствует
int
илиstring
соответствует в реальном мире? Это абстрактные описания, а не конкретные, осязаемые вещи.Тем не менее, ваш случай особенный. По вашему описанию:
Вы абсолютно правы: если ваш код такой, как показано, нет смысла превращать его в класс. Существует известная дискуссия, в которой утверждается, что такое использование классов в Python является запахом кода, и что простых функций часто достаточно. Ваш случай является прекрасным примером этого.
Злоупотребление классами связано с тем, что ООП стал распространенным явлением в Java в 1990-х годах. К сожалению, в то время в Java отсутствовали некоторые современные языковые возможности (такие как замыкания), что означало, что многие понятия было трудно или невозможно выразить без использования классов. Например, в Java до недавнего времени было невозможно иметь методы, которые переносят состояние (например, замыкания). Вместо этого вам пришлось написать класс для переноса состояния, в котором был представлен единственный метод (называемый чем-то вроде
invoke
).К сожалению, этот стиль программирования стал популярным далеко за пределами Java (отчасти благодаря влиятельной книге по разработке программного обеспечения, которая в остальном очень полезна), даже в языках, которые не требуют таких обходных путей.
В Python классы, очевидно, являются очень важным инструментом и должны использоваться свободно. Но они не единственный инструмент, и нет причин использовать их там, где они не имеют смысла. Это распространенное заблуждение, что свободным функциям нет места в ООП.
источник
__call__
не определено, в этом случае просто используйте функцию!)__call__()
? Это действительно так отличается от анонимного экземпляра внутреннего класса? Синтаксически, конечно, но из языкового дизайна, это кажется менее значимым отличием, чем вы здесь представляете.Я был в этом более 20 лет, и я тоже не уверен.
Трудно ошибиться здесь.
Да неужели? Позвольте мне познакомить вас с одной самыми популярным и успешным классом всех времен:
String
. Мы используем это для текста. И объект реального мира, который он представляет, таков:Почему нет, не все программисты помешаны на рыбалке. Здесь мы используем то, что называется метафорой. Можно создавать модели вещей, которые на самом деле не существуют. Это идея, которая должна быть ясной. Вы создаете образы в сознании ваших читателей. Эти изображения не должны быть реальными. Просто понял легко.
Хороший дизайн ООП группирует сообщения (методы) вокруг данных (состояния), так что реакции на эти сообщения могут варьироваться в зависимости от этих данных. Если делать это моделирует что-то реальное, просто элегантное. Если нет, то ладно. Пока это имеет смысл для читателя, это нормально.
Теперь, конечно, вы можете думать об этом так:
но если вы думаете, что это должно существовать в реальном мире, прежде чем вы сможете использовать метафору, то ваша карьера программиста будет включать в себя множество декоративно-прикладного искусства.
источник
class
иtable
иcolumn
...Осторожно! Нигде SOLID не говорит, что класс должен «делать только одну вещь». Если бы это было так, у классов был бы только один метод, и между классами и функциями не было бы никакой разницы.
SOLID говорит, что класс должен представлять одну ответственность . Это что-то вроде ответственности людей в команде: водитель, адвокат, карманник, графический дизайнер и т. Д. Каждый из этих людей может выполнять несколько (связанных) задач, но все они относятся к одной ответственности.
Суть этого в том, что в случае изменения требований в идеале вам нужно всего лишь изменить один класс. Это просто делает код легче для понимания, легче модифицировать и снижает риск.
Не существует правила, согласно которому объект должен представлять собой «реальную вещь». Это всего лишь культовый груз, поскольку ОО изначально был изобретен для использования в симуляциях. Но ваша программа не является симуляцией (мало современных приложений OO), поэтому это правило не применяется. Пока у каждого класса есть четко определенная ответственность, у вас все будет хорошо.
Если у класса действительно есть только один метод, а у класса нет состояния, вы можете рассмотреть возможность сделать его автономной функцией. Это, конечно, хорошо и следует принципам KISS и YAGNI - нет необходимости создавать класс, если вы можете решить его с помощью функции. С другой стороны, если у вас есть основания полагать, что вам может понадобиться внутреннее состояние или несколько реализаций, вы также можете сделать его классом заранее. Вы должны будете использовать свое лучшее суждение здесь.
источник
В целом это нормально.
FeatureExtractor
Трудно сказать, без точного описания того, что именно должен делать класс.В любом случае, даже если
FeatureExtractor
выставляется только публичнаяextract_features()
функция, я мог бы подумать о том, чтобы настроить ее с помощьюStrategy
класса, который определяет, как именно следует выполнить извлечение.Другой пример - класс с функцией Template .
И есть больше Поведенческих Шаблонов Проекта , которые основаны на моделях класса.
Как вы добавили код для уточнения.
Линия
именно то, что я имел в виду, что вы можете настроить класс с помощью стратегии .
Если у вас есть интерфейс для этого
SentimentAnalyser
класса, вы можете передать егоFeatureExtractor
классу в точке его создания, вместо непосредственного соединения с этой конкретной реализацией в вашей функции.источник
FeatureExtractor
класс) просто для того, чтобы еще больше усложнить (интерфейс дляSentimentAnalyser
класса). Если развязка желательна, то функцияextract_features
может приниматьget_sentiment
функцию в качестве аргумента (load
вызов кажется независимым от функции и вызывается только для ее эффектов). Также обратите внимание, что Python не имеет / поощряет интерфейсы.CacheingFeatureExtractor
или aTimeSeriesDependentFeatureExtractor
) тогда объект будет гораздо лучше подходить. Просто потому , что нет никакой необходимости для объекта в данный момент не означает , что никогда не будет.__call__
Метод будет совместим, если вам это нужно (вы не собираетесь), в-четвертых, добавив оболочку, как будтоFeatureExtractor
вы делаете код несовместимым со всем другим когда-либо написанным кодом (если только вы не предоставите__call__
метод, в этом случае функция будет явно проще). )За исключением паттернов и всех причудливых языков / концепций: вы наткнулись на работу или пакетный процесс .
В конце концов, даже чистая программа ООП должна как-то чем-то руководствоваться, чтобы фактически выполнять работу; как-то должна быть точка входа. Например, в шаблоне MVC контроллер «C» получает события щелчка и т. Д. От GUI, а затем организует другие компоненты. В классических инструментах командной строки «главная» функция будет делать то же самое.
Ваш класс представляет сущность, которая делает что-то и организует все остальное. Вы можете назвать это « Контроллер» , « Работа» , « Главное» или как вам на ум.
Это зависит от обстоятельств (и я не знаком с обычным способом, которым это делается в Python). Если это всего лишь небольшой инструмент командной строки с одним выстрелом, тогда метод вместо класса должен подойти. Первая версия вашей программы наверняка поможет. Если позже вы обнаружите, что в итоге вы получили десятки таких методов, возможно, даже с глобальными переменными, то пришло время реорганизовать классы.
источник
this
или явнымself
) аргументом метода. Методы объекта взаимно «открыто» рекурсивны, поэтому заменаfoo
вызывает всеself.foo
вызовы для использования этой замены.Мы можем думать об ООП как о моделировании поведения системы. Обратите внимание, что система не обязательно должна существовать в «реальном мире», хотя метафоры реального мира иногда могут быть полезны (например, «конвейеры», «фабрики» и т. Д.).
Если наша желаемая система слишком сложна для одновременного моделирования, мы можем разбить ее на более мелкие части и смоделировать их («проблемная область»), что может потребовать дальнейшего разрушения и так далее, пока мы не доберемся до частей, поведение которых соответствует (более или менее) для некоторого объекта встроенного языка, такого как число, строка, список и т. д.
Когда у нас есть эти простые части, мы можем объединить их вместе, чтобы описать поведение больших частей, которые мы можем объединить в еще большие части, и так далее, пока мы не сможем описать все компоненты домена, которые необходимы для целого система.
Именно на этой стадии «объединения вместе» мы могли бы написать несколько классов. Мы пишем классы, когда нет существующего объекта, который ведет себя так, как мы хотим. Например, наш домен может содержать «foos», коллекции foos, называемые «bars», и коллекции баров, называемые «bazs». Мы можем заметить, что foos достаточно просты для моделирования со строками, поэтому мы делаем это. Мы находим, что бары требуют, чтобы их содержимое подчинялось определенному ограничению, которое не соответствует ни одному из представленных Python, и в этом случае мы могли бы написать новый класс для обеспечения соблюдения этого ограничения. Возможно, у баз нет таких особенностей, поэтому мы можем просто представить их списком.
Обратите внимание, что мы могли бы написать новый класс для каждого из этих компонентов (foos, bars и bazs), но нам это не нужно, если уже есть что-то с правильным поведением. В частности, для того, чтобы класс был полезен, он должен что-то «предоставлять» (данные, методы, константы, подклассы и т. Д.), Поэтому даже если у нас много слоев пользовательских классов, мы в конечном итоге должны использовать некоторую встроенную функцию; например, если бы мы написали новый класс для foos, он, вероятно, просто содержал бы строку, так почему бы не забыть класс foo и вместо этого иметь класс bar, содержащий эти строки? Имейте в виду, что классы также являются встроенным объектом, они просто очень гибкие.
Получив нашу модель предметной области, мы можем взять некоторые конкретные экземпляры этих частей и упорядочить их в «имитацию» конкретной системы, которую мы хотим смоделировать (например, «систему машинного обучения для ...»).
Как только у нас будет это моделирование, мы сможем запустить его, и, привет, у нас есть работающая (симуляция) система машинного обучения для ... (или чего-то еще, что мы моделировали)
Теперь в вашей конкретной ситуации вы пытаетесь смоделировать поведение компонента «Извлечение возможностей». Вопрос в том, есть ли какие-либо встроенные объекты, которые ведут себя как «экстрактор возможностей», или вам нужно будет разбить его на более простые вещи? Похоже, что экстракторы объектов ведут себя очень похоже на функциональные объекты, поэтому я думаю, что вы можете использовать их в качестве модели.
При изучении такого рода концепций следует иметь в виду, что разные языки могут предоставлять разные встроенные функции и объекты (и, конечно, некоторые даже не используют терминологию, такую как «объекты»!). Следовательно, решения, которые имеют смысл на одном языке, могут быть менее полезны на другом (это может даже относиться к различным версиям одного и того же языка!).
Исторически большая часть литературы по ООП (особенно «шаблоны проектирования») была сосредоточена на Java, которая довольно сильно отличается от Python. Например, классы Java не являются объектами, до недавнего времени в Java не было функциональных объектов, в Java есть строгая проверка типов (что поощряет интерфейсы и создание подклассов), в то время как в Python поддерживается типизирование утки, в Java нет объектов-модулей, целые числа Java / поплавки / и т.д.. не являются объектами, метапрограммирование / самоанализ в Java требует «рефлексии» и так далее.
Я не пытаюсь выбрать Java (в качестве другого примера, многие теории ООП вращаются вокруг Smalltalk, который опять-таки сильно отличается от Python), я просто пытаюсь указать, что мы должны очень тщательно продумать контекст и ограничения, в которых были разработаны решения, и соответствует ли это ситуации, в которой мы находимся.
В вашем случае функциональный объект кажется хорошим выбором. Если вам интересно, почему в некоторых рекомендациях «передового опыта» не упоминаются функциональные объекты как возможное решение, возможно, это просто потому, что эти рекомендации были написаны для старых версий Java!
источник
Прагматически говоря, когда у меня есть «разные вещи, которые делают что-то важное и должны быть отделены», и у них нет ясного дома, я помещаю это в
Utilities
раздел и использую это как свое соглашение об именах. то есть.FeatureExtractionUtility
,Забудьте о количестве методов в классе; один метод сегодня может увеличиться до пяти методов завтра. Важна четкая и последовательная организационная структура, например, служебная зона для разных наборов функций.
источник