Как вы находите иголку в стоге сена? - PullRequest
72 голосов
/ 23 августа 2008

При реализации поиска иголки в стоге сена объектно-ориентированным способом у вас есть три варианта:

1. needle.find(haystack)

2. haystack.find(needle)

3. searcher.find(needle, haystack)

Что вы предпочитаете и почему?

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

Как вы думаете, в каких случаях оправданно вводить вспомогательные объекты, такие как искатель в этом примере, и когда их следует избегать?

Ответы [ 29 ]

55 голосов
/ 23 августа 2008

Из трех я предпочитаю вариант № 3.

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

Что бы это ни стоило, я думаю, что большинству ОО-специалистов требуется ДЛИННОЕ время, чтобы понять, почему № 3 - лучший выбор. Я делал ОО в течение десятилетия, возможно, прежде, чем я действительно ухватил это.

@ wilhelmtell, C ++ - один из очень немногих языков со специализацией шаблонов, благодаря которым такая система действительно работает. Для большинства языков универсальный метод поиска будет УЖАСНОЙ идеей.

52 голосов
/ 23 августа 2008

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

У вас также есть четвертый вариант, который, я думаю, будет лучше, чем вариант 3:

haystack.find(needle, searcher)

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

16 голосов
/ 23 августа 2008

Существует еще одна альтернатива, которая является подходом, используемым STL C ++:

find(haystack.begin(), haystack.end(), needle)

Я думаю, что это отличный пример крика C ++ "в лицо!" в ООП. Идея состоит в том, что ООП не является какой-либо серебряной пулей; иногда вещи лучше всего описывать с точки зрения действий, иногда с точки зрения объектов, иногда ни одного, а иногда и того и другого.

Бьярн Страуструп сказал в TC ++ PL, что когда вы разрабатываете систему, вы должны стремиться отражать реальность в рамках ограничений эффективного и действенного кода. Для меня это означает, что вы никогда не должны следовать слепо. Подумайте о том, что у вас под рукой (стог сена, иголка), и о контексте, в котором мы находимся (поиск, в этом и состоит выражение).

Если упор делается на поиск, то используется алгоритм (действие), который делает упор на поиск (то есть гибко подходит для стогов сена, океанов, пустынь, связанных списков). Если акцент делается на стоге сена, инкапсулируйте метод find внутри объекта стога сена и т. Д.

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

Следуйте этим рекомендациям, и ваш код будет более понятным и, в общем, красивее.

15 голосов
/ 23 августа 2008

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

Вариант 2 выглядит хорошо, если в стоге сена должны быть иглы. ListCollections всегда будут содержать ListItems, поэтому collection.find (item) является естественным и выразительным.

Я думаю, что введение вспомогательного объекта целесообразно, когда:

  1. Вы не контролируете реализацию рассматриваемых объектов
    IE: search.find (ObsecureOSObject, file)
  2. Между объектами нет регулярных или разумных отношений
    IE: nameMatcher.find (дома, деревья.имя)
11 голосов
/ 23 августа 2008

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

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

class Haystack : ISearchableThingsOnAFarm {
   ICollection<Hay> myHay;
   ICollection<IStuffSmallEnoughToBeLostInAHaystack> stuffLostInMe;

   public ICollection<Hay> Hay {
      get {
         return myHay;
      }
   }

   public ICollection<IStuffSmallEnoughToBeLostInAHayStack> LostAndFound {
      get {
        return stuffLostInMe;
      }
   }
}

class Needle : IStuffSmallEnoughToBeLostInAHaystack {
}

class Farmer {
  Search(Haystack haystack, 
                 IStuffSmallEnoughToBeLostInAHaystack itemToFind)
}

На самом деле я собирался печатать и абстрагироваться от интерфейсов, а потом понял, насколько я сумасшедший. Чувствовал, как я учился в колледже CS: P

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

6 голосов
/ 08 сентября 2008

Если Игла и Хейстек являются DAO, то варианты 1 и 2 исключены.

