在java中扩展参数化工厂方法

时间:2017-01-20 16:26:13

标签: java solid-principles factory-method open-closed-principle

我是OOP的新手并且学习设计模式,所以我写了一些简单的代码来试用一个工厂方法,一切看起来都不错,除非我想添加另一个子类型。这是迄今为止的代码:

public interface Person {
  public String getDescription();
}

public class Adult implements Person { 
  @Override
  public String getDescription() {
    return "I am an ADULT";
  }
}

public class Child implements Person {
  @Override
  public String getDescription() {
    return "I am a CHILD";
  }
}

public class PersonFactory {
  public Person create(int age) {
    if (age < 18) return new Child();
    return new Adult();
  }
}

public class ClientA {
  public static void main(String[] args) {
    PersonFactory personFactory = new PersonFactory();
    Person person;
    person = personFactory.create(80);
    System.out.println(person.getDescription());   
  }
}

如果要求稍后更改为包含子类Pensioner,则年龄为&gt; 70,我必须要么:

将行if (age > 70) return new Pensioner();添加到create()类中的PersonFactory方法,这肯定会违反开放原则? 或者,正如The Gang Of Four Design Patterns一书中所建议的那样,覆盖参数化工厂方法以选择性地扩展Creator生成的产品。在这种情况下,我认为这意味着要写一个新的课程:

public class PersonFactoryWithPensioner extends PersonFactory { 
  @Override
  public Person create(int age) {
    if (age > 70) return new Pensioner();
    return super.create(age);
  }
}

现在这意味着现在所有调用PersonFactory的客户都需要更改为使用PersonFactoryWithPensioner,或者我必须接受新客户可以调用PersonFactoryWithPensioner老客户,例如。当年龄> 1时,ClientA仍然只会收到Adult个对象如果以后在另一个子类上,情况会变得更糟。 Infant已添加。为了确保新客户收到InfantChildAdultPensioner的任何对象,新类PersonFactoryWithInfant必须延长{{1} }}。这可能是对的,似乎我更可能误解了GoF的建议。

我的问题是:有没有办法添加一个新的子类型,可以在不更改它们的情况下返回给旧客户端,并且可以通过更改PersonFactoryWithPensioner代码来包含新的子类型而不破坏OCP ?

道歉,如果我没有正确发布,这是我第一次在这里发帖提问。我已经查看了类似问题的先前答案,但他们似乎并没有完全解决这个问题。

5 个答案:

答案 0 :(得分:2)

规则有时意味着被打破,所以我说BREAK封闭原则让它保持干净和简单。在我看来,为每种类型的人创建多个工厂类的开销会破坏整个工厂方法的目的。打破封闭原则允许您使用单个类来创建任何类型的人。

  public Person create(int age) {
    if (age < 4) return new Infant();
    if (age < 18) return new Child();
    if (age < 70) return new Adult();
    return new Pensioner();
  }

答案 1 :(得分:2)

我认为OCP并没有停止修改任何方法或类。

但是,它建议如果你需要做任何修改,你应该这样做,这样你就不必再修改那个代码。

鉴于您可能需要稍后修改PersonFactory - 您可以创建另一个Factory类来创建PersonFactory类型的对象。虽然这似乎是过度设计的解决方案。

另一种可能的解决方案是PersonFactory从一些动态源加载此规则,例如,将此规则保存在使用JSON格式的文件中。 然后使用反射动态创建对象。

这样的事情:

 private static JSONObject RULES;
   static {
       RULES= JSON.parse(rulesEngine.load());
   } 
    public class PersonFactory {
      public Person create(int age) {
        String personToCreate = RULES.get(age);
        Constructor<?> ctor = Class.forName(personToCreate).getConstructor();
        return (Person) ctor.newInstance();
      }
    }

json规则是这样的:

