Как меняется мышление о шаблонах проектирования и методах ООП в динамических и слабо типизированных языках?

11

В этом отношении уже есть довольно полезный вопрос (« Шаблоны проектирования без ООП? »), Но мне более любопытна переходная точка зрения для человека, только начинающего работать с динамическими и слабо типизированными языками.

То есть: скажем, я программировал на C ++, C # или Java в течение многих лет и впитал много мудрости в соответствии с шаблонами проектирования GoF, шаблонами корпоративной архитектуры приложений Фаулера , принципами SOLID и т. Д. Теперь я Я увлекаюсь Ruby, Python, JavaScript и т. д. и задаюсь вопросом, как применимы мои знания. Предположительно, я мог делать прямые переводы во многих случаях, но почти наверняка это не использовало бы в полной мере мои новые настройки. Утиная печать сама по себе переворачивает с ног на голову многие мысли, основанные на интерфейсе.

Что остается прежним? Какие изменения? Существуют ли руководящие принципы, такие как SOLID, или канонические шаблоны (возможно, совершенно новые), которые должен знать новичок в динамическом языке?

Доменик
источник

Ответы:

7

Что остается прежним? Какие изменения?

Шаблоны одинаковы. Языковые техники меняются.

Существуют ли руководящие принципы, такие как SOLID,

Да. Действительно, они остаются руководящими принципами. Ничего не меняется

или канонические модели (возможно, совершенно новые), которые должен знать новичок в динамическом языке?

Некоторые вещи уникальны. В основном влияние заключается в том, что методы реализации меняются.

Шаблон - хорошо - это шаблон . Не закон. Не подпрограмма. Не макрос. Это просто хорошая идея, которая повторяется, потому что это хорошая идея.

Хорошие идеи не выходят из моды и не меняются кардинально.

Другие заметки. Python не является "слабо типизированным". Он более строго типизирован, чем Java или C ++, потому что нет операции приведения. [Да, есть способ выдумать класс, связанный с объектом, но это не то, что делается, кроме как доказать суетливую, законническую точку зрения.]

Также. Большинство шаблонов проектирования основаны на различных способах использования полиморфизма.

Посмотрите на State или Command или Memento в качестве примеров. У них есть иерархии классов для создания полиморфных состояний, команд или сувениров изменений состояний. Ничего существенно не меняется, когда вы делаете это в Python. Незначительные изменения включают ослабление точной иерархии классов, потому что полиморфизм в Python зависит от общих методов, а не от общих предков.

Кроме того, некоторые шаблоны являются просто попыткой добиться позднего связывания. Большинство связанных с фабрикой шаблонов - это попытка легкого изменения иерархии классов без перекомпиляции каждого модуля C ++ в приложении. Это не такая интересная оптимизация в динамическом языке. Тем не менее, Factory как способ скрыть детали реализации по-прежнему имеет огромное значение.

Некоторые шаблоны - это попытка управлять компилятором и компоновщиком. Синглтон , например, существует для создания запутанных глобалов, но, по крайней мере, для их инкапсуляции. Синглтон-классы Python не являются приятной перспективой. Но модули Python уже являются синглетонами, поэтому многие из нас просто используют модуль и стараются не связываться с классом Singleton .

С. Лотт
источник
Я бы не сказал, что «SOLID» ничего не меняет. В зависимости от языка и его объектной модели принцип открытого и закрытого типа и принцип подстановки Лискова могут быть бессмысленными. (JavaScript и Go приходят на ум.)
Мейсон Уилер,
@ Мейсон Уилер. Open-Closed не зависит от языка в моем опыте. Вам нужно будет привести несколько более конкретных примеров того, как открытый и закрытый дизайн «бессмысленен» с помощью JavaScript или Go. Подстановка Лискова, возможно, не применима к JavaScript, но существенный паттерн - полиморфизм - по-прежнему применим.
S.Lott
@ S.Lott: хорошие обновления в редактировании; они были намного интереснее оригинального ответа: с. Спасибо за исправление моей ошибки Python. В целом, конкретные примеры шаблонов и их связь с динамическими языками, полиморфизмом, поздним связыванием и т. Д. Идеальны.
Доменик
@ S.Lott: Потому что Open / Closed - это наследование, которого нет в этих языках. (Кроме того, идея о том, что объект «закрыт для модификации» не будет подходить многим Ruby-кодерам ...)
Мейсон Уилер,
@ Мейсон Уилер: Спасибо за разъяснения по Open / Closed. Я думаю, что исключение JavaScript важно, но, поскольку вопрос так открыт (в нем перечислены JavaScript, Python и Ruby, а также язык с именем ETC), я не уверен, как решить этот особый случай.
С.Лотт
8

Питер Норвиг взял этот самый вопрос в 1998 году, прочитал http://norvig.com/design-patterns/ppframe.htm , чтобы узнать о некоторых подробностях, которые он заметил, и http://c2.com/cgi/wiki?AreDesignPatternsMissingLanguageFeatures для дальнейшее обсуждение вокруг сути.

