当以面向对象的方式实现大海捞针搜索时,您基本上有三种选择:
1. needle.find(haystack)
2. haystack.find(needle)
3. searcher.find(needle, haystack)
你更喜欢哪个?为什么?
我知道有些人更喜欢第二种选择,因为它避免引入第三种物体。然而,我不禁感到第三种方法在概念上更“正确”,至少如果你的目标是塑造“现实世界”。
在哪种情况下,您认为引入辅助对象是合理的,例如本例中的搜索器,何时应避免使用?
通常应该将动作应用于您正在执行的操作...在这种情况下是haystack,所以我认为选项2是最合适的。
你还有第四种选择,我认为比替代3更好:
haystack.find(needle, searcher)
在这种情况下,它允许您提供要作为操作的一部分进行搜索的方式,因此您可以将操作与正在操作的对象保持一致。
这取决于您的要求。
例如,如果你不关心搜索者的属性(例如搜索者的力量,视力等),那么我会说haystack.find(针)将是最干净的解决方案。
但是,如果你关心搜索者的属性(或任何其他属性),我会将一个ISearcher接口注入haystack构造函数或函数以促进它。这支持面向对象的设计(大海捞针有针)和控制/依赖注入的反转(使得更容易对“查找”功能进行单元测试)。
我可以想到前两种口味中的任何一种都有意义的情况:
needle.findIn(haystack)
(或pattern.findIn(text)
)是有意义的,因为针对象保持为算法创建的中间表有效工作haystack.find(needle)
(或words.hasAWordWithPrefix(prefix)
)效果更好。在上述两种情况下,针或干草堆中的一个都知道搜索。而且,他们彼此都知道。如果你想让针和干草堆不相互了解或搜索,searcher.find(needle, haystack)
将是合适的。
容易:烧干草堆!之后,只留下针头。此外,你可以尝试磁铁。
一个更难的问题:你如何在一针针中找到一根特定的针?
答案:对每个线程进行线程并将每个线程的另一端附加到排序索引(即指针)
这个问题的答案实际上应该取决于实现解决方案的域。 如果您碰巧在物理干草堆中模拟物理搜索,则可能有类
空间 知道哪些对象位于哪个坐标处 实现自然法则(转换能量,检测碰撞等)
针,稻草 位于太空 对力量作出反应
导引头 与空间互动: 移动手,施加磁场,烧干草,应用X射线,寻找针......
因此seeker.find(needle, space)
或seeker.find(needle, space, strategy)
干草堆恰好位于您正在寻找针头的空间中。当你将空间抽象为一种虚拟机时(想想:矩阵)你可以用haystack而不是space来得到上面的内容(解决方案3 / 3b):
seeker.find(needle, haystack)
或seeker.find(needle, haystack, strategy)
但矩阵是域,如果你的针不能在任何其他地方,它只能被干草堆取代。
然后,这只是一个神学。有趣的是,这为全新的方向打开了心灵: 你为什么一开始松开针?你能不能改变这个过程,所以你不会松开它? 你是否必须找到丢失的针头,或者你只是得到另一个而忘记了第一个? (如果针头在一段时间后溶解,那就太好了) 如果你经常松开针,你需要再次找到它们,那么你可能想要
needle.find(space)
或needle.find(haystack)
(解决方案1)这只是说,在你的实现中,你做了针和大海捞针,大多数时候矩阵在某种程度上。
因此,根据您的域名决定:
haystack.find(针),但应该有一个搜索字段。
我知道依赖注入风靡一时,所以@Mike Stone的haystack.find(针,搜索者)已被接受并不让我感到惊讶。但我不同意:选择使用什么样的搜索者似乎是我对大海捞针的决定。
考虑两个ISearchers:MagneticSearcher在体积上迭代,以与磁体强度一致的方式移动和踩踏磁体。 QuickSortSearcher将堆栈分成两部分,直到其中一个子堆中的针头明显。搜索者的正确选择可能取决于干草堆的大小(例如相对于磁场),针如何进入大海捞针(即,针的位置是否真正随机或偏向?)等。
如果你有haystack.find(针,搜索者),你说“选择哪种是最好的搜索策略最好在大海捞针的背景下完成”。我不认为这可能是正确的。我认为“干草堆更有可能知道如何最好地搜索自己”。添加一个setter,如果需要覆盖或测试,仍然可以手动注入搜索器。
就个人而言,我喜欢第二种方法。我的理由是因为我使用的主要API使用这种方法,我发现它最有意义。
如果您有一个事物列表(haystack),您将搜索(find())针。
@Peter Meyer
你明白了。我认为尽可能松散耦合是一件好事,但也许我有点沮丧! :)
呃...是的......我认为IStuffSmallEnoughToBeLostInAHaystack
是一种红旗:-)
你还有第四种选择,我认为比替代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)
更好,因为它简单,所以我愿意为它牺牲一些“正确性”。
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();
真的是2和3的混合。
一些草垛没有专门的搜索策略;一个例子是一个数组。找到一些东西的唯一方法是从头开始并测试每个项目,直到找到你想要的那个。
对于这种事情,自由函数可能是最好的(如C ++)。
一些草垛可以对它们施加专门的搜索策略。例如,可以对数组进行排序,允许您使用二进制搜索。自由函数(或一对自由函数,例如sort和binary_search)可能是最佳选择。
一些草垛有一个综合的搜索策略作为其实施的一部分;例如,关联容器(散列或有序集和映射)都可以。在这种情况下,查找可能是一种必不可少的查找方法,因此它应该是一种方法,即haystack.find(needle)。
在这三个中,我更喜欢选项#3。
Single Responsibility Principle让我不想在我的DTO或模型上添加搜索功能。他们的责任是成为数据,而不是发现自己,也不需要了解干草堆,也不知道干草堆知道针头。
对于它的价值,我认为大多数OO从业者需要花费很长时间才能理解为什么#3是最佳选择。在我真正理解它之前,我做了OO十年。
@wilhelmtell,C ++是极少数具有模板专业化的语言之一,使这样的系统真正起作用。对于大多数语言而言,通用的“查找”方法将是一个可怕的想法。
haystack可以包含东西一种类型的东西是针查找器是负责搜索东西查找器的东西可以接受一堆东西作为在哪里找到东西查找器的来源也可以接受它需要找到的东西的东西描述
所以,最好,对于一个灵活的解决方案,你会做类似的事情:IStuff界面
Haystack = IList <IStuff> Needle:IStuff
Finder .Find(IStuff stuffToLookFor,IList <IStuff> stuffsToLookIn)
在这种情况下,您的解决方案不会仅仅针对needle和haystack,但它可用于实现该接口的任何类型
所以,如果你想在海洋中找到一条鱼,你可以。
var results = Finder.Find(fish,ocean)
如果您有针对象的参考,为什么要搜索它? :)问题域和用例告诉你,你不需要在大海捞针的确切位置(就像你可以从list.indexOf(元素)得到的),你只需要一根针。而你还没有它。所以我的回答是这样的
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);
我同意有更多可能的解决方案基于不同的搜索策略,所以他们介绍了搜索者。对此有充分的评论,所以我不在这里注意它。我的观点是上面的解决方案忘记了用例 - 如果你已经参考了它,那么搜索某些内容有什么意义呢?在最自然的使用情况下,您还没有针,所以您不使用可变针。
布拉德威尔逊指出,对象应该只有一个责任。在极端情况下,一个对象有一个责任而没有一个状态。然后它可以成为......一个功能。
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());
干草堆不应该知道针头,针头不应该知道干草堆。搜索者需要知道这两者,但是大海捞针是否应该知道如何搜索自己才是争论的真正要点。
所以我会选择2和3的混合;大海捞针应该能够告诉别人如何搜索它,搜索者应该能够使用该信息搜索大海捞针。
haystack.magnet().filter(needle);
代码是否试图找到特定针头或任何针头?这听起来像是一个愚蠢的问题,但它改变了问题。
寻找特定针头,问题中的代码是有道理的。寻找任何针都会更像
needle = haystack.findNeedle()
要么
needle = searcher.findNeedle(haystack)
无论哪种方式,我更喜欢有一个上课的搜索者。大海捞针不知道如何搜索。从CS的角度来看,它只是一个你不想要的大量垃圾的数据存储。
绝对是第三个,恕我直言。
大海捞针的问题是尝试在大量其他对象中找到一个对象的一个例子,这表明它需要一个复杂的搜索算法(可能涉及磁铁或(更可能是)子进程)并且它没有'对于希望干草堆进行线程管理或实现复杂搜索非常有意义。
然而,搜索对象专用于搜索,并且可以期望知道如何管理用于快速二进制搜索的子线程,或者使用搜索到的元素的属性来缩小区域(即:磁体以找到黑色物品) 。
另一种可能的方法是为Searchable对象创建两个接口,例如haystack和ToBeSearched对象,例如针。所以,它可以这样做
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
}
}
haystack.iterator.findFirst(/* pass here a predicate returning
true if its argument is a needle that we want */)
iterator
可以与任何不可变集合接口,集合具有常见的findFirst(fun: T => Boolean)
方法。只要大海捞针是不可变的,就不需要隐藏“外部”的任何有用数据。而且,当然,将自定义非平凡集合的实现与其他具有haystack
的东西结合在一起并不好。分而治之,好吗?
在大多数情况下,我更喜欢能够在核心对象上执行这样的简单帮助操作,但是根据语言,所讨论的对象可能没有足够的或合理的方法可用。
即使在像JavaScript这样的语言允许你扩充/扩展内置对象,我发现它既方便又有问题(例如,如果该语言的未来版本引入了一种更有效的方法,可以被自定义方法覆盖)。
This article很好地概述了这种情况。
还有另一种选择,即C ++的STL使用的方法:
find(haystack.begin(), haystack.end(), needle)
我认为这是一个很好的例子,C ++喊“在你的脸上!” OOP。这个想法是OOP不是任何类型的银弹;有时事情最好用行动来描述,有时候就物体而言,有时既不是,也不是两者兼而有之。
Bjarne Stroustrup在TC ++ PL中说,当你设计一个系统时,你应该努力在有效和高效的代码的约束下反映现实。对我来说,这意味着你永远不应盲目追随任何事情。想想手头的事情(干草堆,针)和我们所处的背景(搜索,这就是表达的内容)。
如果重点是搜索,那么使用强调搜索的算法(动作)(即灵活地适应干草堆,海洋,沙漠,链表)。如果重点是haystack,请将find方法封装在haystack对象中,依此类推。
也就是说,有时你会有疑问并且很难做出选择。在这种情况下,面向对象。如果您稍后改变主意,我认为从对象中提取操作然后将操作拆分为对象和类更容易。
遵循这些准则,您的代码将更清晰,更美观。
我会说选项1完全没了。代码应该以一种告诉你它的作用的方式阅读。选项1让我觉得这针会找到我的大海捞针。
如果大海捞针要包含针,选项2看起来不错。 ListCollections总是包含ListItems,所以做collection.find(item)是自然而富有表现力的。
我认为在以下情况下引入辅助对象是合适的:
我和布拉德就这个问题。我在越来越复杂的系统上工作的越多,我就越需要真正解耦对象。他是对的。显而易见的是针头不应该对干草堆知之甚少,所以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
你明白了。我认为尽可能松散耦合是一件好事,但也许我有点沮丧! :)
如果Needle和Haystack都是DAO,则选项1和2是不可能的。
这样做的原因是DAO应该只负责保存他们正在建模的现实世界对象的属性,并且只有getter和setter方法(或者只是直接属性访问)。这使得将DAO序列化为文件,或者创建通用比较/通用副本的方法更容易编写,因为代码不会包含一大堆“if”语句来跳过这些辅助方法。
这只留下选项3,大多数人都同意这是正确的行为。
选项3有一些优点,最大的优点是单元测试。这是因为现在可以很容易地模拟Needle和Haystack对象,而如果使用选项1或2,则必须在执行搜索之前修改Needle或Haystack的内部状态。
其次,当搜索者现在在一个单独的类中时,所有搜索代码都可以保存在一个地方,包括常见的搜索代码。如果将搜索代码放入DAO中,则常用搜索代码将存储在复杂的类层次结构中,或者仍然存储在Searcher Helper类中。
这完全取决于变化的内容和保持不变的内容。
例如,我正在研究(非OOP)framework,其中查找算法根据针的类型和干草堆而不同。除了这需要在面向对象的环境中进行双重调度之外,还意味着编写needle.find(haystack)
或编写haystack.find(needle)
没有意义。
另一方面,您的应用程序可以愉快地将查找委托给两个类中的任何一个,或者完全坚持使用一个算法,在这种情况下,决策是任意的。在这种情况下,我更喜欢haystack.find(needle)
方式,因为将这一发现应用于大海捞针似乎更合乎逻辑。
当以面向对象的方式实现大海捞针搜索时,您基本上有三种选择:
- needle.find(干草堆)
- haystack.find(针)
- searcher.find(针,干草堆)
你更喜欢哪个?为什么?
如果我错了,请纠正我,但在所有三个例子中,你已经提到了你正在寻找的针头,所以这不是有点像坐在你的鼻子上寻找你的眼镜吗? :p
除了旁白,我认为这实际上取决于你认为大海捞针在给定领域内的责任。我们只关心它是一个含有针的东西(一个集合,基本上)?然后haystack.find(needlePredicate)很好。否则,farmBoy.find(谓词,haystack)可能更合适。
引用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?
更好地表达了程序的含义。