{
  "1":"Child.class",
  "2":"Child.class",
  ...,
  "17":"Child.class",
  "18":"Adult.class",
  ...,
  "69":"Adult.class",
  "70":"Pensioner.class"
}

这样你就不会违反OCP原则。

答案 2 :(得分:2)

开放封闭的原则很好记住。然而,它与工厂不能很好地协同工作。下面是一个可以进行排序的选项,它将工厂变成了一个注册表:

    PersonFactory pf = new PersonFactory();
    // Java 8 lambdas are great!
    pf.register((age) -> age < 18 ? new Child() : null );
    pf.register((age) -> age >= 18 ? new Adult() : null );

    System.out.println(pf.create(10).getDescription());     

@alayor的答案类似,避免必须修改工厂逻辑,或者必须完全更换工厂并让每个人都使用新版本的唯一方法是工厂的从其他地方获得逻辑。 @alayor从配置文件中获取它;我建议将它作为初始化的一部分添加到工厂(也可以在工厂构造函数中完成;将其更改为,例如public PersonFactory(PersonCreator ... rules))。

完整代码:

interface PersonCreator {
    Person create(int age);
}

class PersonFactory {
    private List<PersonCreator> pcs = new ArrayList<>();

    public void register(PersonCreator pc) {
        pcs.add(pc);
    }

    public Person create(int age) {
        for (PersonCreator pc : pcs) {
            Person p = pc.create(age);
            if (p != null) {
                return p;
            }
        }
        return null;
    }
}

interface Person {
    public String getDescription();
}

class Adult implements Person {
    @Override
    public String getDescription() {
        return "I am an ADULT";
    }
}

class Child implements Person {
    @Override
    public String getDescription() {
        return "I am a CHILD";
    }
}

public class Main {
    public static void main(String[] args) {
        PersonFactory pf = new PersonFactory();
        // Java 8 lambdas are great!
        pf.register((age) -> age < 18 ? new Child() : null );
        pf.register((age) -> age >= 18 ? new Adult() : null );

        System.out.println(pf.create(10).getDescription());            
    }
}

答案 3 :(得分:1)

问题是“客户是否希望在没有代码修改的情况下创建养老金领取者对象?”。如果是,那么您应该打破“已关闭”规则并更新工厂代码。如果没有,那么你应该创建一个新工厂,客户将使用它。

答案 4 :(得分:1)

这里的所有答案都表明某种动态规则实际上是打破了开放原则。这个原则并不是要改变已经编写过的代码片段&#34;但是&#34;不要改变已经使用的代码的结果&#34;。也就是说,如果客户希望它只能获得两个结果 - 成人或儿童然后通过将其硬编码到功能或通过动态规则集提供第三种可能性就违反了开放原则。

但回到你的问题 - 我会说这取决于你。原则和模式很好,很有趣,但除了在实际的日常工作中,我们必须始终关注大局并决定是否应用某些规则。把它们视为提示,而不是用石头写的东西。

如果您的代码有些关闭,那就是您可以控制PersonFactory的每次调用,那么更改在软件生命周期中就是正常的事情。我不记得我参与过的任何现实生活项目,它没有改变以前创建的任何代码。事实上,我们每天都在这样做:)

其他事情是当您的代码被未知数量的第三方客户端使用时(例如公共API)。那么你应该注意不要破坏某些东西,但是在现有方法中呈现新的逻辑(就像在这里添加Person的新概念一样)是完全可以接受的。如果那些会破坏更改,那么请考虑添加旧版本的更新代码的新版本/升级版本(并且可能在将来的某个时候有一个弃用旧版本的计划,因为您真的不希望最终维护10000个版本的版本代码;)

还要记住其他可以帮助您避免一些问题的OOP。在你的例子中,Adult,Child和Pensioner都实现了很好的Person接口。所以任何只知道Adult和Child实现的代码都不应该使用Pensioner值,因为它们都只是Person的实现,而且代码也应该将Pensioner视为Person,甚至不知道你引入了新类型。