您如何在大海捞针中找到针?


74

当以面向对象的方式实现大海捞针的搜索时,您实际上有三种选择:

1. needle.find(haystack)

2. haystack.find(needle)

3. searcher.find(needle, haystack)

你更喜欢哪个?为什么?

我知道有些人喜欢第二种选择,因为它避免引入第三个对象。但是,至少在您的目标是为“真实世界”建模的过程中,我不禁会觉得第三种方法在概念上更“正确”。

您认为在什么情况下引入助手对象是合理的,例如本例中的搜索器,何时应避免使用它们?


1
+1好的标题和好的语义问题。大多数每种OO语言都会遇到这样的问题(请考虑使用Python的string.join方法)。
new123456 2011年

Answers:


53

通常情况下,应该将操作应用于您正在执行的操作...在这种情况下,应该选择大海捞针,因此我认为选项2最合适。

您还有第四个替代方案,我认为它比替代方案3更好:

haystack.find(needle, searcher)

在这种情况下,它允许您提供要作为操作一部分进行搜索的方式,因此可以将操作与正在操作的对象保持在一起。


1
这看起来很像策略模式(en.wikipedia.org/wiki/Strategy_pattern
ARKBAN

这看起来很像是违反“单一责任原则”的行为。选项3绝对是更好的设计选择。
凯尔·卡特梅尔

1
实际上,这看起来更像Visitor模式;当然,这就是它的隐含意图。“搜索者”可以是访问者目标的任何搜索算法。当然 ,必须公开一些“干草堆”(我想是HayCollection),以便“搜索者”进行搜索;但这并不意味着实际上或在精神上都违反了SRP。 en.wikipedia.org/wiki/Visitor_pattern
radarbob

55

在这三个中,我更喜欢选择#3。

单一职责原则使我不希望把搜索我的DTO或模型的能力。他们的责任是成为数据,而不是发现自己,针头也不需要了解干草堆,干草堆也不需了解针头。

对于它的价值,我认为大多数面向对象的从业人员都需要很长时间才能理解为什么#3是最佳选择。在真正摸索之前,我可能已经做了十年了。

@ wilhelmtell,C ++是使模板系统真正起作用的极少数具有模板专业化的语言之一。对于大多数语言,通用的“查找”方法将是一个可怕的想法。


16

还有另一种选择,这是C ++的STL使用的方法:

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

我认为这是C ++大喊大叫的一个很好的例子!面向对象。想法是,面向对象编程不是任何形式的灵丹妙药。有时最好用动作来描述事物,有时用对象来描述,有时两者都不是,有时两者都可以。

Bjarne Stroustrup在TC ++ PL中表示,在设计系统时,您应努力在有效代码的约束下反映现实。对我来说,这意味着您切勿盲目跟随任何事情。考虑一下即将发生的事情(干草堆,针叶树)以及我们所处的环境(搜索,这就是表达的意思)。

如果重点是搜索,则使用强调搜索的算法(动作)(即灵活地适合大海捞针,海洋,沙漠,链表)。如果重点是干草堆,则将find方法封装在干草堆对象中,依此类推。

也就是说,有时您会感到疑惑,并且很难做出选择。在这种情况下,应面向对象。如果您以后改变主意,我认为从对象提取动作然后将动作拆分为对象和类会更容易。

遵循这些准则,您的代码将更加清晰,美观。


15

我要说的是选项1完全没有了。该代码应以告诉您的方式进行阅读。选项1让我认为这针将要去找我一个大海捞针。

如果要在大海捞针中放置草堆,则选项2看起来不错。ListCollections总是包含ListItems,因此执行collection.find(item)是自然而富有表现力的。

我认为在以下情况下适当地引入辅助对象:

  1. 您不控制问题
    IE中对象的实现:search.find(ObsecureOSObject,file)

  2. IE之间没有常规或合理的关系:nameMatcher.find(houses,trees.name)

您可以使用needle.findIn(干草堆)。变量名称不是这里的问题,而是语法。
08年

11

我和布拉德在一起。我在极其复杂的系统上工作的越多,就越需要真正分离对象。他是对的。很明显,一根针对干草堆一无所知,因此1肯定已经淘汰了。但是,大海捞针对针头一无所知。

如果要对干草堆建模,则可以将其实现为一个集合-但可以作为干草稻草的集合-而不是针类的集合!但是,我会考虑到这些东西确实会丢失在大海捞针中,但是我对这些东西到底是什么一无所知。我认为最好不要让大海捞针本身寻找物品(无论如何,大海捞针多么聪明)。对我而言,正确的方法是让干草堆展示其中的所有物品,但不是秸秆,干草或任何赋予干草堆本质的东西。

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

如果Needle和Haystack都是DAO,则选项1和2都不可行。

这样做的原因是,DAO仅应负责保存它们正在建模的现实世界对象的属性,并且仅具有getter和setter方法(或仅具有直接属性访问权限)。这使得将DAO序列化为文件,或为通用比较/通用副本创建方法更容易编写,因为代码不会包含一堆“ if”语句来跳过这些辅助方法。

这只剩下选项3,大多数人都同意这是正确的行为。

选项3具有一些优点,最大的优点是单元测试。这是因为现在可以轻松地模拟Needle和Haystack对象,而如果使用选项1或2,则必须先修改Needle或Haystack的内部状态,然后才能执行搜索。

其次,现在将搜索者放在一个单独的类中,所有搜索代码都可以保存在一个地方,包括通用搜索代码。而如果将搜索代码放入DAO中,则常见的搜索代码要么存储在复杂的类层次结构中,要么存储在Searcher Helper类中。


5

这完全取决于变化和保持不变。

例如,我正在一个(非OOP)框架上,根据针的类型和干草堆的不同,查找算法也不同。除了在面向对象的环境中这需要两次调度这一事实之外,这还意味着写needle.find(haystack)或写都没有意义haystack.find(needle)

另一方面,您的应用程序可以很高兴地将查找结果委托给两个类中的任何一个,或者完全采用一种算法,在这种情况下,决策是任意的。在那种情况下,我更喜欢这种haystack.find(needle)方式,因为将发现应用于干草堆似乎更合乎逻辑。


5

当以面向对象的方式实现大海捞针的搜索时,您实际上有三种选择:

  1. 针刺发现(干草堆)

  2. haystack.find(needle)

  3. searcher.find(针,干草堆)

你更喜欢哪个?为什么?

纠正我,如果我错了,但在所有三个例子你已经拥有你正在寻找的针的引用,所以这不就是有点像找你的眼镜时,他们坐在你的鼻子?:p

撇开双关语,我认为这实际上取决于您认为大海捞针在给定范围内的责任。我们是否只是在关心包含针的东西(本质上是一个集合)?然后haystack.find(needlePredicate)很好。否则,farmBoy.find(predicate,haystack)可能更合适。


needle可能是一种类型。但是含糊不清导致人们相信这可能是鸡与蛋的情况。
new123456 2011年

4

引用SICP的伟大作者,

必须编写程序供人们阅读,并且只能偶然地使机器执行

我更喜欢同时使用方法1和2。以红宝石为例,它附带了.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

这取决于您的要求。

例如,如果您不关心搜索者的属性(例如,搜索者的力量,视野等),那么我会说haystack.find(needle)是最干净的解决方案。

但是,如果您确实关心搜索器的属性(或与此相关的任何其他属性),我会向干草堆构造函数或函数中注入ISearcher接口以简化此操作。这既支持面向对象的设计(大海捞针),又支持控制/依赖注入的倒置(使对“查找”功能的单元测试更加容易)。


3

我可以想到前两种口味中的任何一种都有意义的情况:

  1. 如果针头需要进行预处理(如Knuth-Morris-Pratt算法中那样),则needle.findIn(haystack)(或pattern.findIn(text))是有意义的,因为针头对象拥有为算法有效运行而创建的中间表

  2. 如果干草堆需要进行预处理(例如尝试),则haystack.find(needle)(或words.hasAWordWithPrefix(prefix))效果更好。

在以上两种情况下,针头或干草堆之一都知道搜索。此外,他们俩彼此了解。如果您不希望针头和干草堆相互了解或进行搜索,则searcher.find(needle, haystack)比较合适。


2

简单:烧干草堆!之后,仅保留针头。另外,您可以尝试磁铁。

一个更棘手的问题:如何在针池中找到一根特定的针?

答案:对每个线程进行线程处理,并将线程的另一端附加到排序的索引(即指针)上


2

该问题的答案实际上应取决于解决方案所针对的领域。
如果您碰巧在物理草垛中模拟物理搜索,则可能有此类

  • 空间
  • 稻草
  • 搜寻者

太空
知道哪些物体位于哪个坐标处,从而
实现自然法则(转换能量,检测碰撞等)

稻草
位于太空中,
对力产生反作用

搜寻者
与空间互动:
    移动手,施加磁场,烧干草,施加X射线,寻找针头...

因此  seeker.find(needle, space)   或   seeker.find(needle, space, strategy)

大海捞针恰好在您要寻找针头的地方。当您将空间抽象为一种虚拟机(认为是矩阵)时,您可以使用干草堆代替空间来获得上述内容(解决方案3 / 3b)

seeker.find(needle, haystack)     要么     seeker.find(needle, haystack, strategy)

但是矩阵是域,如果您的针头不能放在其他任何地方,则只能用干草堆代替。

再说一次,那只是一种动物。有趣的是,这为全新的方向开辟了思路:
1.为什么首先松开针头?您可以更改流程,所以不会放弃它吗?
2.您是否必须找到丢失的针头,还是可以简单地再买一根针而忘记第一根呢?(然后,如果一段时间后针头溶解,那将是很好的选择)
。3.如果您定期松动针头并且需要再次找到它们,则可能需要

  • 制造能够找到自己的针,例如,他们经常问自己:我迷路了吗?如果答案为是,他们会将GPS计算的位置发送给某人或开始发出哔哔声或其他声音:
    needle.find(space)needle.find(haystack)    (解决方案1)

  • 在每个稻草上安装带有照相机的干草堆,之后您可以询问干草堆蜂巢的心智是否最近看过针:
    haystack.find(needle)(解决方案2)

  • 将RFID标签贴到您的针上,这样就可以轻松地对它们进行三角剖分

就是说,在您的实现中,已经在某种程度上制造了针,干草堆以及大多数情况下的矩阵。

因此,请根据您的域来决定:

  • 大海捞针的目的是为了大海捞针吗?然后寻求解决方案2。
  • 针在任何地方丢失是否自然?然后寻求解决方案1。
  • 针头是否会意外丢入大海捞针?然后寻求解决方案3。(或考虑其他恢复策略)

2

haystack.find(needle),但应该有一个搜索器字段。

我知道依赖注入非常流行,所以@Mike Stone的haystack.find(needle,searcher)被接受并不奇怪。但是我不同意:选择哪种搜索器对我来说似乎是一个决定。

考虑两个ISearcher:MagneticSearcher遍历整个体积,以与磁体强度一致的方式移动和步进磁体。QuickSortSearcher将堆栈分为两部分,直到其中一个子堆中的针明显为止。搜索者的正确选择可能取决于干草堆的大小(例如,相对于磁场),针如何进入干草堆(即,针的位置是真正随机的还是偏斜的?)等。

如果您有haystack.find(needle,searcher),那么您的意思是“最好的选择是在干草堆之外进行的。” 我认为这可能不正确。我认为“干草堆知道如何最好地自我搜索”的可能性更大。添加设置器,如果您需要覆盖或进行测试,仍然可以手动注入搜索器。


1

我个人比较喜欢第二种方法。我的理由是,因为我使用的主要API都使用了这种方法,所以我认为这是最有意义的。

如果您有东西列表(干草堆),则将搜索(find())针。


1

彼得·迈耶(Peter Meyer)

你明白了。我认为尽可能松散地耦合是一件好事,但也许我有点被迷住了!:)

