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

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

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

Давайте возьмем иерархию классов:

class Fruit {
    void Eat() {

    }
}

class Apple extends Fruit {

}

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

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

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

13 ответов

Решение

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

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

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

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

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

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

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

Я думаю, вы должны прочитать принципы SOLID, это вам очень поможет. http://www.lostechies.com/blogs/chad_myers/archive/2008/03/07/pablo-s-topic-of-the-month-march-solid-principles.aspx

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

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

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

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

Да.

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

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

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

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

Вам просто нужно базовое, чтобы использовать его.

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

Так что в вашем классе фруктов я бы лучше что-то 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() )

И т.п.

Если вы видите, что атрибуты (color и 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

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

Это

Имеет

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

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

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

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

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

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

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

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

Так что это "базовый" шаблон в C++:

object.verb( subject );

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

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

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

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

Теперь вы можете задать следующие вопросы о вашем объекте:

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

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам