ООП и динамическая типизация (не статическая против динамической)

StackOverflow https://stackoverflow.com/questions/1918310

Вопрос

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

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

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

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

РЕДАКТИРОВАТЬ - Утверждалось , что Лисков в динамической среде применяется не то же самое, что в статической, но я не могу отделаться от мысли, что это так.С одной стороны, нет контракта высокого уровня со всем классом.Но разве все вызовы какого-либо данного класса не представляют собой неявный контракт, который должен выполняться дочерними классами так, как предписывает Лисков?Рассмотрим следующее.Вызовы в разделе "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
Это было полезно?

Решение

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

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

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

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

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

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

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

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

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

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

  • есть и множество других отличий

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


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

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

http://c2.com/cgi/wiki ?Принципы объектно-ориентированного проектирования

И

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

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

Более грубые шаблоны, такие как абстрактная фабрика, Конструктор, фабричный метод, Прототип, адаптер, Стратегия, Цепочка команд, Мост, Прокси, Наблюдатель, Посетитель и даже MVC / MMVM, как правило, меньше используются в небольших системах, поскольку объем обмена информацией о коде меньше, поэтому выгода от создания таких структур не так велика.

Более мелкие шаблоны, такие как State, Command, Factory Method, Composite, Decorator, Facade, Flyweight, Memento, Template Method, возможно, более распространены в коде группы 1, но часто несколько шаблонов проектирования применяются не к объекту как таковому, а к разным частям объекта, тогда как в коде группы 2 шаблоны, как правило, присутствуют по одному шаблону на объект.

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

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

Другие советы

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

Тем не менее, вот пример:

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

[править]

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top