Причина этого в том, что DAO должны нести ответственность только за хранение свойств объектов реального мира, которые они моделируют, и иметь только методы получения и установки (или только прямой доступ к свойству). Это упрощает сериализацию DAO в файл или создание методов для универсального сравнения / универсальной копии, поскольку код не будет содержать целую кучу операторов «if», пропускающих эти вспомогательные методы.

Это просто оставляет вариант 3, который большинство согласится с правильным поведением.

Вариант 3 имеет несколько преимуществ, при этом наибольшим преимуществом является модульное тестирование. Это связано с тем, что теперь объекты Needle и Haystack могут быть легко смоделированы, в то время как при использовании опции 1 или 2 внутреннее состояние Needle или Haystack пришлось бы изменить, прежде чем можно будет выполнить поиск.

Во-вторых, теперь, когда поисковик находится в отдельном классе, весь код поиска может храниться в одном месте, включая общий код поиска. Принимая во внимание, что если бы код поиска был помещен в DAO, общий код поиска был бы либо сохранен в сложной иерархии классов, либо в любом случае с классом Searcher Helper.

5 голосов
/ 25 августа 2008

При реализации поиска иглы стог сена в объектно-ориентированном виде, у тебя по сути три альтернативы:

  1. needle.find (стог)

  2. haystack.find (игла)

  3. searcher.find (иголка, стог сена)

Что вы предпочитаете и почему?

Поправьте меня, если я ошибаюсь, но во всех трех примерах у вас уже есть ссылка на иглу, которую вы ищете, так что это не похоже на поиск ваших очков, когда они ' сидишь на носу? : Р

Не обращайте внимания, я думаю, это действительно зависит от того, что вы считаете ответственностью стога сена в пределах данной области. Мы просто заботимся об этом в смысле того, чтобы быть предметом, который содержит иглы (по сути, коллекцию)? Тогда haystack.find (needlePredicate) в порядке. В противном случае, уместнее использовать farmBoy.find (предикат, стог сена).

5 голосов
/ 23 августа 2008

Это полностью зависит от того, что меняется и что остается неизменным.

Например, я работаю над (не-OOP) framework , где алгоритм поиска отличается в зависимости как от типа иглы, так и от стога сена. Помимо того, что это потребовало бы двойной диспетчеризации в объектно-ориентированной среде, это также означает, что не имеет смысла писать либо needle.find(haystack), либо писать haystack.find(needle).

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

4 голосов
/ 23 августа 2008

По словам великих авторов SICP ,

Программы должны быть написаны для того, чтобы люди могли читать, и только для машин, которые выполняются

Я предпочитаю иметь под рукой оба метода: 1 и 2. Используя ruby ​​в качестве примера, он поставляется с .include?, который используется следующим образом:

haystack.include? needle
=> returns true if the haystack includes the needle

Иногда, хотя, исключительно из соображений читабельности, я хочу перевернуть его. Ruby не поставляется с in? методом, но он состоит из одной строки, поэтому я часто делаю это:

needle.in? haystack
=> exactly the same as above

Если «важнее» подчеркнуть стог сена или операцию поиска, я предпочитаю написать include?. Часто, однако, ни стог сена, ни поиск не являются действительно тем, что вас волнует, просто наличие объекта - в этом случае я считаю, что in? лучше передает смысл программы.

3 голосов
/ 23 августа 2008

Это зависит от ваших требований.

Например, если вас не интересуют свойства искателя (например, сила искателя, зрение и т. Д.), Я бы сказал, что haystack.find (игла) будет самым чистым решением.

Но, если вам небезразличны свойства поисковика (или любые другие свойства в этом отношении), я бы добавил интерфейс ISearcher либо в конструктор стога сена, либо в функцию, чтобы облегчить это. Это поддерживает как объектно-ориентированное проектирование (стог сена имеет иглы), так и инверсию управления / внедрения зависимостей (упрощает модульное тестирование функции «найти»).

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