嗯...是的...我认为这IStuffSmallEnoughToBeLostInAHaystack是一个危险信号:-)


1

您还有第四个替代方案,我认为它比替代方案3更好:

haystack.find(needle, searcher)

我明白你的意思,但是如果searcher实现一个搜索接口,该接口允许搜索除干草堆以外的其他类型的对象,并在其中找到除针头之外的其他东西,该怎么办?

该接口还可以使用不同的算法来实现,例如:

binary_searcher.find(needle, haystack)
vision_searcher.find(pitchfork, haystack)
brute_force_searcher.find(money, wallet)

但是,正如其他人已经指出的那样,我还认为,只有当您实际上具有多个搜索算法或多个可搜索或可找到的类时,这才有用。如果不是这样,我同意haystack.find(needle)它的简单性会更好,因此我愿意为此牺牲一些“正确性”。


1
class Haystack {//whatever
 };
class Needle {//whatever
 }:
class Searcher {
   virtual void find() = 0;
};

class HaystackSearcher::public Searcher {
public:
   HaystackSearcher(Haystack, object)
   virtual void find();
};

Haystack H;
Needle N;
HaystackSearcher HS(H, N);
HS.find();

1

确实是2和3的混合。

一些干草堆没有专门的搜索策略;一个例子是数组。找到某物的唯一方法是从头开始并测试每一项,直到找到所需的东西为止。

