ООП и динамическая типизация (не статическая vs динамическая)

какие принципы ООП, если таковые имеются, не применяются или применяются по-разному в динамически типизированной среде в отличие от статически типизированной среды (например, Ruby vs C#)? Это не призыв к статическим и динамическим дебатам, а скорее я хотел бы посмотреть, существуют ли принятые принципы по обе стороны этого разделения, которые применимы к одному, а не к другому, или применяются по-разному. Такие фразы, как" предпочитаю композицию наследованию", хорошо известны в статически типизированной литературе ООП. Они так же применимы на динамической стороне?

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

в Java, с другой стороны, степень детализации соединение может пойти как высоко как пакет. Вызов определенного метода не только устанавливает контракт с другим классом/интерфейсом, но и объединяет его в пакет/jar/assembly этого класса/интерфейса.

такие различия порождают разные принципы и шаблоны? Если да, то были ли сформулированы эти различия? Есть раздел Руби Киркой книга, которая идет в этом направлении немного (Duck Typing / Classes не являются типами), но мне интересно, если есть что-нибудь еще. Я в курсе шаблоны дизайна в Ruby но не читал его.

EDIT -- утверждалось, что Лисков не применяется то же самое в динамической среде, что и в статической среде, но я не могу не думать, что это так. С одной стороны, нет контракта высокого уровня со всем классом. Но не все вызовы любого данного класса составляют подразумевается контракт, который должен быть удовлетворен дочерних классах как предписывает Лисков? Рассмотреть следующее. Вызовы в "do some bar stuff" создают контракт, который должен обслуживаться дочерними классами. Разве это не случай " обращения со специализированным объектом как с базовым классом?":

class Bartender
    def initialize(bar)
       @bar = bar
    end

    def do_some_bar_stuff
        @bar.open
        @bar.tend
        @bar.close
    end
end

class Bar
    def open
        # open the doors, turn on the lights
    end
    def tend
        # tend the bar
    end
    def close
        #clean the bathrooms
    end
end

class BoringSportsBar < Bar
    def open
        # turn on Golden Tee, fire up the plasma screen
    end

    def tend
        # serve lots of Bud Light
    end
end

class NotQuiteAsBoringSportsBar < BoringSportsBar
    def open
        # turn on vintage arcade games
    end
end

class SnootyBeerSnobBar < Bar
    def open
        # replace empty kegs of expensive Belgians
    end

    def tend
        # serve lots of obscure ales, porters and IPAs from 124 different taps
    end
end

# monday night
bartender = Bartender.new(BoringSportsBar.new)
bartender.do_some_bar_stuff

# wednesday night
bartender = Bartender.new(SnootyBeerSnobBar.new)
bartender.do_some_bar_stuff

# friday night
bartender = Bartender.new(NotQuiteAsBoringSportsBar.new)
bartender.do_some_bar_stuff

4 ответов


существенная разница, которую вы касаетесь, я думаю:

  • языки группа 1. фактические методы, которые вызываются, когда объект, например.method1, object.метода Method2, объект.method3 вызываются могут изменяться в течение жизни объекта.

  • языки группа 2. фактические методы, которые вызываются, когда объект, например.method1, object.метода Method2, объект.method3 вызываются не могут изменяться в течение жизни объекта.

языки в группе 1, Как правило, имеют динамическую типизацию и не поддерживают проверенные во время компиляции интерфейсы и языки в группе 2, как правило, имеют статическую типизацию и поддерживают chcked-интерфейсы во время компиляции.

Я бы сказал, что все принципы OO применимы к обоим, но

  • в группе 1 может потребоваться дополнительное (явное) кодирование для реализации (время выполнения вместо времени компиляции) проверок, чтобы утверждать, что новые объекты создаются со всеми соответствующими методами, подключенными для удовлетворения interface contract поскольку нет проверки интерфейса времени компиляции, (если вы хотите сделать код группы 1 больше похожим на группу 2)

  • в группе 2 может потребоваться дополнительное кодирование для моделирования изменений фактического метода, вызываемого для вызова метода, с помощью дополнительных флагов состояния для вызова подметодов или для свертывания метода или набора методов в ссылку на один из нескольких объектов, прикрепленных к основному объекту, где каждый из нескольких объектов имеет другой метод реализации, (если вы хотите сделать код группы 2 более похожим на код группы 1)

  • сами ограничения на дизайн в языках группы 2 делают их лучше для больших проектов, где простота общения (в отличие от понимания) становится более важной

  • отсутствие ограничений на дизайн в языках группы 1 делает тогда лучше для небольших проектов, где программист может более легко проверить, являются ли различные ограничения сантехники дизайна встречаются с первого взгляда просто потому, что код меньше

  • создание кода из одной группы языков, таких как другой, интересно и стоит изучить, но суть языковых различий действительно связана с тем, насколько хорошо они помогают разным размерам команд ( - я считаю! :))

  • есть различные другие различия

  • более или менее ножная работа может потребоваться для реализации дизайна OO на том или ином языке в зависимости от конкретных принципов.