Короче говоря, если у вашего языка больше возможностей, то повторяющиеся шаблоны проектирования становятся проще - зачастую до такой степени, что становятся невидимыми. Он обнаружил, что это верно для большинства шаблонов проектирования, которые были определены GoF.

btilly
источник
8

При программировании на динамическом объектно-ориентированном языке используются многие из тех же шаблонов и принципов, но существуют определенные изменения и различия, связанные с окружающей средой:

Замените интерфейсы на Duck Typing - там, где «Бригада четырех» скажет вам использовать абстрактный базовый класс с чисто виртуальными функциями, и вы будете использовать интерфейс в Java на динамическом языке, вам нужно только понимание. Поскольку вы можете использовать любой объект где угодно, и он будет прекрасно работать, если он реализует методы, которые фактически вызываются, вам не нужно определять формальный интерфейс. Возможно, стоит документировать один, чтобы было ясно, что на самом деле требуется.

Функции тоже являются объектами. Существует множество шаблонов, которые позволяют отделить решение от действия; Команда, стратегия, цепочка ответственности и т. Д. На языке с первоклассными функциями часто разумно просто передавать функцию вместо создания объектов с .doIt()методами. Эти шаблоны превращаются в «использовать функцию более высокого порядка».

ПРОДАНО - Принцип сегрегации интерфейсов имеет наибольший успех, так как здесь нет интерфейсов. Вы все еще должны учитывать этот принцип, но вы не можете воплотить его в своем коде. Только личная бдительность защитит вас здесь. С другой стороны, боль, вызванная нарушением этого принципа, значительно уменьшается в обычных динамических средах.

"... по-своему ... идиома!" - У каждого языка есть хорошие и плохие практики, и вам придется изучать их и следовать им, если вы хотите лучший код на этих языках. Например, идеально написанный шаблон итератора может быть смешным на языке со встроенным списочным пониманием.

Шон Макмиллан
источник
3

По моему опыту, некоторые паттерны все еще полезны в Python, и их даже проще настроить, чем в более статичных языках. Некоторые паттерны OTOH просто не нужны или даже не одобряются, как паттерн Singleton. Вместо этого используйте переменную уровня модуля или функцию. Или используйте шаблон Борга.

Вместо того, чтобы устанавливать шаблон создания, часто достаточно передать вызываемый объект, который создает объекты. Это может быть функция, объект с __call__методом или даже класс, так как new()в Python его нет, просто вызов самого класса:

def make_da_thing(maker, other, stuff):
    da_thing = maker(other + 1, stuff + 2)
    # ... do sth
    return da_thing

def maker_func(x, y):
     return x * y

class MakerClass(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
...
a = make_da_thing(maker_func, 5, 8)
b = make_da_thing(MakerClass, 6, 7)

State и Strategy Pattern имеют очень похожую структуру в таких языках, как C ++ и Java. Меньше так в Python. Паттерн стратегии остается более или менее таким же, но паттерн состояний становится в основном ненужным. Шаблон состояния в статических языках имитирует изменение класса во время выполнения. В Python вы можете сделать это: изменить класс объекта во время выполнения. Пока вы делаете это контролируемым, инкапсулированным способом, у вас все будет хорошо:

class On(object):
    is_on = True
    def switch(self):
        self.__class__ = Off

class Off(object):
    is_on = False
    def switch(self):
        self.__class__ = On
...

my_switch = On()
assert my_switch.is_on
my_switch.switch()
assert not my_switch.is_on

Шаблоны, основанные на статической диспетчеризации типов, не будут работать или работать по-другому. Вам не нужно писать так много кода, например Visitor Pattern: в Java и C ++ вы должны писать метод accept в каждом доступном классе, тогда как в Python вы можете наследовать эту функциональность через класс mixin, например Visitable:

class Visitable(object):
    def accept(self, visitor):
        visit = getattr(visitor, 'visit' + self.__class__.__name__)
        return visit(self)
...

class On(Visitable):
    ''' exactly like above '''

class Off(Visitable):
    ''' exactly like above '''

class SwitchStatePrinter(object): # Visitor
    def visitOn(self, switch):
         print 'the switch is on'
    def visitOff(self, switch):
         print 'the switch is off'

class SwitchAllOff(object): # Visitor
    def visitOn(self, switch):
         switch.switch()
    def visitOff(self, switch):
         pass
...
print_state = SwitchStatePrinter()
turn_em_off = SwitchAllOff()
for each in my_switches:
    each.accept(print_state)
    each.accept(turn_em_off)

Многие ситуации, в которых требуется применение Pattern на статическом языке, в Python делают не так много. Многие вещи могут быть решены с помощью других методов, таких как функции более высокого порядка (декораторы, фабрики функций) или мета-классы.

pillmuncher
источник
Теперь я понимаю, что ваш ответ amost охватывает вопрос, который я только что задал: хорошая ли идея перезаписи __class__для реализации фабрики в Python ?
Rds