对于仅运行自包含计算的类,哪种设计更好?
我目前正在研究一个可计算两个对象之间差异的类。我正在尝试确定该课程的最佳设计。我看到两个选择:
1)一次性类实例。在构造函数中获取要比较的对象,并为此计算差异。
public class MyObjDiffer { public MyObjDiffer(MyObj o1, MyObj o2) { // Calculate diff here and store results in member variables } public boolean areObjectsDifferent() { // ... } public Vector getOnlyInObj1() { // ... } public Vector getOnlyInObj2() { // ... } // ... }
2)可重用的类实例。构造函数不接受任何参数。有一个" calculateDiff()"方法,该方法将对象进行比较,并返回结果。
public class MyObjDiffer { public MyObjDiffer() { } public DiffResults getResults(MyObj o1, MyObj o2) { // calculate and return the results. Nothing is stored in this class's members. } } public class DiffResults { public boolean areObjectsDifferent() { // ... } public Vector getOnlyInObj1() { // ... } public Vector getOnlyInObj2() { // ... } }
区别将相当复杂(细节与问题无关紧要),因此将需要一些辅助功能。如果采用解决方案1,则可以将数据存储在成员变量中,而不必传递所有内容。由于所有内容都在一个类中处理,因此它稍微紧凑一些。
但是,从概念上讲,"差异"特定于特定的一组结果似乎很奇怪。选项2从实际计算结果的逻辑中分离结果。
编辑:选项2还提供了使" MyObjDiffer"类静态的功能。谢谢kitsune,我忘了提了。
我无法看到任何一个明显的优点或者缺点。我认为这种事情(一个只处理一次计算的类)必须经常出现,也许我错过了一些东西。因此,我认为我会将问题提出给云。这里的一个或者另一个选项是否有明显的利弊?是天生更好的吗?有关系吗?
我在Java中进行此操作,因此可能存在一些限制,但是设计的总体问题可能与语言无关。
解决方案
我将使用numero 2并思考是否应该将此保持静态。
这取决于我们将如何使用差异。在我看来,将差异作为逻辑实体是有意义的,因为它需要支持某些操作,例如" getDiffString()"," numHunks()"或者" apply()"。我可能会选择第一个,并像这样做得更多:
public class Diff { public Diff(String path1, String path2) { // get diff if (same) throw new EmptyDiffException(); } public String getDiffString() { } public int numHunks() { } public bool apply(String path1) { // try to apply diff as patch to file at path1. Return // whether the patch applied successfully or not. } public bool merge(Diff diff) { // similar to apply(), but do merge yourself with another diff } }
像这样使用diff对象也可能使自己适应诸如保存一堆补丁程序或者序列化到压缩归档文件(可能是"撤消"队列)之类的事情。
为什么要编写一个仅用于计算两个对象之间的差异的类?这听起来像是该类的静态函数或者成员函数的任务。
我会去找一个静态的构造方法,就像。
Diffs diffs = Diffs.calculateDifferences(foo, bar);
这样,在计算差值时很明显,并且没有任何方法可以滥用对象的界面。
我喜欢显式启动工作而不是在实例化时进行工作的想法。另外,我认为结果足以保证自己的水平。第一个设计对我而言并不干净。使用此类的人必须了解执行计算之后,其他一些类成员现在正在保存结果。选项2更清楚地说明正在发生的事情。
使用面向对象的编程
使用选项2,但不要使其变为静态。
策略模式
这样,实例" MyObjDiffer"可以传递给需要策略来计算对象之间差异的任何人。
如果一路走来,我们发现在不同的上下文中使用不同的规则进行计算,则可以创建一个适合的新策略。使用代码,我们将扩展MyObjDiffer并覆盖其方法,这当然是可行的。更好的方法是定义一个接口,并将MyObjDiffer作为一种实现。
如果我们想延迟决策,则任何体面的重构工具都将能够从MyObjDiffer中"提取接口",并在以后使用该接口类型替换对该类型的引用。将"选项2"与实例方法(而不是类过程)一起使用,可以为我们带来灵活性。
配置实例
即使我们永远不需要编写新的比较方法,也可能会发现指定选项以定制基本方法的行为很有用。如果考虑使用" diff"命令比较文本文件,我们会记得有多少个不同的选项:空格和区分大小写,输出选项等。OO编程中与此类似的最佳模拟是考虑每个差异过程作为对象,并将选项设置为该对象的属性。
我不能真正地说出为什么它是"最佳"方法的坚定原因,但是我通常会为可以与其进行"对话"的对象编写类。因此,就像"单次使用"选项1一样,不同之处在于通过调用设置器,我们可以将其"重置"以用于其他用途。
而不是提供实现(这是显而易见的),这里是一个示例调用:
MyComparer cmp = new MyComparer(obj1, obj2); boolean match = cmp.isMatch(); cmp.setSubjects(obj3,obj4); List uniques1 = cmp.getOnlyIn(MyComparer.FIRST); cmd.setSubject(MyComparer.SECOND,obj5); List uniques = cmp.getOnlyIn(MyComparer.SECOND);
... 等等。
这样,调用者可以决定是要实例化大量对象还是继续重用该对象。
如果对象需要大量设置,则特别有用。可以说比较器带有选项。可能有很多。设置一次,然后多次使用。
// set up cmp with options and the master object MyComparer cmp = new MyComparer(); cmp.setIgnoreCase(true); cmp.setIgnoreTrailingWhitespace(false); cmp.setSubject(MyComparer.FIRST,canonicalSubject); // find items that are in the testSubjects objects, // but not in the master. List extraItems = new ArrayList(); for (Iterator it=testSubjects.iterator(); it.hasNext(); ) { cmp.setSubject(MyComparer.SECOND,it.next()); extraItems.append(cmp.getOnlyIn(MyComparer.SECOND); }
编辑:顺便说一句,我称它为MyComparer而不是MyDiffer,因为拥有isMatch()方法似乎比isDifferent()方法更为自然。
出于多种原因,我们需要解决方案2. 而且我们不希望它是静态的。
虽然静态似乎很有趣,但是当我们提出(a)具有相同要求的新算法或者(b)新要求时,这是维护的噩梦。
一流的对象(没有太多内部状态)使我们可以演变成不同的类层次结构-一些较慢,一些更快,一些具有更多的内存,一些具有较少的内存,一些用于旧需求,一些用于新需求。
某些差异可能会伴随着复杂的内部状态或者内存,或者增量差异或者基于哈希码的差异。可能存在各种各样的可能性。
可重用的对象使我们可以在启动应用程序时使用属性文件来选择不同的对象。
从长远来看,我们希望最大程度地减少分散在整个应用程序中的新操作的数量。我们希望将新操作集中在可以找到和控制它们的地方。要将旧的不同算法更改为新的不同算法,我们需要执行以下操作。
- 编写新的子类。
- 更新属性文件以开始使用新的子类。
并且要完全确信没有隐藏的d = new MyObjDiffer(x,y)
,它们是我们不知道的。
我们想在任何地方使用d = theDiffer.getResults(x,y)
。
Java库所做的是它们具有一个静态的DifferFactory。该因子检查属性并发出正确的Differ。
DifferFactory df= new DifferFactory(); MyObjDiffer mod= df.getDiffer(); mod.getResults( x, y );
工厂通常会缓存单个副本-不必每次调用getDiffer
时都要物理读取属性。
这种设计为我们将来提供了最大的灵活性。它看起来像Java库的其他部分。