1

我正在讨论正确的 OO 设计,以使用来自 java 类的另一个对象的功能(方法),同时尽可能地保持两个对象的解耦。

例如,在我的类中的某个时刻,为了实现我的逻辑,我需要调用一个属于另一个对象的方法,比如一个辅助类。这个帮助类不需要与我的原始类有任何关系,它只是有一个特定的方法,它对我的​​类可见并且可供我的类使用。

实现逻辑后,就不再需要辅助方法(或辅助对象)了。

显然,为了使用它的方法,我需要一个对这个帮助对象的引用。但是为了强制封装,我不应该在我的原始类中声明一个实例变量来引用这个助手对象吗?这个推理正确吗?此外,助手类不知道任何可能使用它的客户端类。

在这种情况下,局部变量会更合适吗?在将使用其功能的方法中声明并实例化辅助对象?在我的原始类中声明和实例化这样一个辅助对象的最佳位置在哪里?

我想知道是否有一个高级示例,或者是否在 OO 文章中对此进行了更详细的解释。我将不胜感激以上任何以封装为重点的输入或提示。

4

6 回答 6

2

但是为了强制封装,我不应该在我的原始类中声明一个实例变量来引用这个助手对象吗?这个推理正确吗?

不,声明实例变量与破坏封装无关。

相关的考虑是:

  • dependency:默认情况下,您依赖于您使用的实用程序类,它不依赖于您。如果需要,可以使用各种技术(例如接口、策略模式、依赖注入)来逆转或减少这种依赖。但在简单的情况下,取决于它可能是可以的。
  • 对象生命周期:如果它是一个对象,你需要它在你使用它的时候就存在。它的存在可能在语义上意味着某些东西(即改变程序其他部分的行为),或者可能对性能产生影响(创建成本很高,或者如果在不需要的时候闲置会占用大量内存)。因此,您需要一种与它的性质和您的目标兼容的方式来处理它的生命周期。

基本选择是:

  • 一个或多个函数中的本地非共享变量- 它在需要时创建,一旦函数退出就会消失。可能是默认选择,其他一切都是优化或特殊情况。
  • 在构造函数中创建的共享实例变量- 仅创建一次,但会持续到您的对象本身被垃圾收集/销毁。
  • 第一次使用创建的共享实例变量- 如上所述,但以复杂性为代价延迟创建。
  • 外部静态函数- 没有对象,所以没有生命周期问题。适用于没有内部状态和简单接口的东西,否则您最终会获得仅由函数注释管理的隐式对象生命周期(如在strcpy等 C 库函数中)。

高级选择:

  • 外部单例- 对象管理它自己的生命周期,保证您可以使用一个。在某些情况下工作正常,但很可能过度使用。
  • 依赖注入- 其他人(通常是由配置文件管理的框架)破坏您的封装并放入您需要的对象。

执行此操作的所有其他方法(例如,将对象添加到构造函数或方法参数)会向系统添加额外的依赖项,因此除非至少上述基本选择不合适,否则不应这样做。

于 2009-08-29T12:04:31.563 回答
1

正确答案取决于辅助类 (H) 和您在其上使用的方法 (M) 以及原始对象类 (C) 之间关系的性质。你提到了几个关键点:

  • 您不想将所需的逻辑放入C.
  • 您已将其放入H
  • H.M()只使用一次C
  • H与客户端无关。
  • 因为你说“很明显,我需要一个对这个帮助对象的引用才能使用它的方法”,我假设你只能使用的实例,HM()是一个实例方法。

有几个解决方案:

  • 评估M作为静态方法是否会更好。如果我见过一个静态方法,这是一个非常引人注目的用途。你没有提到任何关于 H 保持状态的事情。

  • 使用策略模式。如果H.M()表示做某事的特定方式,那么H就是模式中的 Strategy 对象C。如果还有其他H类似M()方法的类,这些是您可以选择的不同策略。

于 2009-08-29T11:12:44.770 回答
0

静态方法很难测试,或者更确切地说,在另一种方法中调用的静态方法很难模拟。我发现从测试的角度思考很容易做出好的设计。如果该方法是非静态成员,您可以轻松地对调用该方法的代码进行单元测试,而无需同时测试该方法。你和我在一起吗?假设一个方法 m 使用网络来做一些事情。如果该方法是静态的,那么每次您测试使用方法 m 的代码时,它都会在网络上做一些事情。如果网络失败,你的测试会失败,但你想要测试的代码不会。你看?如果它不是静态的,您可以使用该方法模拟对象,并使其始终返回“OK”。

也就是说,我会将帮助器作为参数发送给方法,或者更确切地说是帮助器接口。这样你的班级就完全不知道如何创建一个助手,甚至是什么类型。无知是福。班级只会知道如何使用它,漂亮的东西。:)

于 2009-08-29T11:44:46.797 回答
0

那么这是场景吗?这些是问题吗?

Class MyClass {
   private SomeState myState;

   public voic oneMethod() {
         // Q1 : what type is **ahelper** ?
         // Q2 : where do we declare it ?
         // Q3 : where do we initialise it?
         aHelper.doSomeWork();

         // update your state depending upon the results
   }
}

Q1。我认为您应该将 aHelper 声明为接口

 HelperInterface aHelper;

然后我们不耦合到任何具体的实现。

Q2。如果您只在一个地方使用它,请在该函数中声明它,否则作为成员变量。你也不会通过这样做来增加耦合。

 HelperInterface aHelper = ? what here?
 aHelper.soSomeWork();

Q3。在构造函数中或使用工厂的惰性 getter 进行初始化。

public MyClass(HelperInterface injectedHelper) {
    aHelper = injectedHelper;
}

这可以使测试变得非常容易,您的测试可以注入一个 Mocked Helper 类。

或者您可以使用惰性初始化程序。如果您的助手是方法中的局部变量,这将非常方便。同样,工厂可以根据您的喜好注入或保持静态。

private getHelper() {
    if (aHelper == null ){ make the helper, perhaps using a factory }

    return aHelper     
}
于 2009-08-29T21:22:50.603 回答
0

我正在讨论正确的 OO 设计,以使用来自 java 类的另一个对象的功能(方法),同时尽可能地保持两个对象的解耦。

你在这方面付出了太多的努力。解耦并不意味着完全没有联系。如果您打算使用一次辅助对象,只需将其作为参数传递给使用它的代码块,或者让您的类从工厂获取它的实例。

但在某些时候,您必须参考它。显然,您不希望有成员实例引用它(潜在的内存泄漏)。所以这会调用某种工厂或实例管理器,让您获得对助手的引用。

但不要过火。目标是找到解决方案,而不是玩解耦游戏,让事物适应人为的、冗余的类层次结构。后者是 OO 概念最糟糕的用法。

于 2010-06-30T02:55:07.637 回答
-1

为什么不使用静态方法,或者使用单例辅助对象?

于 2009-08-29T11:07:41.253 回答