Клас, який нічого не представляє - це правильно?


42

Я просто розробляю свою заявку, і я не впевнений, чи правильно я розумію SOLID і OOP. Класи повинні робити одну справу і робити це добре, але з іншого боку, вони повинні представляти реальні об'єкти, з якими ми працюємо.

У моєму випадку я роблю вилучення функції на наборі даних, а потім роблю аналіз машинного навчання. Я припускаю, що я міг би створити три класи

  1. FeatureExtractor
  2. Набір даних
  3. Аналізатор

Але клас FeatureExtractor нічого не представляє, він робить щось, що робить його більше рутиною, ніж класом. Він буде мати лише одну функцію, яка буде використовуватися: extra_features ()

Чи правильно створювати класи, які не являють собою одне, але роблять одне?

EDIT: не впевнений, чи це має значення, але я використовую Python

І якби extra_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

13
Це виглядає цілком чудово як функція. Зважаючи на три речі, які ви вказали як модулі, це нормально, і ви можете розмістити їх у різних файлах, але це не означає, що вони повинні бути класами.
Бергі

31
Будьте в курсі, що в Python цілком поширене і прийнятне використання підходів, що не входять до ООС.
jpmc26

13
Вас може зацікавити Дизайн, керований доменом. "Класи повинні представляти об'єкти з реального світу" насправді помилкові ... вони повинні представляти об'єкти в домені . Домен часто сильно пов'язаний з реальним світом, але залежно від програми деякі речі можуть бути або не вважатись об'єктами, або деякі речі, які "насправді" є окремими, можуть бути зв'язаними або ідентичними всередині домену програми.
Бакуріу

1
Коли ви ознайомитесь із OOP, я думаю, що ви побачите, що класи дуже рідко відповідають один одному в суті з реальними сутностями. Наприклад, ось нарис, який стверджує, що намагання втиснути всю функціональність, пов’язану з реальною сутністю в одному класі, дуже часто є анти-шаблоном: programmer.97things.oreilly.com/wiki/index.php/…
Кевін - Відновіть Моніку

1
"вони повинні представляти реальні об'єкти, з якими ми працюємо." не обов'язково. Багато мов мають клас потоку, що представляє потік байтів, що є абстрактним поняттям, а не «реальним об’єктом». Технічно файлова система теж не є "реальним об'єктом", це лише концепція, але іноді існують класи, що представляють файлову систему або її частину.
Фарап

Відповіді:


96

Заняття повинні робити 1 річ і робити це добре

Так, це взагалі хороший підхід.

але з іншого боку, вони повинні представляти реальний об'єкт, з яким ми працюємо.

Ні, це звичайне непорозуміння ІМХО. Хороший доступ початківців до ООП часто "починається з предметів, що представляють речі з реального світу" , це правда.

Однак не варто зупинятися на цьому !

Класи можуть (і повинні) використовуватися для структурування вашої програми різними способами. Моделювання об'єктів із реального світу є одним із аспектів цього, але не єдиним. Створення модулів або компонентів для конкретного завдання - ще один розумний випадок використання класів. "Витяжка функцій" - це, мабуть, такий модуль, і навіть він містить лише один публічний метод extract_features(), я був би здивований, якщо, якщо він також не містить багато приватних методів і, можливо, деякий загальний стан. Тож наявність класу FeatureExtractorзапровадить природне місце для цих приватних методів.

Побічна примітка: у таких мовах, як Python, які підтримують окрему концепцію модуля, також можна використовувати модуль FeatureExtractorдля цього, але в контексті цього питання це IMHO незначна різниця.

Більше того, "екстрактор можливостей" можна уявити як "людина або бот, який витягує функції". Це абстрактна "річ", можливо, не річ, яку ви знайдете в реальному світі, але саме ім'я є корисною абстракцією, яка дає кожному уявлення про те, яка відповідальність цього класу. Тож я не згоден, що цей клас нічого не представляє.


33
Мені особливо подобається, що ви згадали про питання внутрішнього стану. Я часто вважаю, що це є вирішальним фактором для того, чи я роблю щось в класі, чи в функції в Python.
David Z

17
Ви, здається, рекомендуєте "класицит". Не робіть для цього класу, це антипатерн у стилі Java. Якщо це функція, зробіть її функцією. Внутрішній стан не має значення: функції можуть мати це (через закриття).
Конрад Рудольф

25
@KonradRudolph: вам здається, ви пропустили, що справа не в тому, щоб зробити одну функцію. Мова йде про фрагмент коду, який вимагає декількох функцій, загальної назви та, можливо, деякого загального стану. Використовувати модуль для цього може бути розумним у мовах з концепцією модуля крім класів.
Док Браун

8
@DocBrown Мені доведеться не погодитися. Це клас з одним публічним методом. API мудрий, це не відрізняється від функції. Детальну інформацію див. У моїй відповіді. Використання одно методи методів може бути виправданим, якщо вам потрібно створити певний тип для представлення стану. Але це не так (і навіть тоді функції іноді можна використовувати).
Конрад Рудольф

6
@DocBrown Я починаю бачити, що ви маєте на увазі: ви говорите про функції, які викликаються зсередини extract_features? Я начебто припустив, що вони були громадськими функціями з іншого місця. Досить чесно, я погоджуюся, що якщо вони приватні, то, ймовірно, вони повинні перейти в модуль (але все ж: не клас, якщо вони не мають загального стану) разом з extract_features. (Це означає, що ви, звичайно, можете оголосити їх локально всередині цієї функції.)
Конрад Рудольф

44

Док Браун - це пляма: класам не потрібно представляти об'єкти реального світу. Вони просто повинні бути корисними . Заняття - це принципово лише додаткові типи, а що відповідає intабо stringвідповідає в реальному світі? Це абстрактні описи, а не конкретні, відчутні речі.

Однак, ваша справа особлива. Відповідно до вашого опису:

І якби extra_features () виглядав би так: чи варто створити спеціальний клас для проведення цього методу?

Ви абсолютно праві: якщо ваш код такий, як показано, немає сенсу перетворювати його в клас. Існує відома розмова, яка стверджує, що подібне використання класів в Python є запахом коду, і що простих функцій часто достатньо. Ваш випадок є прекрасним прикладом цього.

Надмірне використання класів пов'язане з тим, що OOP став мейнстрімом з Java у 90-х роках. На жаль, Java в той час не вистачало декількох сучасних мовних особливостей (таких як закриття), що означає, що багато понять було важко або неможливо виразити без використання класів. Наприклад, до недавнього часу в Java не можна було використовувати методи, що підтримують стан (тобто закриття). Натомість вам довелося написати клас, щоб перенести стан, і який викрив єдиний метод (називається щось на зразок invoke).

На жаль, цей стиль програмування став популярним далеко за межами Java (частково завдяки впливовій книжці з інженерних програм, яка інакше є дуже корисною), навіть мовами, які не потребують таких способів вирішення.

У Python класи, очевидно, є дуже важливим інструментом, і його слід використовувати вільно. Але вони не єдиний інструмент, і немає причин використовувати їх там, де вони не мають сенсу. Поширена помилка, що вільним функціям немає місця в OOP.


Було б корисно додати, що якщо функції, названі в прикладі, насправді є приватними функціями, інкапсуляція їх у модуль чи клас була б цілком доречною. В іншому випадку я цілком згоден.
Лелієль

11
Також корисно пам’ятати, що «ООП» не означає «написати купу занять». Функції - це об'єкти в Python, тому немає необхідності вважати це "не OOP". Швидше, це просто повторне використання вбудованих типів / класів, а "повторне використання" є одним із святих граалів в програмуванні. Обгортання цього класу заважатиме повторному використанню, оскільки нічого не буде сумісним із цим новим API (якщо __call__не визначено, у такому випадку просто використовуйте функцію!)
Warbo

3
Також на тему "класи проти вільно стоячих функцій": eev.ee/blog/2013/03/03/…
Joker_vD

1
Чи не так, хоча в Python "вільні функції" також є об'єктами з типом, які піддають метод __call__()? Це насправді настільки відрізняється від анонімного внутрішнього класу? Синтаксично, впевнено, але з мовної конструкції, це здається менш значним відмінністю, ніж ви представлені тут.
JimmyJames

1
@JimmyJames Правильно. Вся справа в тому, що вони пропонують однаковий функціонал для конкретних цілей, але простіші у використанні.
Конрад Рудольф

36

Я просто розробляю свою заявку, і я не впевнений, чи правильно я розумію SOLID і OOP.

Був у цьому понад 20 років, і я теж не впевнений.

Заняття повинні робити 1 річ і робити це добре

Тут важко помилитися.

вони повинні представляти реальні об'єкти, з якими ми працюємо.

О, справді? Дозвольте мені познайомити вас з однією найбільш популярним і успішним класом всіх часів: String. Ми використовуємо його для тексту. А об'єкт реального світу, який він представляє, це:

