#python #ооп
Я изучаю/программирую на Python уже как год, но до сих пор не могу понять когда использовать классы. Читая книги и просматривая видео уроки, я осознаю такие вещи как инкапсуляция или, что такое абстракция. Но, когда речь заходит о моментах когда именно надо использовать ООП, я встаю в ступор. В моем мышлении, классы можно заменить на модули с набором тематических функций. Никаких self'ов перед переменными и в качестве первого аргумента методов, никаких объявлений класса перед использованием, импортируй и пользуйся. Какие советы/материал для изучения Вы можете дать для понятия данной темы и изучения ООП в целом? P.S. Я видел похожий вопрос, но он не ответил на мой.
Ответы
Ответ 1
Это парадигма, которую вы можете использовать везде или не использовать вообще. У неё есть свои плюсы и свои минусы. Но какого-то магического порога, когда её использовать или когда её не использовать нету. Из основных плюсов можно отметить, что программа разбивается на классы, которые отвечают только за свой участок кода. При этом абстракция скрывает внутренние детали, которые вам не нужно знать. Поэтому большие и сложные программы становится легче понимать и заниматься их отладкой. Вы точно будете знать, где и что находится, если не допустите ошибок. Из минусов, это сложность проектирования, ведь перед написанием следует очень хорошо продумать вашу модель данных. Иногда это может занимать очень много времени и сил, особенно с непривычки. Классы действительно можно заменить на модули и если я не ошибаюсь, то вы получите что-то вроде компонентно-ориентированного программирования. В общем есть множество способов проектировать программное обеспечение и оно не заканчивается только на ООП, хоть последний и на слуху у всех. P.S. В общем, можете прямо сразу брать и использовать ООП везде, даже в самых маленьких программах. Правда для Python и маленьких программ, это просто лишняя трата сил на проектирование, как по мне. Но может будет очень полезно при работе с базами данных и ORM.Ответ 2
По сути ООП - это один из способов структурирования кода и разбиения задачи на более простые части (уменьшение сложности). Но для некоторых задач применение ООП не нужно, и даже вредно - например не уменьшает, а увеличивает сложность кода. В моем мышлении, классы можно заменить на модули с набором тематических функций. Никаких self'ов перед переменными и в качестве первого аргумента методов, никаких объявлений класса перед использованием, импортируй и пользуйся. Модуль с набором тематических функций - это по сути класс с набором статических методов, не изменяющих состояние объекта. Отличный пример - модуль math: функции только принимают значения и возвращают результат, внутреннее состояние не изменяется. Поэтому и self не нужен. Если функции сохраняют состояние в глобальных переменных модуля - тогда это будут аналогично методам класса (декоратор @classmethod перед методом класса). Например, такой класс: class Test: a = 1 @classmethod def inc_a(cls): cls.a += 1 Будет аналогичен такому модулю: a = 1 def inc_a(): global a a += 1 Если создавать объект класса Test, то поле a при вызове метода inc_a будет одновременно увеличиваться у всех объектов этого класса. Ну или у нас синглтон, тогда все нормально. Пример модуля, который хранит в себе внутреннее состояние - модуль random (исходник модуля в ветке Python 3.8): функции seed и setstate меняют состояние глобального (для данного модуля) объекта класса Random. Если же есть функция, которая меняет переданный в нее объект (например, в функцию передается словарь, она его каким-то образом меняет), то это фактически не будет отличаться от передачи в функцию объекта через self. Допустим, мы хотим реализовать упрощенный аналог Counter. Через класс: from operator import itemgetter class MyCounter: def __init__(self, items): self._counter = dict() for item in items: if item not in self._counter: self._counter[item] = 0 self._counter[item] += 1 def as_dict(self): return self._counter def most_common(self, n=None): sorted_items = sorted(self._counter.items(), key=itemgetter(1), reverse=True) if n is None: return sorted_items return sorted_items[0:n] Пример использования: c = MyCounter("aaaqsggex") print(c.as_dict()) # {'a': 3, 'q': 1, 's': 1, 'g': 2, 'e': 1, 'x': 1} print(c.most_common(3)) # [('a', 3), ('g', 2), ('q', 1)] То же самое в виде отдельных функций: from operator import itemgetter def counter(items): cnt = dict() for item in items: if item not in cnt: cnt[item] = 0 cnt[item] += 1 return cnt def most_common(d, n=None): sorted_items = sorted(d.items(), key=itemgetter(1), reverse=True) if n is None: return sorted_items return sorted_items[0:n] Пример использования: c = counter("aaaqsggex") print(c) # {'a': 3, 'q': 1, 's': 1, 'g': 2, 'e': 1, 'x': 1} print(most_common(c, 3)) # [('a', 3), ('g', 2), ('q', 1)] Видим, что в случае модуля если нам нужно просто посчитать количество повторений, то все хорошо, просто вызываем функцию counter. Если же нужно от результата найти самые часто повторяющиеся элементы, то нужно сохранить результат, потом явно передать его в функцию most_common. В ООП варианте этот результат хранится внутри объекта, и метод сам получает его из self, вручную передавать результат не нужно. Можно конечно извратиться и сохранять результат в глобальной переменной модуля - но тогда сложнее будет реализовать вариант, когда нужно посчитать количество повторений в разных объектах, да и зачем извращаться, если можно сделать через ООП. никаких объявлений класса перед использованием, импортируй и пользуйся. Достаточно положить класс в модуль, потом импортировать, создать объект и пользоваться.Ответ 3
Но, когда речь заходит о моментах когда именно надо использовать ООП, я встаю в ступор. Потому что не набита рука. Когда рука будет набита, будете наоборот из классов искать элементали, что бы их в библиотеку турнуть вон из объекта... Примеров, как принять ООП масса, возьмём деревянную структуру, есть корень, есть узлы, есть листья. - Очевидно, что ничего лучше чем ООП для работы с этим мета-объектом не подходит. Заводим class nodes, заводим class leafs - узел будет корневым в нодах, листья будут у нод параметрами. И вперёд! В такие парадигмы замечательно ложатся рекурсии... гиперссылочность позволяет творить чудеса, с листа звать корень и тд... Дальше-больше, любой мета-объект, который хотите использовать - это класс. Если надо 2 инта, 3 стринга и 5 булов вести в привязке к одному потоку/процессу то это самое оно. Раньше вы бы взяли структуру, struct - весь код ядра линукса ими забит. Но это Си, а не Си++, теперь мы берём класс. Но если вы вчитаетесь в документацию - то между ними нет разницы. Другой пример мега объекта в ||-программировании: Опять, есть 5 стрингов, 2 инта, 3 була - всё это вяжется на один поток, надо ко всему иметь доступ из родителя, вы либо создаёте 5 + 2 + 3 = 10 раковин, либо собираете весь поток в одну структуру, и создаёте элегантную раковинку: std::unordered_map < std::thread::id, your_mega_obj > sink; Теперь по tid-у, родительский поток может видеть кишки всех своих детишек: cout << sink[ 1235 ].name << std::endl; // - шлёп, имя готово.. мало имени, конечно, ... cout << sink[ 1235 ].name << ": " << << sink[ 1235 ].data << "; " << << sink[ 1235 ].enabled << "; " << << sink[ 1235 ].time << "; " << std::endl; Вуаля - всё в одном месте, не раскидано никуда, нет пересекания имён, у всех всё локанично, data, time, name, enabled. Никакой ерунды лишней, как например в javascript... Ну и когда речь идёт об объектах - надо не забыть про операторы. Создав класс, с парой операторов - можно компактным кодом, одним объектом - затыкать огромные дыры. Например, сокращать однообразные куски кода. Попробуйте стандартную работу с файлом, но с вашими личными обмотками - завернуть в класс обёртку ( wrapper ) - и звать не 5-10 строк кода, а одну строку - с параметрами... и сами убедитесь в мощности ООП. Так же и с базами данных.. И вообще - например у вас есть кусок кода, пусть 2 - или 10 - функций - относящихся к чему то конкретному, что то - что может быть объедененно по смыслу, например "работа с базой". Смело заворачивайте это в один класс, не уносите в библиотеку, и вы увидете - что объект "красивее", "удобнее" и "расширяемее", пускай там и больше символов из за self / this->, но и есть куда расти, всегда можно налепить сверху супер, класс, добавить операторы, и тд и тп. С библиотеками - этого можно добиться только низкоуровневыми выкрутасами с макросами - что есть прошлый век. ООП - это настоящее и будущее.Ответ 4
Хочу предложить следующую идею использования ООП; Для прикладного программирования - используй процедурное программирование, а для системного - ооп. Т.е. если нужно написать скажем сайт - пиши его в стиле процедурного программирования. А вот если тебе нужно написать движок для сайта - используй ооп.
Комментариев нет:
Отправить комментарий