在构造函数中进行冗长的操作会被认为是不好的设计吗?

时间:2008-11-06 19:54:05

标签: c# constructor class-design

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

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

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

DirectoryComparer c = DirectoryComparer.Compare("C:\\Dir1", "C:\\Dir2");
你怎么看?你期望构造函数“快速”吗?第二个例子是更好的还是只是使类的使用复杂化?

BTW:

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

修改

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

14 个答案:

答案 0 :(得分:12)

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

DirectoryComparer c = new DirectoryComparer();

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

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

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

  1. 构造函数使用(可选)“配置”参数
  2. 初始化实例
  3. 比较方法采用两个“数据”参数,比较它们并返回“结果”
  4. 现在,结果可以是int,bool,diff的集合。无论什么适合需要。

答案 1 :(得分:10)

我更喜欢第二个。

我希望构造函数实例化该类。 方法比较实现了它的设计目的。

答案 2 :(得分:5)

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

在代码中,您的电话将是:

D1 = new Directory("C:\");
..
D1.compare(D2);

答案 3 :(得分:3)

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

答案 4 :(得分:3)

我同意不在构造函数内部进行冗长操作的一般情绪。

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

然后,如果要定义比较目录的不同方法(例如忽略时间戳,只读属性,空目录等),可以将这些参数传递给DirectoryComparer类构造函数。或者,如果您始终希望DirectoryComparer具有完全相同的比较目录规则,则可以简单地将DirectoryComparer设为静态类。

例如:

DirectoryComparer comparer = new DirectoryComparer(
    DirectoryComparerOptions.IgnoreDirectoryAttributes
);
DirectoryComparerResult result = comparer.Compare("C:\\Dir1", "C:\\Dir2");

答案 5 :(得分:2)

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

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

答案 6 :(得分:1)

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

答案 7 :(得分:1)

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

  • 再次比较 - 如果目录发生了变化?
  • 通过更新成员来更改您要比较的文件。

此外,您可能需要在实施中考虑在目标目录中更改文件时从操作系统接收消息,并可选择重新进行重新比较。

关键在于 - 假设此类仅用于对这些文件的单个实例进行一次比较,从而强加限制。

因此,我更喜欢:

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

DirectoryComparer->Compare();

或者

DirectoryComparer = new DirectoryComparer();

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

答案 8 :(得分:1)

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

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

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

答案 9 :(得分:0)

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

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

DirectoryCompare c = dir1.CompareTo(dir2);

这将是更清晰的实施。 有关扩展方法的更多信息here

答案 10 :(得分:0)

如果某个操作可能需要一段时间,那么这是一个您可能想要导出到另一个线程的操作(因此您的主线程不会阻塞并可以执行其他操作,例如显示旋转进度指示器)。其他应用可能不想这样做,他们可能想要一个线程内的所有内容(例如那些没有UI的内容)。将对象创建移动到单独的线程有点尴尬恕我直言。我更喜欢在我当前的线程中创建对象(快速),然后让它的方法在另一个线程中运行,一旦方法完成运行,另一个线程就可以死了,我可以在我的方法中获取此方法的结果当前线程通过在转储对象之前使用该对象的另一个方法,因为一旦我知道结果我就很高兴(或者如果结果涉及更多细节则保留副本,我可能必须一次消耗一个)。

答案 11 :(得分:0)

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

但是,如果比较服务将支持某种可挂起的算法,或者您希望在两个目录的相等状态根据文件系统事件或类似事件发生更改时通知侦听器。然后,其他目录是实例状态的一部分。

在任何情况下,构造函数都不会执行除初始化实例之外的任何工作。如果算法上面的两个是由客户端驱动的,就像Iterator一样,或者它是由事件监听线程驱动的。

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

Conceptualy构造函数是将对象表示映射到它所代表的对象的函数。

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

答案 12 :(得分:0)

我肯定会做第二次。

如果构造函数中的长动作实际构建对象以使其可用,则它们很好。

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

答案 13 :(得分:0)

我不认为谈论像“冗长”这样的抽象术语与决定有什么关系,如果你在构造函数中放入某些东西。

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