重构函数-Replace Constructor with Factory Method以工厂函数取代构造函数十二

重构函数-Replace Constructor with Factory Method以工厂函数取代构造函数十二

1.以工厂函数取代构造函数

1.1.使用场景

你希望在创建对象时不仅仅是做简单的建构动作。将构造函数替换为工厂函数。

使用Replace Constructor with Factory Method (304)的最显而易见的动机,就是在派生子类的过程中以工厂函数取代类型码。你可能常常需要根据类型码创建相应的对象,现在,创建名单中还得加上子类,那些子类也是根据类型码来创建。然而由于构造函数只能返回单一类型的对象,因此你需要将构造函数替换为工厂函数[Gang of Four]。

此外,如果构造函数的功能不能满足你的需要,也可以使用工厂函数来代替它。工厂函数也是Change Value to Reference (179)的基础。你也可以令你的工厂函数根据参数的个数和类型,选择不同的创建行为。

1.2.如何做

  • 新建一个工厂函数,让它调用现有的构造函数。
  • 将调用构造函数的代码改为调用工厂函数。
  • 每次替换后,编译并测试。
  • 将构造函数声明为private。
  • 编译。

1.3.示例

class Employee {
    
    
  private int _type;
  static final int ENGINEER = 0;
  static final int SALESMAN = 1;
  static final int MANAGER = 2;
  Employee (int type) {
    
    
      _type = type;
  }

我希望为Employee提供不同的子类,并分别给予它们相应的类型码。因此,我需要建立一个工厂函数

  static Employee create(int type) {
    
    
     return new Employee(type);
  }

然后,我要修改构造函数的所有调用点,让它们改用上述新建的工厂函数,并将构造函数声明为private

client code...
  Employee eng = Employee.create(Employee.ENGINEER);
class Employee...
  private Employee (int type) {
    
    
     _type = type;
  }

根据字符串创建子类对象

迄今为止,我还没有获得什么实质收获。目前的好处在于:我把“对象创建请求的接收者”和“被创建对象所属的类”分开了。如果我随后使用Replace Type Code with Subclasses (223)把类型码转换为Employee的子类,就可以运用工厂函数,将这些子类对用户隐藏起来

  static Employee create(int type) {
    
    
      switch (type) {
    
    
          case ENGINEER:
             return new Engineer();
          case SALESMAN:
             return new Salesman();
          case MANAGER:
             return new Manager();
          default:
             throw new IllegalArgumentException("Incorrect type code value");
      }
  }

可惜的是,这里面有一个switch语句。如果我添加一个新的子类,就必须记得更新这里的switch语句,而我又偏偏很健忘。
绕过这个switch语句的一个好办法是使用Class.forName()。
第一件要做的事是修改参数类型,这从根本上说是Rename Method (273)的一种变体。
首先我得建立一个函数,让它接收一个字符串参数:

  static Employee create (String name) {
    
    
      try {
    
    
          return (Employee) Class.forName(name).newInstance();
      } catch (Exception e) {
    
    
          throw new IllegalArgumentException ("Unable to instantiate" + name);
      }
  }

然后让稍早那个“create()函数int版”调用新建的“create()函数String版

class Employee {
    
    
  static Employee create(int type) {
    
    
      switch (type) {
    
    
          case ENGINEER:
             return create("Engineer");
          case SALESMAN:
             return create("Salesman");
          case MANAGER:
             return create("Manager");
          default:
             throw new IllegalArgumentException("Incorrect type code value");
      }
  }

然后,我得修改create() 函数的调用者,将下列这样的语句

Employee.create(ENGINEER)

修改为

Employee.create("Engineer")

完成之后,我就可以将“create()函数int版”移除了。
现在,当我需要添加新的Employee子类时,就不再需要更新create()函数了。但我却因此失去了编译期检验,使得一个小小的拼写错误就可能造成运行期错误。如果有必要防止运行期错误,我会使用明确函数来创建对象(见本页下节)。但这样一来,每添加一个新的子类,我就必须添加一个新函数。这就是为了类型安全而牺牲掉的灵活性。还好,即使我做了错误选择,也可以使用Parameterize Method (283)或Replace Parameter with Explicit Methods (285)撤销决定。

另一个必须谨慎使用Class.forName()的原因是:它向用户暴露了子类名称。不过这并不太糟糕,因为你可以使用其他字符串,并在工厂函数中执行其他行为。这也是不使用Inline Method (117)去除工厂函数的一个好理由。

以明确函数创建子类

我可以通过另一条途径来隐藏子类——使用明确函数。如果你只有少数几个子类,而且它们都不再变化,这条途径是很有用的。我可能有个抽象的Person类,它有两个子类:Male和Female。首先我在超类中为每个子类定义一个工厂函数:

class Person...
  static Person createMale(){
    
    
      return new Male();
  }
  static Person createFemale() {
    
    
      return new Female();
  }

然后我可以把下面的调用

Person kent = new Male();

替换成

Person kent = Person.createMale();

但是这就使得超类必须知晓子类。如果想避免这种情况,你需要一个更为复杂的设计,例如Product Trader模式[Bäumer and Riehle]。绝大多数情况下你并不需要如此复杂的设计,上面介绍的做法已经绰绰有余。

猜你喜欢

转载自blog.csdn.net/m0_38039437/article/details/129744855