редактировать

Итак, чтобы ответить на ваш первоначальный вопрос, я рассмотрел

http://c2.com/cgi/wiki?PrinciplesOfObjectOrientedDesign

и

http://www.dofactory.com/patterns/Patterns.aspx

на практике принципы ОО не соблюдаются по различным уважительным причинам (и, конечно, некоторые плохие) в системе. Важные причины включенный где заботы представления перевешивают чисто заботы качества конструкции, везде, где культурные преимущества альтернативной структуры/именовать перевешивают чисто заботы качества конструкции и где цена экстренной работы снабжать функцию не в стандартном путе для определенного языка перевешивает преимущества чисто конструкции.

грубые зернистые шаблоны, такие как абстрактная Фабрика, Строитель, Заводской метод, прототип, адаптер, стратегия, цепочка команд, мост, Прокси, наблюдатель, посетитель и даже MVC / MMVM, как правило, меньше используются в небольших системах, потому что количество сообщений о коде меньше, поэтому преимущество создания таких структур не так велико.

более мелкозернистые шаблоны, такие как состояние, команда, Заводской метод, композит, декоратор, фасад, Flyweight, Memento, метод шаблона, возможно, более распространены в коде группы 1, но часто несколько шаблонов дизайна применяются не к объекту как таковому, а к различным частям объекта, тогда как в шаблонах кода группы 2 как правило, присутствует по одному шаблону на каждый объект.

IMHO в большинстве языков группы 1 имеет смысл рассматривать все глобальные данные и функции как своего рода одноэлементный объект "приложение". Я знаю, что мы начинаем размывать границы между процедурным и OO-программированием, но этот вид кода определенно крякает, как объект "приложение" во многих случаях! :)

некоторые очень мелкозернистые шаблоны проектирования, такие как Iterator, как правило, встроены в группу 1 языки.


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

Что сказал, Вот пример:

Принцип Сегрегации Интерфейса (http://objectmentor.com/resources/articles/isp.pdf) заявляет, что клиенты должны зависеть от наиболее конкретного интерфейса, который отвечает их потребностям. Если клиентский код должен использовать два метода класса C, то c должен реализовать интерфейс I, содержащий только эти два метода, и клиент будет использовать I, а не C. Этот принцип не имеет значения в динамически типизированных языках, где интерфейсы не нужны (поскольку интерфейсы определяют типы, а типы не нужны в языке, где переменные не имеют типа)

[edit]

второй пример-принцип инверсии зависимостей (http://objectmentor.com/resources/articles/dip.pdf). Этот принцип утверждает, что " стратегия в зависимости от интерфейсов или абстрактных функций и классов, а не от конкретных функций и классов". Опять же, в динамически типизированном языке клиентский код ни от чего не зависит - он просто указывает сигнатуры метода - тем самым устраняя этот принцип.

третий пример-принцип подстановки Лискова (http://objectmentor.com/resources/articles/lsp.pdf). Примером для этого принципа является класс Square, который подклассы класса Rectangle. И клиентский код, вызывающий метод setWidth () для переменной Rectangle, удивляется, когда высота также изменяется, поскольку фактический объект является квадратом. Опять же, в динамически типизированном языке переменные не имеют типа, класс Rectangle не будет упоминаться в клиентском коде, и, следовательно, таких сюрпризов не возникнет.


У меня есть "радикальный" взгляд на все это: на мой взгляд, подкрепленный математикой, ООП не работает в статически типизированной среде для каких-либо интересных проблем. Я определяю интересное как значение абстрактных отношений. Это можно легко доказать (см. "Проблема ковариации").

суть этой проблемы заключается в том, что концепции ООП обещают, что это способ моделирования абстракций и в сочетании с контрактным программированием, поставляемым статическим типом, отношения не могут быть реализовано без нарушения инкапсуляции. Просто попробуйте любой ковариантный двоичный оператор: попробуйте реализовать "меньше " или" добавить " в C++. Вы можете легко кодировать базовую абстракцию, но не можете ее реализовать.

в динамических системах нет формализованных типов высокого уровня и нет инкапсуляции, чтобы беспокоиться о том, что OO действительно работает, в частности, системы на основе прототипов, такие как оригинальный Smalltalk, фактически доставляют рабочие модели, которые не могут быть закодированы вообще со статическим типом ограничения.

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


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

пусть каждый объект поговорит с интерфейсами, которые он хотел бы существовать в идеальном мире. Если вы это сделаете, вы получите небольшие интерфейсы с небольшой областью действия. Таким образом, вы получите сбои времени компиляции при изменении интерфейсов.

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

одним из реальных преимуществ статического ввода является не статическое знание методов, которые вы можете вызвать, но гарантирование того, что объекты value уже проверены... если вам нужно имя, и имя должно быть

Если вы собираетесь использовать статический язык, использовать его в своих интересах.