对于这种情况,自由函数可能是最好的(例如C ++)。

一些干草堆可以对它们施加专门的搜索策略。可以对数组进行排序,例如,允许您使用二进制搜索。一个自由函数(或一对自由函数,例如sort和binary_search)可能是最好的选择。

一些草垛将集成搜索策略作为其实施的一部分;例如,关联容器(哈希或有序集合和映射)都可以。在这种情况下,查找可能是必不可少的查找方法,因此它可能应该是一种方法,即haystack.find(needle)。


1

干草堆可以包含东西。一种类型的东西是检针器,它是负责搜索东西的东西,可以接受一堆东西作为在哪里找到东西的源。发现者还可以接受需要寻找的东西的东西描述

因此,最好是,对于灵活的解决方案,您可以执行以下操作:IStuff接口

干草堆= IList <IStuff>针:IStuff

Finder .Find(IStuff stuffToLookFor,IList <IStuff> stuffsToLookIn)

在这种情况下,您的解决方案将不仅仅局限于针和干草堆,而是可用于实现该接口的任何类型

因此,如果您想在海洋中找到一条鱼,就可以。

var结果= Finder.Find(鱼类,海洋)


1

如果您有对针头物体的参考,为什么要搜索它?:)问题域和用例告诉您,不需要在大海捞针中的确切位置(就像从list.indexOf(element)可以得到的一样),您只需要一根针即可。而且您还没有。所以我的答案是这样的

