ООП и динамический контроль типов (не статичный по сравнению с динамическим)

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

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

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

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

РЕДАКТИРОВАНИЕ - утверждалось, что Liskov не применяет то же в динамической среде, как это делает в статической среде, но я не могу сдержать взгляды, что это делает. С одной стороны, нет никакого высокоуровневого контракта со всем классом. Но не все вызовы к какому-либо данному классу составляют неявный контракт, который должен быть удовлетворен дочерними классами путем, Liskov предписывает? Рассмотрите следующее. Вызовы в "делают некоторый материал панели" создает контракт, к которому должны проявить внимание дочерние классы. Разве это не случай "обработки специализированного объекта, как будто это был базовый класс?":

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

10
задан Dave Sims 17 December 2009 в 15:28
поделиться

3 ответа

The essential difference you are touching on I think are:

  • languages group 1. the actual methods that are invoked when eg object.method1, object.method2, object.method3 are called can change during object's lifetime.

  • languages group 2. the actual methods that are invoked when eg object.method1, object.method2, object.method3 are called cannot change during object's lifetime.

Languages in group 1 tend to have dynamic typing and to not support compile-time checked interfaces and languages in group 2 tend to have static typing and to support compile-time chcked interfaces.

I would say that all OO principles apply to both, but

  • some extra (explicit) coding to implement (run-time instead of compile-time) checks may be required in group 1 to assert that new objects are created with all appropriate methods plumbed in to meet an interface contract as there is no compile-time interface-agreement checking, (if you want to make group 1 code more like group 2)

  • some extra coding may be required in group 2 to model changes of the actual method invoked for a method call by using extra state flags to call submethods, or to wrap up the method or a set of methods in a reference to one of several objects attached to the main object, where each of the several objects has different method implementations, (if you want to make group 2 code more like group 1 code)

  • the very restrictions on design in group 2 languages make them better for larger projects where ease of communication (as opposed to comprehension) becomes more important

  • the lack of restrictions on design in group 1 languages makes then better for smaller projects, where the programmer can more easily check whether the various design plumbing constraints are met at a glance simply because the code is smaller

  • making code from one group of languages like the other is interesting and well worth studying but the point of the language differences is really to do with how well they help different sizes of teams ( - I believe! :) )

  • there are various other differences

  • more or less leg-work may be required to implement an OO design in one language or another depending on the exact principles involved.


EDIT

So to answer your original question, I examined

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

AND

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

In practice the OO principles are not followed for various good reasons (and of course some bad) in a system. Good reasons included where performance concerns outweigh pure design quality concerns, wherever cultural benefits of alternate structure/naming outweigh pure design quality concerns and where the cost of the extra work of implementing a function not in the standard way for a particular language outweighs the benefits of a pure design.

Coarser-grained patterns like Abstract Factory, Builder, Factory Method, Prototype, Adapter, Strategy, Chain of Command, Bridge, Proxy, Observer, Visitor and even MVC/MMVM tend to get used less in small systems because the amount of communication about the code is less, so the benefit of creating such structures is not as great.

Finer-grained patterns like State, Command, Factory Method, Composite, Decorator, Facade, Flyweight, Memento, Template method are perhaps more common in group 1 code, but often several design patterns apply not to an object as such but to different parts of an object whereas in group 2 code patterns tend to be present on a one pattern per object basis.

IMHO it makes a lot of sense in most group 1 languages to think of all global data and functions as a kind of singleton "Application" object. I know we're getting to blurring the lines between Procedural and OO programming, but this kind of code definitely quacks like an "Application" object in a lot of cases! :)

Some very fine-grained design patterns like Iterator tend to be built into group 1 languages.

5
ответ дан 4 December 2019 в 01:57
поделиться

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

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

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

3
ответ дан 4 December 2019 в 01:57
поделиться

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

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

Чем меньше и точнее ваши интерфейсы, тем меньше вы «ведете бухгалтерский учет».

0
ответ дан 4 December 2019 в 01:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: