Pull to refresh

Comments 36

Ваш функциональный «наблюдатель» отличается от объектного только переносом состояния в область глобальных переменных.
Ну да, это простейший случай. Если нужно несколько наблюдателей, можно добавить ещё один параметр и в subscribe и в notify, обозначающий, какой из наблюдателей использовать.
Вообще, одна из главных особенностей ООП и Паттернов — отпадает необходимость править код при расширении функционала. Код не изменяется, а именно расширяется.
Боюсь, при переходе от глобального хранилища подписок к локальному, нам придется вносить изменения в код наблюдателя, независимо от парадигмы. Если вы об изменениях в коде клиентов, то они не обязательны. Хочу указываю доп.параметр, хочу нет — в дефолтном случае будет использоваться всё то же глобальное хранилище.
На Пайтоне вы используете ООП, даже когда вам кажется, что вы его не используете. Вот эти ваши «функции» можно вызвать как
import имяфайла

имяфайла.run_claim()
Т.е. по вашему наличие тут точки — признак ООП?
Именно это точка в Пайтоне и значит. Вы от этого класс-объекта можете наследоваться и так далее.
Ну вот возьмите для примера Haskell. Там можем написать
import Module
-- ...
xxx = Module.function_name
Тут тоже ООП? Или в чем отличие от Python?

То, что модули в питоне объекты — это, условно говоря, деталь реализации.
Ведь и функции объекты, и коллекции, и даже числа.
Вы же не будете утверждать, что это ООП-код (дада, тут используются объекты)?
print(1 + 2)

На самом деле, завит от контекста, в котором мы используем «объект».
xxx = Module.function_name

конечно не ООП.
А когда мы применяем магию вроде
from types import ModuleType
class(ModuleType):
    ...

или
function_name.newattr = xxx

то резко замечаем, что и модуль и функция тоже объекты.
Мне кажется, тут надо разделять «использование ООП» и «возможность использования ООП».
Причём тут Хаскель? В Пайтоне — ООП, в Хаскеле мне всё равно что.
Да в принципе и не при чем. Главное не говорить на собеседованиях «ООП — это когда точку ставишь, а потом такой список доступных методов вылазит».
Я про Пайтон говорю, в Пайтоне это так.
Старше меня, а такую чушь несёте, да ещё так упор(но|ото). ООП — это объектно-ОРИЕНТИРОВАННОЕ программирование. Вызов обычной функции через точку, как «метод» модуля — не тот случай. Можно написать целый модуль из чистых (в смысле ФП) функций, и все их вызывать через точку, но это не будет ООП, т.к. ООП — это парадигма, а не синтаксис. На ассемблере тоже есть ООП, однако нет точки:

;; object.method(arg1, arg2)
push object
push arg1
push arg2
call method
Младше меня, а такую чушь несёте. Причём тут возраст вообще?

Вы, вот, например, путаете ФП и процедурный стиль.

На Ассемблере, очевидным образом, ООП нет, так как нет инкапсуляции и наследования.

Так как написание файла с функциями почти равно на Пайтоне написанию класса с методами, то это, конечно же, ООП.

Если вы накидаете бессистемно класс в методами, отнаследовавшись от чего-либо — это не ООП будет что ли? Файл с функциями ровно так и выглядит внутри Пайтона (да и для программиста — тоже).
От класса с методами отнаследоваться далее можно. А от модуля с функциями уже нельзя. Вы не сможете (без магии) определить модуль, отсутствующие атрибуты которого будут искаться в «родительском» модуле. Так что модуль — это всё таки экземпляр класса, но не сам класс. Т.е. наследования, так необходимого для ООП, здесь нет.
О какой магии речь идёт?

Вы точно о Пайтоне говорите?

a.py:
attrib1="a1"
attrib2="a2"

def method1():
    print("a1")

def method2():
    print("a2")

def _private():
    print("private")


b.py:
from a import *

def method2():
    print("b2")

def method3():
    print("b3")

attrib2="b2"

method1()
method2()
method3()

print(attrib1, attrib2)

_private()