Needle needle = (Needle)haystack.searchByName("needle");

要么

Needle needle = (Needle)haystack.searchWithFilter(new Filter(){
    public boolean isWhatYouNeed(Object obj)
    {
        return obj instanceof Needle;
    }
});

要么

Needle needle = (Needle)haystack.searchByPattern(Size.SMALL, 
                                                 Sharpness.SHARP, 
                                                 Material.METAL);

我同意,有更多可能的解决方案基于不同的搜索策略,因此它们介绍了搜索器。关于此有足够的评论,因此在这里我不关注它。我的观点是,上面的解决方案会忘记用例-如果您已经参考了用例,那么寻找点是什么呢?在最自然的使用情况下,您还没有针,因此不要使用可变针。


1

布拉德·威尔逊(Brad Wilson)指出,对象应该承担单一责任。在极端情况下,一个对象只有一个责任而没有状态。然后它就可以成为...一个功能。

needle = findNeedleIn(haystack);

或者您可以这样写:

SynchronizedHaystackSearcherProxyFactory proxyFactory =
    SynchronizedHaystackSearcherProxyFactory.getInstance();
StrategyBasedHaystackSearcher searcher =
    new BasicStrategyBasedHaystackSearcher(
        NeedleSeekingStrategies.getMethodicalInstance());
