1

我正在阅读 Joshua Bloch 的“ Effective Java Programming Language Guide ”。
他解释说可以使用静态工厂方法来避免不必要的重复对象
这一点我还不是很明白。
谁能解释一下?

4

7 回答 7

7

一个真实的例子:

Java 支持原始类型和对象类型来表示一个字节。当您将原语转换为对象时,您可以执行以下操作:

Byte b = new Byte( (byte) 65);

但这会为每次调用创建一个新实例。相反,你这样做:

Byte b = Byte.valueOf( (byte) 65);

在每次调用时,方法 valueOf() 将返回表示字节值 65 的 Byte 对象的相同实例。

在 10000 次调用之后,第一个示例将创建 10000 个对象,而第二个示例只有一个,因为 Byte 类有一个 Byte 对象的内部缓存,表示 -128 和 127 之间的所有数字。

于 2009-09-13T04:09:32.540 回答
6

所有关于不重复的答案似乎都集中在单例模式上,这是不重复的一个很好的例子,但在一般情况下使用的模式不好。在我看来,一个给定的应用程序应该有零到一个单例,偏好为零。但是,这与不创建不必要的对象无关。

请考虑一个必须生成大量 Date 对象的应用程序。它生成了如此多的 Date 对象,以至于 Date 对象的构造对性能产生了不利影响。因此,代替调用 Date 对象的构造函数,代码被重构为仅通过工厂方法创建 Dates。在这个工厂方法中,会检查 Map 以查看请求的日期是否已经创建。如果是,则从 Map 返回相同的对象。否则创建一个新的,放入 Map 并返回。

让您感到困惑的是如何通过调用工厂方法来防止创建重复对象。仅仅通过调用工厂方法并没有真正改变任何东西。调用工厂允许代码接管并决定创建对象。当调用 new 时,不能做出这样的决定。

另请参阅此问题,以更深入地了解该模式及其可用于什么。

于 2009-09-13T03:59:57.363 回答
5

当您调用构造函数时,它总是会返回一个新对象(除非抛出异常)。静态工厂方法,或任何类型的工厂,不必总是返回一个新对象。例如,getInstance()传统 Singleton 设计模式中的方法是始终返回完全相同的对象的工厂方法。在某些情况下,有时您想做这种事情,无论是强制对象只能实例化一次,还是创建某种对象池等。总的来说,我认为这是使用的边缘原因静态工厂方法。主要目的是创建命名良好的伪构造函数。

这是一个使用静态工厂方法制作命名良好的伪构造函数的(有点愚蠢的)示例。考虑这个类:

class Person {

   public Person(Role role) {
      setRole(role);
   }

   ...
}

如果没有静态工厂方法,您可能会执行以下操作:

Person employee = new Person(Role.EMPLOYEE);
Person manager = new Person(Role.MANAGER);

相反,您可以创建静态工厂方法:

class Person {

   public static Person newEmployee() {
      return new Person(Role.EMPLOYEE);
   }

   public static Person newManager() {
      return new Person(Role.MANAGER);
   }

   private Person(Role role) {
      setRole(role);
   }

   ...
}

你可能会做这样的事情:

Person employee = Person.newEmployee();
Person manager = Person.newManager();

这可能不是一个很好的例子,但请考虑一个更复杂的构造函数或具有较少描述性参数的构造函数。有时走工厂方法路线会使代码更清晰。当然也有缺点...

至于限制对象的创建,考虑一些奇怪的约束,比如 CEO 永远不会超过一个:

class Person {

   private static Person singletonCEO = new Person(Role.CEO);

   public static Person newCEO() {
      return singletonCEO;
   }

   ...
}

以及如何创建:

Person ceo1 = Person.newCEO();
Person ceo2 = Person.newCEO();

assertThat(ceo1, is(ceo2)); // JUnit 4.x

我希望这些例子有所帮助。

于 2009-09-13T03:28:36.847 回答
2

当不需要创建对象的新实例来执行某些操作时,工厂方法模式可能很有用。