Запускаем:
a1
b2
b3
a1 b2
Traceback (most recent call last):
  File "b.py", line 17, in <module>
    _private()
NameError: name '_private' is not defined


Вы тут не видите принципы ООП что ли?
Нет, не вижу. Вы просто скопировали атрибуты одного модуля в другой, наследственной связи не возникло.
Если после ваших манипуляций выполнить a.method1 = another_method, то b.method1 никак не изменится.
А какой принцип ООП требует, чтобы что-то изменилось в этом случае?
Тот самый, который вы пытаетесь проиллюстрировать. Мы ведь о наследовании говорим?
Все свойства подкласса, которые он не перезаписывал, должны всегда соответствовать свойствам родителя.
По-русски: Если в классе «Гражданин» во время выполнения программы изменился метод «расчет НДФЛ», то это автоматически должно отразиться на всех подклассах — и «Программист» и «Врач» и всех остальных. Кроме, может быть, специального класса «Льготный гражданин», в котором определено, что он НДФЛ считает не как все.
Они и соответствуют. То, что вы потом на хочу что-то изменили «задним числом» ни о чём не говорит. Это уже особенности реализации, а не принципы ООП.
Это не «на хочу», а совешенно необходимая вещь. Вместо метода, там мог бы быть какой-нибудь счётчик, или ещё какое-то изменяемое свойство, которое должно быть доступно для всех подклассов и их экземпляров.

Видимо у нас слишком разное понимание этих принципов. Похоже, что вы даже такой
dct.update(another_dict)

или такой
lst[:] = another_list

код тоже отнесёте к проявлениям ООП.
Мне же ближе классическое определение от Алана Кея.
Подчеркну — я знаю, что в Python'е всё есть объект. И это очень удобно. Но писать на нём все же можно по-разному. Статья написана о стиле кодирования, а не об особенностях терминологии. Наверное надо было её назвать «паттерны без классов», а не «без ООП». Меньше возражений было бы.
Различных способов реализовать наследование или его подобие при отсутствии встроенной в язык поддержке ООП очень много. Покажите где хотя бы один из них используется в статье. У вас написано «from a import *». У автора такого нет. Наличие возможности использования ООП тем или иным способам не делает любой код использующим ООП. Я, к примеру, могу написать пачку классов вида

from math import acos, atan, sin, cos

class CoordCartezian:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    def distance(self, c2):
        return ((self.x-c2.x)**2 + (self.y-c2.y)**2 + (self.z-c2.z)**2) ** 0.5

    def spherical(self):
        r = (self.x*self.x + self.y*self.y + self.z*self.z) ** 0.5
        return CoordSpherical(
            r,
            acos(self.z/r),
            atan(self.y/self.x)
        )

class CoordSpherical:
    def __init__(self, r, t, p):
        self.r = r
        self.t = t
        self.p = p

    def angle(self, c2):
        return acos(sin(self.t)*sin(self.p)*sin(c2.t)*sin(c2.p) + sin(self.t)*cos(self.p)*sin(c2.t)*cos(c2.p) + cos(self.t)*cos(c2.t))

    def cartezian(self):
        return CoordCartezian(
            self.r * sin(self.t) * cos(self.p),
            self.r * sin(self.t) * sin(self.p),
            self.r * cos(self.t)
        )


и так далее, но пока эти классы не начнут наследоваться друг от друга или хотя бы реализовывать одинаковые интерфейсы весь этот код будет процедурным, в котором просто для удобства к структурам были присобачены методы. В библиотеке с такими классами ООП нет. А классы есть.

Так что вопрос не в том, можно ли реализовать принципы ООП на модулях. Как уже сказано, ООП можно реализовать даже на ассемблере. Вопрос в том, где вы нашли ООП в статье?
Вы от этого класс-объекта можете наследоваться и так далее.

Неа, не можем:
Скрытый текст
In [1]: import itertools

In [2]: class MyItertools(itertools):
   ...:     pass
   ...: 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-8cefe4e16e35> in <module>()
----> 1 class MyItertools(itertools):
      2     pass

TypeError: Error when calling the metaclass bases
    module.__init__() takes at most 2 arguments (3 given)


