【问题标题】:Java Object creation pattern and designJava 对象创建模式和设计
【发布时间】:2015-06-18 16:04:56
【问题描述】:

我最近开始从事一个 Java 项目,该项目已经由一个团队开发了超过 3 个月的大量代码库。我注意到在很多地方,他们直接在客户端对象的构造函数中实例化了一些对象,而不是使用依赖注入。我想将对象构造重构为工厂并使用一些注入框架。

我创建了一个工厂,它本质上是一个做new <type(some params here)> 的班轮。这里没有什么花哨的——没有单例,没有静态工厂模式。只是一个 newInstance() 方法,它返回一个新的依赖实例。

在代码中显示一些东西:

class A {   A() {  
       B bobj = new B();  // A and B are coupled directly
    } 
}

我想将其重构为:

BFactory {
    newInstance() {  return new B(); // return B implementation  }
}

 class A {
   A(BFactory factory){  
     B bobj = factory.newInstance(); // A does not know about B impl
  }
}

我的论点是,不应在代码中的任何地方创建对象,除非在为此目的的工厂中创建对象。这促进了松散耦合,否则您将两种类型紧密耦合。一位资深成员(我正在尝试重构的代码的作者)认为单班轮工厂的设计过于复杂。

是否有关于管理这个问题的模式的权威建议/参考?可以用来决定哪种方法更好以及为什么?

【问题讨论】:

  • 我的论点是“如果它没有坏就不要修复它。”使用工厂方法获得的耦合丢失不值得花费时间或风险(错误)来触及代码库的每个部分。现在就开始吧。
  • 您的帖子能否通过一个简短的示例来说明您正在谈论的内容?您的描述可以有几种不同的解释方式。
  • 我会将@markspace 的评论稍微更改为“等到它坏了再修复它。”。如果并且当您需要使用工厂方法进行更简单和更清洁的更改时,请重构该类的构造。如果某项工作正常且不需要更改,请不要理会它。
  • 我认为这个问题不应该因为主要基于意见而被关闭。 OP询问他的做事方式是否正确。我不知道答案是取决于你的情况
  • 仅在需要时使用松散耦合,如果工厂可以创建该接口/类的多个实现,那么您应该使用工厂来创建它们,如果它是一个对象,则不要创造生命通过向其中添加工厂变得复杂。

标签: java object factory


【解决方案1】:

一位资深成员(我正在尝试重构的代码的作者)认为单班轮工厂的设计过于复杂。

这看起来像是您问题的症结所在,而不是您是否应该重构代码,所以让我们回答它而不是偏离实际问题。如果我们考虑您在代码中提供的示例,我同意您的同事的观点。您不应该为要注入的每个依赖项创建工厂类。你想要达到的目标并没有错,但你试图实现它的方式却是矫枉过正。

您要么依赖于知道如何创建每个依赖项的 Factory 类的层次结构,要么依赖于实际类本身并拥有一个 Container 可以为您将对象连接在一起。

选项 1:依赖普通工厂

class A {
   B bobj;
   C cobj;
   A(Factory factory){  
     bobj = factory.createB(); 
     cobj = factory.createC();
   }
}

选项2:直接依赖依赖

class A {
   B bobj;
   C cobj;
   A(A a,B b) {  
      this.bobj = b;
      this.cobj = c
   }
}

然后您可以创建一个知道如何将对象连接在一起的Container 类:

class Container {
    public static B createB() {
        return new BImpl();
    }

    public static C createC() {
         return new CImpl();
    }

    public static A createA() {
        return newAImpl(createB(),createC());
    }
}

上面的例子太基础了。在现实世界中,您通常会有一个更复杂的依赖关系图。这就是 DI 框架派上用场的地方,而不是重新发明轮子。如果您的最终目标是开始使用 DI 框架,则可以选择选项 2,因为 DI 框架通过向其客户端提供依赖项而不是请求它们的客户端代码来实现控制反转。

【讨论】:

    【解决方案2】:

    您的基本观点是完全有效的,直接在构造函数中实例化对象通常不是一个好主意(它们可能是该规则的有效例外)。

    如果你这样做:

    class Car {
    
       private Engine engine;
    
       public Car() {
           engine = new Engine();
       }
    
    }
    

    如果没有发动机,您将很难测试汽车。您必须使用反射才能通过模拟交换实例。

    如果您改为执行以下操作

    class Car {
    
       private Engine engine;
    
       @Inject
       public Car(Engine engine) {
           this.engine = engine;
       }
    
    }
    

    很容易用假引擎测试汽车,替换引擎的实现或改变引擎的构造方式(例如向构造函数添加更多参数)。

    但是您绝对应该使用已建立的依赖注入框架,而不是编写自己的工厂。如果你按照 Chetan 的建议传入一个“通用工厂”,你最终会隐藏你的依赖项。

    可以在这里找到更多使用依赖注入的好资源: https://github.com/google/guice/wiki/Motivationhttps://youtu.be/acjvKJiOvXw(非常好的演讲,值得你花时间)。

    【讨论】:

    • 为什么不在两个类中同时使用两种类型的构造函数,一种是提供引擎的默认构造函数,另一种是允许您注入引擎的构造函数。
    • @AlexC 当您的一位客户的DefaultEngine 不再是以前的DefaultEngine 时会发生什么? Factory 理想情况下应该注入一个引擎,如果客户端不知道他们想要什么引擎,该引擎应该充当默认引擎。对于一个客户端,Engine1 可能是默认引擎,而对于另一个客户端,它将是 Engine2Car 真的应该关心吗?答案是否定的,因为违背了控制反转的目的。
    • 很多假设。您编写了一个用例,现在您正在编写约束并回答您自己的问题。如果包含默认值的 IoC 配置不再像以前那样怎么办?代码可能会过时,配置文件也会过时。再次阅读我的答案,您可以同时拥有默认行为和 IoC,您的观点到底是什么?
    • @AlexC 我不认为你明白我的意思。让我重述一下。默认引擎与任何其他Engine 有何不同。为什么Car 应该知道它的默认引擎是什么?一辆车不应该关心它是什么引擎,只要它有一个引擎。不同的客户可能希望将不同的引擎视为默认引擎?您是否使用默认引擎为每个客户端创建一个新的 Car 类?不。您创建一个 Car 类并让客户决定他们希望默认引擎是什么。如果一辆车开始决定它的默认引擎是什么,它就违反了很多规则。
    猜你喜欢
    • 2012-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-30
    • 1970-01-01
    • 1970-01-01
    • 2023-04-08
    相关资源
    最近更新 更多