Рибалка тримає 10 риб, підвішених на мотузці

Чому ні, не всі програмісти одержимі риболовлею. Тут ми використовуємо щось, що називається метафорою. Добре робити моделі речей, які насправді не існують. Це ідея, яка повинна бути зрозумілою. Ви створюєте образи у свідомості своїх читачів. Ці зображення не повинні бути справжніми. Просто зрозумів легко.

Гарний дизайн OOP кластеризує повідомлення (методи) навколо даних (стану), так що реакції на ці повідомлення можуть змінюватися залежно від цих даних. Якщо робити це моделює якусь річ справжнього світу, шикарний. Якщо ні, то добре. Поки це має сенс для читача, це добре.

Тепер впевнений, ви могли подумати про це так:

святкові листи, відмінені від рядка, читають "ПЕРЕМОЖИ РОБИТИ!"

але якщо ви думаєте, що це повинно існувати в реальному світі, перш ніж ви можете використовувати метафору, добре, що ваша кар’єра програмування передбачає багато мистецтв та ремесел.


1
Досить рядок фотографій ...
Zev Spitz

На даний момент я не впевнений, що "рядок" є навіть метафоричним. Він просто має специфічне значення в області програмування, як і такі слова, як classі tableта column...
Kyralessa

@Kyralessa ви ефір навчите новачка метафорі або ви дозволите їм це магія. Врятуй мене, будь ласка, від кодерів, які вірять у магію.
candied_orange

6

Остерігайся! Ніде SOLID не каже, що клас повинен "робити лише одне". Якби це було так, класи б коли-небудь мали єдиний метод, і різниці між класами та функціями насправді не було б.

SOLID каже, що клас повинен представляти собою єдину відповідальність . Вони подібні до відповідальності осіб у колективі: водій, адвокат, викрадач, графічний дизайнер тощо. Кожна з цих осіб може виконувати кілька (пов'язаних) завдань, але всі стосуються однієї відповідальності.

Суть цього в тому, що - якщо в вимогах є зміна, вам в ідеалі потрібно лише змінити один клас. Це просто полегшує розуміння коду, легше змінювати та зменшує ризик.

Не існує правила, що об’єкт повинен представляти "реальну річ". Це просто культовий культ, оскільки ОО спочатку було винайдено для використання в симуляціях. Але ваша програма не є симуляцією (мало сучасних програм OO), тому це правило не застосовується. Поки кожен клас несе чітко визначену відповідальність, ви повинні бути добре.

Якщо клас справді має лише один метод, а клас не має жодного стану, ви можете розглянути можливість створення його окремої функції. Це певно добре і відповідає принципам KISS та YAGNI - не потрібно складати клас, якщо ви можете вирішити його за допомогою функції. З іншого боку, якщо у вас є підстави вважати, що вам може знадобитися внутрішній стан або кілька реалізацій, ви також можете перетворити його на клас попереднього рівня. Тут вам доведеться скористатися своїм найкращим судженням.


+1 для "не потрібно складати клас, якщо ви можете вирішити його за допомогою функції". Іноді комусь потрібно говорити правду.
tchrist

5

Чи правильно створювати класи, які не являють собою одне, але роблять одне?

Загалом це нормально.

Без трохи більш конкретного опису, що FeatureExtractorсаме повинен робити клас, важко сказати.

У будь-якому випадку, навіть якщо FeatureExtractorвикриває лише публічну extract_features()функцію, я міг би подумати про її налаштування з Strategyкласом, який визначає, як саме слід робити видобуток.

Інший приклад - клас з функцією Шаблон .

І є більше моделей поведінкового дизайну , які базуються на моделях класів.


Як ви додали код для уточнення.

І якби extra_features () виглядав би так: чи варто створити спеціальний клас для проведення цього методу?

Лінія

 sent = SentimentAnalyser()

саме містить те, що я мав на увазі, що ви могли налаштувати клас із стратегією .

Якщо у вас є інтерфейс для цього SentimentAnalyserкласу, ви можете передати його FeatureExtractorкласу в його точці побудови, замість того, щоб безпосередньо з'єднуватися з цією конкретною реалізацією у вашій функції.


2
Я не бачу причини додавати складність ( FeatureExtractorклас) просто для того, щоб ввести ще більшу складність (інтерфейс для SentimentAnalyserкласу). Якщо розв'язка є бажаною, тоді функція extract_featuresможе сприймати get_sentimentфункцію як аргумент ( loadвиклик, здається, не залежить від функції і викликає лише його ефекти). Також зауважте, що Python не має / заохочує інтерфейси.
Варбо

1
@warbo - навіть якщо ви подаєте функцію в якості аргументу, роблячи її функцією, ви обмежуєте потенційні реалізації тими, які впишуться у формат функції, але якщо є необхідність керувати стійким станом між одним викликом і далі (наприклад, a CacheingFeatureExtractorабо a TimeSeriesDependentFeatureExtractor), тоді об'єкт був би набагато кращим. Просто тому , що немає ніякої необхідності для об'єкта в даний момент не означає , що ніколи не буде.
Жуль

3
@Jules По-перше, вам це не потрібно (YAGNI), по-друге, функції Python можуть посилатися на стійкий стан (закриття), якщо він вам потрібен (ви цього не збираєтесь), по-третє, використання функції нічого не обмежує, оскільки будь-який об'єкт з __call__метод буде сумісним, якщо він вам потрібен (ви цього не збираєтесь), по-четверте, додавши обгортку, як FeatureExtractorви робите код несумісним із усіма написаними кодами, коли-небудь написаними (якщо ви не надаєте __call__метод, і в цьому випадку функція буде явно простішою )
Варбо

0

Шаблони та всі вигадливі мови / поняття в сторону: те, що ви натрапили, це робота або пакетний процес .

Зрештою, навіть чистою програмою ООП потрібно якось керувати чимось, щоб реально виконати роботу; має бути якось точка входу. Наприклад, у шаблоні MVC ontroller отримує події клацання тощо від GUI, а потім оркеструє інші компоненти. У класичних інструментах командного рядка "головна" функція робила би те саме.

Чи правильно створювати класи, які не являють собою одне, але роблять одне?

Ваш клас представляє сутність, яка щось робить і оркеструє все інше. Ви можете назвати його контролером , роботою , головним або що завгодно.

І якби extra_features () виглядав би так: чи варто створити спеціальний клас для проведення цього методу?

Це залежить від обставин (і я не знайомий зі звичним способом, як це робиться в Python). Якщо це лише невеликий інструмент командного рядка з одним ударом, то метод замість класу повинен бути добре. Перша версія вашої програми може точно піти з методу. Якщо згодом ви виявите, що в кінцевому підсумку ви знайдете десятки таких методів, можливо, навіть із змішаними глобальними змінними, тоді саме час перетворювати на класи.


3
Зауважте, що у подібних сценаріях виклик самостійних процедур "методами" може бути заплутаним. Більшість мов, включаючи Python, називають їх "функціями". "Методи" - це функції / процедури, які пов'язані з певним екземпляром класу, що протилежно вашому використанню терміна :)
Warbo

Правда, @Warbo. Або ми могли б назвати їх процедурою, або виправданням, або підпунктом, або ...; і вони можуть бути методами класу (sic), не пов'язаними з екземпляром. Я сподіваюся, що ніжний читач зможе абстрагуватися від наміченого сенсу. :)
AnoE

@Warbo це добре знати! Більшість навчальних матеріалів, з якими я стикався, стверджує, що терміни функція та метод взаємозамінні, і що це лише перевага, що залежить від мови.
Дом

@Dom Загалом a ("чиста") "функція" - це відображення від вхідних значень до вихідних значень; "процедура" - це функція, яка також може викликати ефекти (наприклад, видалення файлу); обидва статично розсилаються (тобто шукаються в лексичному просторі). "Метод" - це функція або (зазвичай) процедура, яка динамічно розсилається (шукається вгору) від значення (званого "об'єктом"), яке автоматично прив'язується до (неявного thisабо явного self) аргументу методу. Методи об'єкта взаємно "відкрито" рекурсують, тому заміна fooвикликає всі self.fooдзвінки використовувати цю заміну.
Варбо

0

Ми можемо розглядати OOP як моделювання поведінки системи. Зауважте, що система не повинна існувати у "реальному світі", хоча метафори реального світу іноді можуть бути корисними (наприклад, "трубопроводи", "фабрики" тощо).

Якщо наша бажана система є надто складною для моделювання всіх відразу, ми можемо розбити її на більш дрібні шматки та моделювати ті ("проблемний домен"), що може включати подальше руйнування, і так далі, поки ми не знайдемо деталі, поведінка яких збігається (більш-менш) об'єкта якогось вбудованого мовного об'єкта, наприклад, числа, рядка, списку тощо.

Після того, як у нас з’являться ці прості фрагменти, ми можемо їх об'єднати, щоб описати поведінку більших шматочків, які ми можемо поєднати разом у ще більші шматки, і так далі, поки не зможемо описати всі компоненти домену, необхідні для цілого система.

Саме ця фаза "поєднання разом" ми можемо написати кілька класів. Ми пишемо класи, коли немає існуючого об'єкта, який веде себе так, як ми хочемо. Наприклад, наш домен може містити "foos", колекції foos під назвою "bars" та колекції барів під назвою "bazs". Ми можемо помітити, що foos досить простий для моделювання струн, тому ми це робимо. Ми виявляємо, що бари вимагають, щоб їх вміст підкорявся якомусь певному обмеженню, яке не відповідає нічого, що надає Python, і в такому випадку ми можемо написати новий клас для забезпечення цього обмеження. Можливо, у баз немає таких особливостей, тому ми можемо просто представити їх списком.

Зауважте, що ми могли б написати новий клас для кожного з цих компонентів (футів, барів і баз), але нам цього не потрібно, якщо вже є щось з правильною поведінкою. Зокрема, щоб клас був корисним, він повинен щось «надати» (дані, методи, константи, підкласи тощо), тому навіть якщо у нас є багато шарів спеціальних класів, ми зрештою повинні використовувати якусь вбудовану функцію; Наприклад, якщо ми написали новий клас для foos, він, ймовірно, просто містив би рядок, то чому б не забути foo-клас і щоб бар-клас містив ці рядки замість цього? Майте на увазі, що класи - це також вбудований об’єкт, вони просто особливо гнучкі.

Отримавши нашу доменну модель, ми можемо взяти деякі конкретні екземпляри цих фрагментів і впорядкувати їх на "моделювання" конкретної системи, яку ми хочемо моделювати (наприклад, "система машинного навчання для ...").

Після того, як у нас є таке моделювання, ми зможемо запустити його та пристосуємось до престо, ми маємо працюючу (моделювання а) машинної системи навчання для ... (або будь-якого іншого, що ми моделювали).


Тепер у вашій конкретній ситуації ви намагаєтеся моделювати поведінку компонента "екстрактор функцій". Питання в тому, чи є вбудовані об’єкти, які ведуть себе як "екстрактор функцій", чи вам потрібно буде розбити його на більш прості речі? Схоже, екстрактори функціонують дуже схоже на об'єкти функцій, тому я думаю, вам було б добре використовувати їх як вашу модель.


Одне, що слід пам’ятати, вивчаючи подібні поняття, - це те, що різні мови можуть надавати різні вбудовані функції та об’єкти (і, звичайно, деякі навіть не використовують термінологію, як «об’єкти»!). Отже, рішення, що мають сенс для однієї мови, можуть бути менш корисними для іншої (це може стосуватися навіть різних версій однієї мови!).

Історично багато літератури OOP (особливо "дизайнерські зразки") зосереджувались на Java, що сильно відрізняється від Python. Наприклад, класи Java не є об’єктами, у Java не було функціональних об'єктів ще зовсім недавно, у Java є сувора перевірка типів (що заохочує інтерфейси та підкласифікацію), в той час як Python заохочує введення качок, у Java немає об'єктів модулів, цілі Java / плаває / тощо. не є об'єктами, метапрограмування / інтроспекція на Java вимагає "рефлексії" тощо.

Я не намагаюся вибирати Java (як інший приклад, багато теорії OOP обертається навколо Smalltalk, який знову дуже відрізняється від Python), я просто намагаюся зазначити, що ми повинні дуже ретельно думати про контекст і обмеження, в яких розроблені рішення та чи відповідає це ситуації, в якій ми знаходимося.

У вашому випадку об’єкт функції здається хорошим вибором. Якщо вам цікаво, чому в деякому керівництві "найкраща практика" не згадуються функціональні об'єкти як можливе рішення, це може бути просто тому, що ці вказівки були написані для старих версій Java!


0

Прагматично кажучи, коли у мене є "різна річ, яка робить щось важливе і має бути окремим", і у неї немає чіткого дому, я розміщую це в Utilitiesрозділі і використовую це як мою конвенцію про іменування. тобто. FeatureExtractionUtility.

Забудьте про кількість методів у класі; сьогодні єдиному методу може знадобитися перейти до п’яти методів завтра. Важливе значення має чітка та послідовна організаційна структура, наприклад, зона комунальних служб для різних колекцій функцій.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.