Размышление / открытые вопросы по философии ОО - PullRequest
0 голосов
/ 26 октября 2010

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

Предположим, у меня есть abstract class Bird.Предположим, что мы берем довольно типичную философию ОО *, Bird - это объект, который способен поддерживать и изменять состояние.Очевидным примером состояния для этой простой модели является то, летит ли наш Bird или нет.

Итак, Bird имеет метод fly().

Или делает это??Я видел этот очень простой пример, приведенный во вводных книгах, и, на мой взгляд, он слегка вводит в заблуждение.Если вы поддерживаете состояние, вам не нужно указывать объекту продолжать оставаться в данном состоянии;это просто так.Это то, что делает объект.Поэтому на самом деле вы хотите, чтобы у Bird был метод takeoff().

Предположим, в какой-то момент мы создали объект Bird из другого класса, Egg.В какой-то момент мы можем вызвать метод Egg.hatch(), который имеет тип возврата Bird.Предполагая, что мы кодируем интерфейс , Egg знает, из какого типа птицы это яйцо, а возвращаемый экземпляр из hatch() знает, что это за птица.Но мы не можем сказать, что между Egg и Bird есть что-то общее.действительно ли мы говорим, что мы хотим, чтобы конкретные реализации этих классов имели ссылку на экземпляр какого-то вида BirdArchetype, который представляет характеристики всех экземпляров этого вида, чтобы и Egg, и Bird знали свои собственныедобрый, независимо, но последовательно?Существует ли какой-либо известный шаблон для такого рода отношений?

Чья ответственность за обеспечение того, чтобы Яйцо изменило состояние, чтобы оно больше не могло снова вылупиться или делать большинство других вещей, которые яйца обычно делают?do?

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

Что еще мы можем сделать с яйцом?Возможно, мы могли бы приготовить это.Если бы это был я, я бы закодировал это так, что вызов cook() для яйца «разбивает» экземпляр Egg и возвращает экземпляр нового объекта, FriedEgg.Как это отличается от сообщения от Bird до takeoff()?Абсурдно предполагать, что взлет делает его принципиально другой птицей.Все же рассуждение то же самое;птица, которая летает (обычно), не может делать большинство других вещей, которые делают птицы, потому что она занята.Возможно, тогда Bird можно сломать, так как пока он находится в состоянии полета, вызов некоторых других его методов не будет работать.

Полагаю, единственное реальное различие здесь в том, что Bird может стать ип -broken.Действительно ли концепция термодинамической обратимости так важна для программирования очень простых моделей, подобных этой?

Ответы [ 6 ]

