【问题标题】:using another object's functionality following a proper OO design - encapsulation在正确的 OO 设计之后使用另一个对象的功能 - 封装
【发布时间】:2009-08-29 11:03:22
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

  • 如果您的示例更具体,将会有所帮助。使用一些伪代码让我们更好地了解您的意图。
  • 我会尝试给出一个伪代码示例

标签: java oop encapsulation


【解决方案1】:

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

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

相关注意事项有:

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

基本选择是:

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

高级选择:

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

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

【讨论】:

  • 感谢您提供完整的选择列表及其解释!
【解决方案2】:

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

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

有几个解决方案:

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

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

【讨论】:

  • 帮助类不需要维护状态。它可以被其他业务逻辑类使用,但它会帮助改变使用它的客户端的内部状态。我希望这能让我的问题更清楚一些。我明白您使用策略模式的意思。我会调查的。目标是还要避免在 H 和 C 之间有一个循环引用。我的意思是,让 H 引用 C 和 C 引用 H 没有意义吗?
  • 只是一个快速的附加问题,策略模式将要求 C 将 H 作为实例元素,对吗? H当然也可以是一个接口。提前感谢您的澄清。
【解决方案3】:

这就是场景吗?这些是问题吗?

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
   }
}

第一季度。我认为您应该将 aHelper 声明为接口

 HelperInterface aHelper;

那么我们不耦合到任何具体的实现。

第二季度。如果您只在一个地方使用它,请在该函数中声明它,否则作为成员变量。两者都不会增加耦合度。

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

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

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

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

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

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

    return aHelper     
}

【讨论】:

  • 是的,确切地说,这几乎是伪代码场景,还有我所有的问题。在构造函数中初始化 aHelper 有什么特别的好处吗?即使我将使用 aHelper.doSomeWork();在我的业务逻辑方法中本地?我的印象是构造函数不应该做很多工作......虽然不确定这何时适用。可能是完全不同的对话。但是如果我只在本地使用 aHelper,我想我可以声明, 和 在本地初始化?再次感谢。
  • 我不会在逻辑方法中执行“new RealHelper()”,因为我们不想耦合到 HelperInterface 的特定实现。简单地接受注入的帮助器并将引用保存在构造函数中所做的工作很少。像这样的构造函数注入是一种常见的技术,我认为这很合理。完全本地化对业务方法的引用确实会导致该方法需要知道如何“制作”辅助类实例,这往往会增加耦合。
【解决方案4】:

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

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

【讨论】:

    【解决方案5】:

    我正在讨论正确的面向对象设计 使用另一个对象的功能 (方法)来自 java 类,而 两个对象都保持解耦 尽可能。

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

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

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

    【讨论】:

      【解决方案6】:

      您为什么不使用static method,或者使用singleton 辅助对象?

      【讨论】:

      • 你的意思是在我的业务逻辑类中创建一个静态方法?
      • 是的。这完全取决于业务逻辑类是否需要维护状态。如果没有,请将方法设为静态。如果只有全局状态,请选择单例。如果需要为每个单独的用户存储状态,则需要在用户中维护引用。
      • 是的,业务逻辑类将使用辅助对象中的方法进行一些计算,并将更改其内部状态。这可能意味着逻辑类在使用这些辅助方法后会更改其实例变量之一。
      猜你喜欢
      • 1970-01-01
      • 2010-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-21
      • 1970-01-01
      • 1970-01-01
      • 2019-05-02
      相关资源
      最近更新 更多