以下是我能想到的几种一般情况,其中返回相同对象的静态工厂方法可以派上用场:

  1. 创建对象的成本很高——实例化对象时需要进行大量处理,因此不希望多次实例化对象。(这也与单例模式有关。)

  2. 对象不保持状态——如果实例之间没有状态差异,那么每次都创建一个新对象没有什么好的目的。

关于工厂方法模式的Wikipedia 页面提供了有关此主题的更多信息。


让我们看一个具体的例子。

该类DateFormat使用getInstance静态方法返回一个DateFormat实例,该实例可用于Date根据机器的语言环境将 a 格式化为预设格式。

由于DateFormat返回的每个日期格式化操作都使用相同的格式,因此没有真正的理由DateFormat每次都创建一个新实例。

一般来说,实现的方式是在实例不存在的情况下创建一个实例,然后保留对该实例的引用。如果再次需要该实例,则返回该引用。(这通常也是单例模式的实现方式。)

例如:

class MySingleInstanceObject {

  private MySingleInstanceObject instance;

  private MySingleInstanceObject() {
    // Initialize the object.
    // This may be expensive.
  }

  public MySingleInstanceObject getInstance() {
    if (instance == null) {
      instance = new MySingleInstanceObject();
    }

    return instance;
  }
}

(仅供参考,上面的代码是单例的示例。此外,它不是线程安全的。)

于 2009-09-13T03:29:08.160 回答
2

如果我没记错的话,他还在书中举了一个例子。考虑Decimal。零经常被使用。因此,如果您要调用静态工厂方法Decimal.valueOf("0")(不知道这是否是实际的 API,但这对于本示例而言无关紧要),它将返回一个 Decimal 表示 0 的实例,它将是任何呼叫的相同实例。实现将是这样的:

public class Decimal {
    private static Decimal zero = new Decimal(0);

    public static Decimal valueOf(String s) {
        if (s.equals("0")) {
            return zero;
        } else {
            return new Decimal(parse(s)); // or whatever
        }

    // rest of the class
}

请注意,只有一个零实例,而对于任何其他数字,都会创建一个新对象。此外,这适用于工厂方法,而您不能使用构造函数来做到这一点。这就是布洛赫试图指出的,作为前者的优势。

而且,正如 Yishai 所提到的,它与 Singleton 并没有那么紧密的关系。如您所见,您周围可以有很多 Decimal 对象。相反,您可以使用工厂方法来完全控制您创建的实例数量。这就是为什么它被称为工厂。

于 2009-09-13T04:35:50.847 回答
0

我能够在这里阅读这本书的一些内容。在阅读了他所写的内容之后,似乎他所说的是静态工厂方法为您作为开发人员提供了更大的灵活性,并且它还允许您更明确地返回返回的内容。当您将此与构造函数进行对比时,构造函数可能无法清楚地说明返回的内容。此外,您可以在我认为很吸引人的静态工厂方法中执行缓存等操作。如果您需要这种级别的控制和灵活性,这种方法似乎是一种很好的方法。

如果您想使用缓存,则不要创建不必要的重复对象。使用这种静态工厂方法,您可以在每次调用静态工厂方法时返回相同的对象。

一个例子:

public class Person
{

   private Person(string firstName, string lastName)
   {
      this.FirstName = firstName;
      this.LastName = lastName;
   }

   public string FirstName {get; private set;}
   public string LastName {get; private set;}

   private static Dictionary<string, Person> objectPool = new Dictionary<string, Person>();
   private object lockObject = new object();

   public static Person CreatePerson(string firstName, string lastName)
   {
      var result = objectPool[firstName + lastName];
      Person person = null; 
      if (result != null)
      {
         return result
      }
      lock(lockObject)
      {
          person = new Person(firstName, lastName);
          objectPool.Add(firstName + lastName, person)
      }
      return person;
   }
}
于 2009-09-13T03:36:37.100 回答
-1

如果你有一个工厂类来创建对象实例化,那么每次你去创建一个对象时,你也必须实例化工厂类。基本上你会创建这个工厂类的副本。

如果它是静态的,则您只有一个要使用的工厂实例。

于 2009-09-13T03:29:11.527 回答