2 голосов
/ 26 октября 2010
  1. "Муха" разумно означает "начать летать!" (Я бы сказал, что он используется чаще таким образом в естественной речи). У вас нет , чтобы интерпретировать это как "устойчивый полет!" Вот почему мы разумно используем «летать» и «земля» (на естественном английском языке) в качестве антонимов. ( Свидетельство для скептика )

  2. Вы предполагаете, что яйцо создано ex nihilo в вашей модели. Это не совсем разумное предположение. Птица произошла из яйца, а яйцо - из птицы. Предполагая разумную цель для моделирования яйца, это моделирование времени размножения видов птиц. Это означает, что птенец сформирован половым размножением двух других птиц. Средством генерации птенца является яйцо. Яйцо не должно знать, какая птица в нем; эта птица была создана родителями и обернута в родовое яйцо (которое, возможно, имеет переменные свойства в зависимости от матери.

    Другими словами, ваша модель не совсем правильно сформирована по этому вопросу.

  3. Яйцо несет ответственность за то, чтобы оно не вылупилось снова (проверка в функции штриховки). См. Обсуждение «императивов» ниже.

  4. Понятие "хрупкий", вероятно, излишне для вашей модели. Вас беспокоит ломкость яйца или выводимость? Если нарушение является значительной частью данных, которые вы пытаетесь смоделировать (например, если что-то в вашей модели существенно зависит от разбития яйца, то смоделируйте его. В любом случае штриховка не случается, когда яйцо разбивается, это происходит, когда яйцо вылупляется. Одно из последствий вылупления разбивается, а не наоборот.

  5. Причина, по которой egg.cook () кажется неправильной, заключается в том, что она есть Методы глагола должны взять объект как предмет. В вашем утверждении «egg.cook ()» говорится, что яйцо готовится, и редко мы собираемся использовать глагол «готовить» в командах относительно яиц. Вы, вероятно, действительно хотите chef.cook (food) - где аргумент «food» - это все, что происходит от еды (или, что еще лучше, имеет роль (a la Moose) «isCookable».

  6. Помните, языки программирования являются императивными языками; опросы, наставления и другие функции «естественного языка» по своей сути неуместны в языках программирования, даже если мы достигаем какой-то версии с помощью команд (мы эмулируем опрос с помощью команд; «СКАЖИТЕ МНЕ ВАШЕ СОСТОЯНИЕ!» - это обязательная интерпретация вопросительного слова » В каком ты состоянии? ")

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

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

2 голосов
/ 26 октября 2010

Вот почему меня не особо волнует моделирование реального мира и использование его для объяснения / определения философии ОО.

Если вы не создаете симулятор (в котором ответы на эти вопросы сразу становятсяэто очевидно, если взглянуть на контекст симулятора) слишком легко уйти в сорняки и запутаться.

2 голосов
/ 26 октября 2010

Возможно, наличие мутирующего состояния в объекте - это неправильно; -)

Не должен ли Bird.takeoff() вернуть FlyingBird, который может land()? (Это явно не имеет смысла для птицы, которая не летит на землю.)

Аналогично для яйца:

egg = Egg()
incubatedEgg = egg.incubate()
bird = incubatedEgg.hatch()

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

egg = Egg()
egg.incubate() ->
 if "still alive" (aliveEgg) ->
   aliveEgg.hatch()
 else "died in incubation" (deadEgg) ->
   deadEgg.discard()

Счастливое философствование: -)

0 голосов
/ 26 октября 2010

Похоже, проблема именования. Например, в Java вы можете запросить состояние птицы как «getFly ()», где вы называете «fly ()», как это делает python. Здесь вы говорите, что нужно взлететь, как будто вы меняете состояние птицы, что немного неоднозначно для читателя кода.

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

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

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

Что касается твоей Птицы, которую можно сломать, пока она в полете, это вина птицы? Если бы моя собака была в своем питомнике, она бы сломалась, если бы я сказал ей пойти за мячом?

0 голосов
/ 26 октября 2010

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

Это зависито том, хотите ли вы сохранить яйцо, оставшееся в вашей модели, или считать, что яйцо пропало.

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

Или так?Я видел этот очень простой пример, приведенный во вводных книгах, и, на мой взгляд, он слегка вводит в заблуждение.Если вы поддерживаете состояние, вам не нужно указывать объекту продолжать оставаться в данном состоянии;это просто так.Это то, что делает объект.Итак, действительно, вы хотите, чтобы у Bird был метод takeoff ().

Я думаю, что ваша проблема здесь в неточном использовании слов, а не в разработке программного обеспечения.Авторы решения fly() используют глагол «fly» в смысле «старт полета», например, как синоним «взлета».Является ли это 100% допустимым использованием английского языка или нет, выше моей зарплаты как разработчика ESL, но я, безусловно, согласен, что «взлет» - это значительно менее неоднозначное имя метода.

... так что обаЯйцо и Птица знают свой род самостоятельно, но последовательно?Есть ли какой-нибудь известный шаблон для такого рода отношений?

Я не уверен, существует ли официальный "шаблон", но реализован конструктор яйца (или фабрика, если вы используете фабрику)в универсальном классе «Птица» передают тип «птица» новому объекту «Яйцо», и наоборот, когда объект «Яйцо» передает тип птицы в конструктор (или фабрику) «Птицы», очень широко используется.

Что еще мы можем сделать с яйцом?Возможно, мы могли бы приготовить это.Если бы это был я, я бы написал это так, чтобы вызов метода cook () для яйца «разбивал» экземпляр Egg и возвращал экземпляр нового объекта FriedEgg.Как это отличается от того, чтобы сказать птице взлет ()

Как уже отмечал комментатор, вышеизложенная проблема связана с ОО - яйцо само не готовится.Поэтому вы не можете реализовать метод «готовить» на яйце в отдельности (хотя для вашей модели может потребоваться какой-то метод «установить статус для приготовленного», если только по какой-то другой причине, кроме как сделать его непостижимым. Однако вещь FriedEgg должна бытьсозданный (если необходимо) с помощью метода «cook_egg» объекта Cook - который также вызывает «установить статус готового» на яйце.

0 голосов
/ 26 октября 2010

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

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

В вашем случае давайтескажем, вы сделали egg.fry() (с другой стороны, яйцо не может поджариться само по себе. Поэтому, возможно, вам нужен класс Cook, который принимает egg в качестве аргумента метода fry и возвращает экземпляр FriedEgg),и после этого вы сделали egg.hatch().Второй вызов должен вернуть исключение.Вместо того, чтобы заставлять пользователя вашего класса помещать этот вызов в явный блок try...catch, вы должны предоставить метод проверки состояния;что-то вроде isFried() или isHatchable().Тогда вместо:

try {
  egg.hatch();
}

catch(UnhatchableException e) {
  ...
}

У вас есть:

if(egg.isHatchable()) {
   egg.hatch();
}

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

Теперь, если у вас есть класс Cook и метод с именем fry, и вы сделали Cook.fry(egg), который возвращает экземпляр FriedEgg, чтопроизойдет, если вы позвоните hatch() на это?Здравый смысл скажет вам, что жареное яйцо не может высиживать ничего!

В этом случае у вас должен быть интерфейс Egg с LiveEgg (т. Е. Яйцо, которое может высиживать) и FriedEgg, оба реализующиеEgg интерфейс.Разница, однако, заключается в реализации метода hatch() в FriedEgg;нужно было бы выбросить UnsupportedOperationException, потому что вы не можете вылупить жареное яйцо.

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

В любом случае, надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...