8

我正在实现一个类来比较目录树(在 C# 中)。起初我在类的构造函数中实现了实际的比较。像这样:

DirectoryComparer c = new DirectoryComparer("C:\\Dir1", "C:\\Dir2");

但是在构造函数中做一个可能冗长的操作感觉不“正确”。另一种方法是将构造函数设为私有并添加如下静态方法:

DirectoryComparer c = DirectoryComparer.Compare("C:\\Dir1", "C:\\Dir2");

你怎么看?您希望构造函数“快速”吗?第二个例子更好还是只是使类的使用复杂化?

顺便提一句:

我不会将任何答案标记为已接受,因为我认为没有正确答案,只有偏好和品味。

编辑:

只是为了澄清我的例子。我不仅对目录是否不同感兴趣,而且对它们的不同之处(哪些文件)感兴趣。所以一个简单的 int 返回值是不够的。cdragon76.myopenid.com 的答案实际上非常接近我想要的(+1 给你)。

4

14 回答 14

12

我认为两者的组合是“正确”的选择,因为我希望 Compare 方法返回比较结果,而不是比较器本身。

DirectoryComparer c = new DirectoryComparer();

int equality = c.Compare("C:\\Dir1", "C:\\Dir2");

...正如 Dana 提到的,.Net 中有一个IComparer接口反映了这种模式。

IComparer.Compare方法返回一个 int ,因为 IComparer 类的使用主要用于排序。一般模式虽然适合问题的问题:

  1. 构造函数使用(可选)“配置”参数初始化实例
  2. 比较方法接受两个“数据”参数,比较它们并返回一个“结果”

现在,结果可以是一个 int、一个 bool、一个 diff 集合。什么都适合需要。

于 2008-11-06T20:01:09.063 回答
10

我更喜欢第二个。

我希望构造函数实例化该类。compare 方法完成了它的设计目的。

于 2008-11-06T19:56:12.523 回答
5

我认为界面可能是您所追求的。我会创建一个类来表示一个目录,并让它实现 DirectoryComparer 接口。该接口将包括 compare 方法。如果 C# 已经有一个 Comparable 接口,你也可以实现它。

在代码中,您的调用将是:

D1 = new Directory("C:\");
..
D1.compare(D2);
于 2008-11-06T19:58:18.680 回答
3

你永远不应该做任何可能在构造函数中失败的事情。您不想创建无效对象。虽然您可以实现对象不做太多事情的“僵尸”状态,但最好在单独的方法中执行任何复杂的逻辑。

于 2008-11-06T19:56:14.363 回答
3

我同意不要在构造函数中进行冗长操作的普遍观点。

此外,在设计主题时,我会考虑更改您的第二个示例,以便该DirectoryComparer.Compare方法返回对象以外的其他内容DirectoryComparer。(也许是一个名为DirectoryDifferencesor的新类DirectoryComparisonResult。)类型的对象DirectoryComparer听起来像是用于比较目录的对象,而不是表示一对目录之间差异的对象。

然后,如果您想定义比较目录的不同方式(例如忽略时间戳、只读属性、空目录等),您可以将这些参数传递给DirectoryComparer类构造函数。或者,如果您总是希望DirectoryComparer使用完全相同的规则来比较目录,您可以简单地创建DirectoryComparer一个静态类。

例如:

DirectoryComparer comparer = new DirectoryComparer(
    DirectoryComparerOptions.IgnoreDirectoryAttributes
);
DirectoryComparerResult result = comparer.Compare("C:\\Dir1", "C:\\Dir2");
于 2008-11-06T20:57:22.510 回答
2

是的,通常构造函数是快速的,它旨在准备对象以供使用,而不是实际执行操作。我喜欢您的第二个选项,因为它保持单线操作。

您还可以通过允许构造函数传递两个路径来使其更容易一些,然后使用一个实际执行处理的 Compare() 方法。

于 2008-11-06T19:56:18.280 回答
1

我喜欢第二个例子,因为它解释了实例化对象时到底发生了什么。另外,我总是使用构造函数来初始化类的所有全局设置。

于 2008-11-06T20:01:06.133 回答
1

我认为对于通用比较器,您可能在构建时只想指定要比较的文件,然后再进行比较-这样您还可以实现扩展逻辑:

  • 再比较一下——如果目录改变了怎么办?
  • 通过更新成员来更改您正在比较的文件。

此外,您可能希望在您的实现中考虑在目标目录中的文件已更改时从您的操作系统接收消息 - 并且可以选择再次重新比较。

关键是-您通过假设此类仅用于对这些文件的单个实例进行一次比较来施加限制。

因此,我更喜欢:

DirectoryComparer = new DirectoryComparer(&Dir1,&Dir2);

DirectoryComparer->Compare();

或者

DirectoryComparer = new DirectoryComparer();

DirectoryComparer->Compare(&Dir1,&Dir2);

于 2008-11-06T20:04:08.233 回答
1

我认为构造函数不仅可以根据需要花费尽可能多的时间来构造有效对象,而且构造函数也必须这样做。推迟对象创建是非常糟糕的,因为您最终会得到可能无效的对象。因此,您必须每次在触摸对象之前检查它(这就是在 MFC 中完成的方式,您bool IsValid()到处都有方法)。

我只看到创建对象的两种方式略有不同。无论如何,人们都可以将 new 运算符视为类的静态函数。所以,这一切都归结为语法糖。

DirectoryComparer班级做什么?它的责任是什么?从我的角度来看(这是 C++ 程序员的观点),看起来你最好只使用一个自由函数,但我认为你不能在 C# 中拥有自由函数,对吗?我猜你会收集DirectoryComparer对象中不同的文件。如果是这样,您可以更好地创建文件数组或相应命名的等效类之类的东西。

于 2008-11-07T14:30:51.900 回答
0

如果您正在使用 C#,您可以使用扩展方法创建一个方法来比较您将附加到 DirectoryClass 中的构建的 2 个目录,因此它看起来像:

Directory dir1 = new Directory("C:\.....");
Directory dir2 = new Directory("D:\.....");

DirectoryCompare c = dir1.CompareTo(dir2);

这将是更清晰的实现。更多关于这里的扩展方法。

于 2008-11-06T20:28:18.340 回答
0

如果一个操作可能需要未知的时间,那么您可能希望将它导出到不同的线程中(这样您的主线程就不会阻塞并且可以做其他事情,例如显示一个旋转的进度指示器)。其他应用程序可能不想这样做,他们可能希望在单个线程中完成所有操作(例如,那些没有 UI 的应用程序)。恕我直言,将对象创建移动到单独的线程有点尴尬。我宁愿在我当前的线程中(快速)创建对象,然后让它的一个方法在另一个线程中运行,一旦方法完成运行,另一个线程可能会死掉,我可以在我的当前线程通过在转储对象之前使用对象的另一种方法,因为我'

于 2008-11-06T20:34:20.840 回答
0

如果参数只被处理一次,那么我认为它们不属于构造函数参数或实例状态。

但是,如果比较服务将支持某种可暂停算法,或者您想在两个目录的相等状态基于文件系统事件或类似事件发生变化时通知侦听器。然后目录是实例状态的一部分。

在这两种情况下,除了初始化实例之外,构造函数都不会做任何工作。在上述两种情况下,算法要么由客户端驱动,就像迭代器一样,要么由事件监听线程驱动。

我通常会尝试做这样的事情:如果可以将状态作为参数传递给服务方法,则不要在实例中保持状态。尝试设计具有不可变状态的对象。定义属性,例如在 equals 和 hashcode 中使用的属性应该始终是不可变的。

从概念上讲,构造函数是将对象表示映射到它所表示的对象的函数。

根据上面的定义,Integer.valueOf(1) 实际上比 new Integer(1) 更像是一个构造函数,因为 Integer.valueOf(1) == Integer.valueOf(1)。, 在任何一种情况下,这个概念也意味着所有的 cosntructor 参数,只有构造函数参数,应该定义对象的 equals 行为。

于 2008-11-06T21:02:59.830 回答
0

我肯定会做第二个。

如果构造函数中的长操作实际上是在构建对象,那么它是可用的。

现在我看到人们在构造函数中做的一件事是调用虚方法。这很糟糕,因为一旦有人将您用作基类并覆盖其中一个函数,一旦您进入构造函数,您将调用基类的版本而不是派生类。

于 2008-11-07T14:00:55.363 回答
0

我认为谈论“冗长”之类的抽象术语与是否将某些内容放入构造函数中的决定没有任何关系。

构造函数是应该用来初始化一个对象的东西,一个方法应该用来“做某事”,即有一个函数。

于 2008-11-07T14:19:04.937 回答