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


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

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

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

Приводят ли подобные различия к возникновению различных принципов и моделей поведения? Если да, то были ли сформулированы эти различия? В книге Ruby Pickaxe есть раздел, который немного идет в этом направлении (Duck Typing/Classes are not Types), но мне интересно, есть ли что-нибудь еще. Я знаю об этом. шаблоны проектирования в Ruby , но не читал его. Было доказано, чтоЛисков не применяет то же самое в динамической среде, что и в статической среде, но я не могу не думать, что это так. С одной стороны, нет контракта высокого уровня со всем классом. Но разве все вызовы любого данного класса не составляют неявный контракт, который должен быть удовлетворен дочерними классами, как предписывает Лисков? Рассмотреть следующее. То вызовы В "сделать некоторые вещи бара" создают контракт, который должен быть посещен дочерними классами. Разве это не случай " обращения со специализированным объектом, как если бы он был базовым классом?":
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 9

4 ответа:

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

  • Группа языков 1. фактические методы, которые вызываются, когда объект eg.метод1, объект.Метод 2, Объект.метод 3 вызывается может изменяться в течение жизни объекта.

  • Группа языков 2. фактические методы, которые вызываются, когда объект eg.метод1, объект.Метод 2, Объект.метод 3 вызывается не может изменяться в течение жизни объекта.

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

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

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

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

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

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

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

  • Существуют различные другие различия

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


EDIT

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

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

И

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

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

Более грубые шаблоны, такие как абстрактная Фабрика, конструктор, метод фабрики, прототип, адаптер, стратегия, цепочка команд, мост, Прокси, наблюдатель, посетитель и даже MVC / MMVM, как правило, используются меньше в небольших системах, потому что объем информации о коде меньше, поэтому преимущество создания таких структур не так велико. отличный. Более мелкозернистые шаблоны, такие как State, Command, Factory Method, Composite, Decorator, Facade, Flyweight, Memento, Template method, возможно, более распространены в коде группы 1, но часто несколько шаблонов проектирования применяются не к объекту как таковому, а к различным частям объекта, тогда как в группе 2 шаблоны кода, как правило, присутствуют по одному шаблону на объект.

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

Некоторые очень мелкозернистые шаблоны проектирования, такие как 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). Пример текст книги для этого принципа является квадратом класс, который наследуется от класса Rectangle. А затем клиентский код, который вызывает метод setWidth () на прямоугольнике переменная удивляется, когда высота также изменяется, так как фактический объект является квадратом. Опять же, в динамически типизированном языке переменные являются безтипными, класс Rectangle не будет упомянут в клиентском коде, и, следовательно, никаких таких сюрпризов не возникнет.

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

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

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

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

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

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

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

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

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