Чуток не так надо делать.

import itertools

Module = type(itertools)

class MyItertools(Module):
    pass


Хотя по-моему это уже жуткий оффтопик…
Это понятно, я к
от этого класс-объекта можете наследоваться

придрался =)
Он путает функциональщину с процедурным стилем.
Не вижу, в чем ерунда. Переменная mailing_list в примере кода в статье — глобальная. ixSci ниже правильно написал, то, что автор отказался от использования классов при реализации наблюдателя, не делает этот паттерн функциональным.

Ваш функциональный «наблюдатель» отличается от объектного только переносом состояния в область глобальных переменных.

Позвольте поправить. Не следует путать функциональщину с процедурщиной.

UPD: упс. Не посмотрел, что статья 2013 года. Привет из будущего

ООП это методология, классы лишь наиболее простое средства для реализации модели декларирующей объектные свойства. Это вовсе не означает, что ООП это классы. ООП можно реализовывать и на чистом С, в котором, как Вы наверное знаете, классов нет и в помине. Поэтому Ваш заголовок не соответствует действительности. Паттерн «Наблюдатель» это ООП паттерн по своей сути, и не важно какими средствами Вы реализовали эту абстрактную модель. Она от этого не перестанет быть ОО.
Я согласен, что паттерн остаётся самим собой независимо от реализации. Перестаёт ли он от этого быть объектно-ориентированным… это пожалуй спорный вопрос и он может легко развиться в холивар в стиле «это мы придумали», «а у нас это уже было» и т.д. :)
Но пост как раз про реализацию. Паттерны в пособиях объясняются именно на примерах создания классов. И тот же человек пишущий на чистом C может не догадаться, что тоже может ими пользоваться. Поэтому я и решил предложить альтернативную версию. Для расширения понимания, так сказать.
Разумеется, в тех случаях, где функциональная парадигма изначально естественна, ее эмуляция методами ООП будет выглядеть более монстроподобно.

У ООПшного подхода есть так плюс как самодокументируемость. Что такое «словарь функций»? — как я могу догадаться, что в данном конкретном случае это «почта»? Только поддерживая консистентные имена переменных? Если в какое-нибудь место ваша «почта» будет передана просто как arg3 — что я смогу понять про ее назначение?

В то время как, скажем, интерфейс SubscriptionTopics говорит мне уже гораздо больше — независимо от того, каким путем он ко мне попал. Я даю имя сущности, которая мне нужна — в то время как вы называете реализацию сущности: в одном месте словарь функций может быть «почтой», в другом — частью конечного автомата — это очень разные сущности, а реализация одна.
> У ООПшного подхода есть так плюс как самодокументируемость

class a{
private $b,$c,$d;
public $e,$f,$g;
}
В «Состояние», таскаемый везде dict и есть объект. Это тот же ООП, только записанный иначе.
И с классами оно немного лаконичней — gist.github.com/nvbn/5830627 =)
Кажется, что использование паттерна Команда как простого коллбэка — это один из наиболее редких юзкейзов.

Основные, как мне кажется — объединить код и данные с целью манипуляции этими данными — например сохранить команды, чтобы иметь возможность повторно применить их к другому объекту; или иметь возможность откатить изменения (для этого в команде должен быть метод undo). Или сбросить команды на диск как «историю» изменений.
Более хитрый юзкейз — слияние однотипных подряд идущих команд в одно действие — тоже может быть полезен.
Подписываем наши функции на рассылки:
>>> subscribe('insertors', fun)
>>> subscribe('insertors', bar)

ммда, такие примеры можно называть паттернами, в каком-то узком смысле, просто потому, что они достаточно типичны. но на роль хороших архитектурных приемов, они, по-моему, не годятся уже лет как тридцать.)

как выше заметил bolk, в статье перепутаны ФП и процедурное программирование — практически во всех примерах видны все стандартные грабли этого подхода, связанных с плохо инкапсулированным состоянием. как раз ООП с этим и борется, за счет «развесистых схем классов», а в ФП состояния не должно быть как такового (во всяком случае в таком совсем уж явном виде).
Sign up to leave a comment.

Articles