静态工厂方法以避免重复对象

时间:2009-09-13 03:22:12

标签: java methods factory

我正在读约书亚布洛赫的“Effective Java Programming Language Guide” 他解释说静态工厂方法可用于避免不必要的重复对象 我对此并不十分了解 谁能解释一下?

7 个答案:

答案 0 :(得分:7)

一个现实世界的例子:

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

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

这会为每次通话创建一个新实例。相反,你做:

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

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

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

答案 1 :(得分:6)

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

相反,请考虑一个必须创建大量Date对象的应用程序。正在制作如此多的Date对象,Date对象的构造对性能产生了负面影响。因此,在调用Date对象的构造函数时,代码将被重构为仅通过工厂方法创建日期。在此工厂方法中,将检查Map以查看是否已创建所请求的日期。如果是,则从Map返回相同的对象。否则创建一个新的,放入Map并返回。

让您感到困惑的是如何通过调用工厂方法来阻止创建重复对象。只是通过调用工厂方法并没有真正改变任何东西。调用工厂允许的是代码接管并做出关于创建对象的决定。在打电话给新人时,不能做出这样的决定。

另请参阅this question以了解有关模式及其可用性的更多信息。

答案 2 :(得分:5)

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

这是一个使用静态工厂方法制作名称很好的伪构造函数的(有些愚蠢)示例。考虑这个课程:

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

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

答案 3 :(得分:2)

factory method pattern对于无需创建对象的新实例以执行某些操作的时间非常有用。

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

  1. 创建对象的成本很高 - 实例化对象时会有很多处理,因此不希望多次实例化对象。 (这也与Singleton pattern有关。)

  2. 对象不保持状态 - 如果实例之间没有状态差异,则每次创建新对象都没有好处。

  3. Wikipedia page on the factory method pattern有关于此主题的更多信息。


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

    DateFormat类使用getInstance静态方法返回DateFormat实例,该实例可用于将Date格式化为预设格式,具体取决于机器。

    由于返回的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;
      }
    }
    

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

答案 4 :(得分: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对象。相反,您可以使用工厂方法完全控制您创建的实例数。这就是为什么它被称为工厂。

答案 5 :(得分:0)

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

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

一个例子:

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

答案 6 :(得分:-1)

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

如果是静态的,则只能使用工厂的一个实例。