Объектно-ориентированное программирование - путаница дизайна класса

Я пытаюсь обвести голову объектно-ориентированным программированием.

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

Возьмем иерархию классов:

class Fruit {
    void Eat() {

    }
}

class Apple extends Fruit {

}

Очевидно, что вы можете использовать Fruit полиморфно, если Eat() является виртуальным. Но имеет ли это смысл? Фрукты не могут съесть себя!

Если объект фрукта скорее передается человеческому объекту, который имеет функцию Eat()?

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

Ответ 1

У вас проблемы с дизайном - как вы правильно указываете, Eat() не имеет очевидного смысла в качестве члена Fruit. С другой стороны, "съедобный" атрибут будет иметь больше смысла. Как и событие "onEaten" и т.д. Что раскрывают ваши классы фруктов/яблок (и какие другие объекты имеют смысл в вашей модели), зависит от множества других факторов, в том числе от того, что вы пытаетесь выполнить с помощью этих конструкций в вашей заявке.

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

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

Ответ 2

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

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

Если ваше яблоко происходит от фруктов, добавляет ли оно какое-нибудь интересное поведение? Нужна ли иерархия? Наследование добавляет уровень сложности вашему программному обеспечению, и все, что становится все сложнее, плохо. Ваше программное обеспечение немного сложнее, чтобы следить и понимать, просто немного больше, чтобы охватить в вашем тесте, и вероятность ошибки будет чуть-чуть больше.

Я нахожу, что ООП больше относится к пробелу - то, что вы оставляете, более важно.

Ответ 4

Что-то из класса Herbivore будет иметь функцию Eat, как и что-то из класса Carnivore, но каждый из Eat будет иметь некоторые разные ограничения на то, какой аргумент может быть передан функции Eat. Плод - это то, что съедено, поэтому оно будет передано в качестве аргумента Herbivore.Eat(), тогда как вы хотели бы передать объект типа Hamburger в Carnivore.Eat() и возбудить исключение, если Гамбургер был передан Herbivore.Eat().

Но на самом деле, я не думаю, что ООП - это то, что мы можем моделировать объекты программного обеспечения так же, как объекты реальной жизни. Я обнаружил, что большая часть моего проекта ООП работает с довольно абстрактными объектами, и только в отношении системы, в которой они входят. Если бы я написал библиотеку checkin/checkout system, я бы моделировал книгу с точки зрения ее административных свойств и функций - я бы не моделировал ее как набор объектов страницы, и я сомневаюсь, что я бы даже определил что-то вроде метода Read(), хотя такова основная цель иметь книгу в первую очередь. Роль объекта Book в системе диктует мой дизайн гораздо больше, чем то, что он делает с книгами в реальном мире.

Ответ 5

Предполагая, что вы, скажем, пишите голодный симулятор людей, тогда я думаю, что имеет смысл, как вы говорите, иметь функцию Human::Eat(Fruit f). У вашего Fruit могут не быть методов, так как Fruit не делает ничего по-своему, но может иметь свойство calories и т.д.

Ответ 6

Если объект фрукта скорее пройден к человеческому объекту, который имеет Eat() функция?

Да.

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

Ответ 7

Вы правы, что, вероятно, есть() было бы методом человека или млекопитающего или плода. Сам плод, вероятно, не имеет никакого поведения, а значит, и методов.

Я не часто рассматриваю преимущества OO в realtion для сопоставления реальных вещей объекту. Это вероятно потому, что мы имеем дело с менее осязаемыми понятиями, такими как счета-фактуры и заказы.

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

Ответ 8

Большинство человеческих языков следуют структуре предложения (для простых утверждений) Объект предметной области

В языках OOP, таких как С++, не совсем иронично, объект является объектом, и он на первом месте, поэтому нормальный порядок обращается вспять:

Итак, это базовый шаблон в С++: -

object.verb( subject );

Это действительно мусор. Большинство классов разработаны с помощью интерфейса subject.verb(object). Знание SVO, однако, позволяет проверить, был ли интерфейс класса спроектирован правильно (в этом случае он этого не сделал).

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

Ответ 9

Я склонен думать о:

Является

Имеет

Итак, яблоко - это плод, поэтому наследование имеет смысл.

Но плод есть (есть) съедобный, может иметь смысл, но это показывает, что это свойство плода, а не действие (метод).

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

Теперь, что бы съесть это, вы могли бы установить, является ли яблоко частью его диеты.

Теперь Has a будет для композиции. Итак, у яблока есть семя, означающее, что семя не распространяется на яблоко, но у яблока будет коллекция семян.

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

Ответ 10

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

Не много, только достаточно.

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

Вам просто нужно использовать его для использования.

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

Итак, в вашем классе фруктов мне бы лучше было что-то вроде Color или указание, если он будет съеден. Например:

 Fruit
     + color : Color
     - isMature : Boolean

     + canBeEaten() : Boolean
          return isMature

Таким образом, вы можете создавать разные фрукты

 apple = Fruit()
 appe.color = Color.red
 out.print( "Can this fruit be eaten? %s ", apple.canBeEaten() )

 orange = Fruit()
 orage.color = Color.orange
 out.print( "Can this fruit be eaten? %s ", orange.canBeEaten() )

Etc.

Если вы видите, что атрибуты (цвет и isMature) хранятся внутри объекта. Таким образом, вам не нужно отслеживать свое состояние извне.

Что касается наследования, это имеет смысл только тогда, когда вам нужно добавить новое поведение к некоторому методу, и да, методы относятся к атрибутам или характеристикам объекта. Как вы указываете, fruit.eat() не имеет большого смысла.

Но рассмотрим метод получения сока из фруктов.

Fruit
    + getJuice(): Juice

Apple -> Fruit
     + getJuice(): Juice
         // do what ever is needed internally to create the juice

Orange -> Fruit
    + getJuice(): Juice
        // here, certainly the way to get the juice will be different

Ответ 11

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

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

Теперь вы можете задать эти вопросы о своем объекте:

  • "Какие responsibilites имеет ли мой объект в системе?"
  • "Является ли этот объект ответственным за выполнение xxx или должен быть xxx ответственным за другой объект?"
  • "Этот объект делает больше, чем должно быть?" (например, если он проектирует для вычисления чего-то, он не должен нести ответственность за загрузку значений)

"Объектное мышление" Дэвида Уэста - хорошая книга для чтения по этой теме.

Ответ 12

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

Возможно, больше похоже на "относить" их к объектам реальной жизни, где это применимо.

Если объект фрукта скорее передается человеческому объекту с функцией Eat()?

Да, или что-то более общее, чем человеческое.

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

Определите только то, что вам нужно определить. Тогда реализация (обычно) становится очень очевидной. Другими словами, вы, вероятно, слишком много думаете.

Ответ 13

Я не думаю, что мы должны попытаться "зеркально отразить объекты реальной жизни". Я думаю, что это больше похоже на объекты реальной жизни, которые очень напоминают поведение, моделируемое в контексте системы (домена). Класс Fruit в игре, где вы делаете кусочек фруктов для очков, может иметь совершенно разные поведения и атрибуты, чем класс Fruit в игре, где персонаж бегает вокруг сбора фруктов за очки; или симуляция людей, которые едят фрукты. Присвоение поведения классам, названным в честь объектов реальной жизни, облегчает принятие поведения модулей кода и предполагает их взаимодействие.