SynchronizedHaystackSearcherProxy proxy =
    proxyFactory.createSynchronizedHaystackSearcherProxy(searcher);
SearchableHaystackAdapter searchableHaystack =
    new SearchableHaystackAdapter(haystack);
FindableSearchResultObject foundObject = null;
while (!HaystackSearcherUtil.isNeedleObject(foundObject)) {
    try {
        foundObject = proxy.find(searchableHaystack);
    } catch (GruesomeInjuryException exc) {
        returnPitchforkToShed();  // sigh, i hate it when this happens
        HaystackSearcherUtil.cleanUp(hay); // XXX fixme not really thread-safe,
                                           // but we can't just leave this mess
        HaystackSearcherUtil.cleanup(exc.getGruesomeMess()); // bug 510000884
        throw exc; // caller will catch this and get us to a hospital,
                   // if it's not already too late
    }
}
return (Needle) BarnyardObjectProtocolUtil.createSynchronizedFindableSearchResultObjectProxyAdapterUnwrapperForToolInterfaceName(SimpleToolInterfaces.NEEDLE_INTERFACE_NAME).adapt(foundObject.getAdaptable());

lambda,终极外观模式
Jason Orendorff

1

大海捞针不应该知道针,大海捞针不应该知道大海捞针。搜索者需要了解这两者,但是大海捞针是否应该知道如何进行搜索本身才是争论的重点。

所以我将混合使用2和3。干草堆应该能够告诉其他人如何进行搜索,而搜索者应该能够使用该信息来搜索干草堆。



1

代码是在试图找到特定的针头还是只是寻找任何针头?听起来像一个愚蠢的问题,但它改变了这个问题。

寻找特定的针头可以解决问题中的代码。寻找任何针头会更像

needle = haystack.findNeedle()

要么

needle = searcher.findNeedle(haystack)

无论哪种方式,我都希望有一个该类的搜索者。大海捞针不知道如何搜索。从CS的角度来看,它只是您不想要的带有很多废话的数据存储。


这是个好问题。我假设我们想找到特定的针,即您在大海捞针中丢失的针。如果您只是想找到任何针,那么先前建议的磁铁方法也可以使用...;)
Anders Sandvig,

0

绝对是第三,恕我直言。

大海捞针的问题就是试图在大量其他物体中找到一个物体的一个例子,这表明它将需要一个复杂的搜索算法(可能涉及磁铁或(更可能的)子过程),而它并没有。期望大海捞针进行线程管理或实现复杂的搜索是很有意义的。

但是,搜索对象专用于搜索,并且可以期望知道如何管理子线程以进行快速二进制搜索,或者使用搜索到的元素的属性来缩小区域(即:寻找铁质物品的磁铁) 。


0

另一种可能的方式是为可搜索对象(例如干草堆)和ToBeSearched对象(例如needle)创建两个接口。因此,可以通过这种方式完成

public Interface IToBeSearched
{}

public Interface ISearchable
{

    public void Find(IToBeSearched a);

}

Class Needle Implements IToBeSearched
{}

Class Haystack Implements ISearchable
{

    public void Find(IToBeSearched needle)

   {

   //Here goes the original coding of find function

   }

}

0
haystack.iterator.findFirst(/* pass here a predicate returning
                               true if its argument is a needle that we want */)

iterator可以作为任何不可变集合的接口,而集合具有共同的findFirst(fun: T => Boolean)方法即可完成工作。只要干草堆是不可变的,就无需从“外部”隐藏任何有用的数据。而且,当然,将自定义非平凡集合的实现与其他确实具有的东西捆绑在一起并不是很好haystack。分而治之,好吗?


0

在大多数情况下,我希望能够对核心对象执行类似这样的简单帮助程序操作,但是根据语言的不同,所讨论的对象可能没有足够或明智的方法。

即使使用JavaScript之类的语言允许您扩展/扩展内置对象的)中,我也发现它既方便又有问题(例如,如果该语言的未来版本引入了一种更有效的方法,而该方法会被自定义方法所取代)。

本文在概述此类方案方面做得很好。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.