使工厂方法更具动态性

时间:2015-11-01 20:28:36

标签: java oop design-patterns dynamic

我正在我的代码中实现工厂模式,因此在工厂中遇到了一个有趣的事情,我可以使用Reflection替换工厂方法中的if else条件,以使我的代码更具动态性。

下面是两个设计的代码......

1)使用if-else条件

   public static Pizza createPizza(String type) {

        Pizza pizza = null;

            if(type.equals(PizzaType.Cheese))
            {
                pizza = new CheesePizza();
            }

            else if (type.equals(PizzaType.Tomato))
            {
                pizza = new TomatoPizza();
            }

            else if (type.equals(PizzaType.Capsicum))
            {
                pizza = new CapsicumPizza();
            }
            else
            {
                try {
                    throw new Exception("Entered PizzaType is not Valid");
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }


        return pizza;
    }

2)有了反思

    public static Pizza createPizza(String type) {

            Pizza pizza = null;

            for(PizzaType value : PizzaType.values())
            {
                if(type.equals(value.getPizzaTypeValue()))
                {
                    String fullyQualifiedclassname = value.getClassNameByPizzaType(type);

                    try {
                        pizza = (Pizza)Class.forName(fullyQualifiedclassname).newInstance();
                    } catch (InstantiationException | IllegalAccessException
                            | ClassNotFoundException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }

            return pizza;
        }

第二种方式对我来说非常好,因为我可以通过使用它来使我的代码更具动态性,因为我可以创建一个属性文件,其中包含类的名称和与之关联的类型以提供Open和更多关闭,如如果将来所有者想要向PizzaStore添加更多比萨饼,那么他只需要在属性文件中创建条目,然后再创建一个Pizza的子类。

但我读到反思有很多缺点,很少提到它们。

It is hack of compiler
Automatic development tools are not able to work with reflections code
It's difficult to debug reflections code
Reflection complicates understanding and navigation in code
Significant performance penalty

非常好奇地知道,哪种设计很好,因为我非常有兴趣让我的代码变得越来越动态。

5 个答案:

答案 0 :(得分:2)

您也可以在中间使用设计。 E.g。

public interface Factory<T> {
  public T newInstance();
}

public class TomatoPizzaFactory implements Factory<TomatoPizza> {

    @Override
    public TomatoPizza newInstance() {
        return new TomatoPizza();
    }
}

public class PizzaFactory {

    private Map<String, Factory<? extends Pizza>> factories = new HashMap<String, Factory<? extends Pizza>>();

    public PizzaFactory(){
        factories.put(PizzaType.Cheese, new CheesePizzaFactory());
        factories.put(PizzaType.Tomato, new TomatoPizzaFactory());
    }

    public Pizza createPizza(String type){
        Factory<? extends Pizza> factory = factories.get(type);
        if(factory == null){
           throw new IllegalArgumentException("Unknown pizza type");
        }
        return  factory.newInstance();
    }
}

为简单实例化实施DefaultConstructorFactory

public class DefaultConstructorFactory<T> implements Factory<T> {

    private Class<T> type;

    public DefaultConstructorFactory(Class<T> type) {
      this.type = type;
    }

    public T newInstance() {
       try {
         return type.newInstance();
       } catch (InstantiationException e) {
         throw new IllegalStateException("Can not instantiate " + type, e);
       } catch (IllegalAccessException e) {
         throw new IllegalStateException("Can not instantiate " + type, e);
    }
  }
}
  

但我读到反思有很多缺点,很少提到它们。

     

是编译器的黑客

它可能是一个黑客,但如果您正在编写基础设施代码,您将经常使用反射。特别是在编写必须在运行时内省类的框架时,因为框架不知道它将在运行时处理的类。想想hibernate,spring,jpa等。

  

自动开发工具无法使用反射代码

这是真的,因为您将许多编译器问题转移到运行时。因此,您应该像编译器一样处理反射异常并提供良好的错误消息。

在某些IDE中也只有一点点重构支持。因此在更改反射代码使用的代码时必须小心。

尽管如此,您可以编写测试来快速查找错误。

  

调试反射代码很困难

这也是事实,因为你无法直接跳转到某个方法,你必须调查变量以找出访问哪个对象的成员。 它更加间接。

  

反射使代码中的理解和导航变得复杂

是的,如果以错误的方式使用它。如果您不知道必须在运行时处理的类型(基础结构或框架代码),则仅使用反射。如果您知道类型并且只想最小化编写的代码,请不要使用反射。如果要最小化编写的代码,请选择更好的设计。

  

显着的性能损失

我不这么认为。当然,反射调用是间接方法调用,因此必须执行更多代码才能执行与直接方法调用相同的操作。但是这个开销很小。

答案 1 :(得分:1)

在这种情况下,反思似乎对我来说太过分了。为什么不在3.4界面中添加工厂方法Pizza create();

然后让每个实现类进行初始化,如:

PizzaType

所以你最终只得到:

class CheesePizza implements PizzaType {
    Pizza create() {
        return new CheesePizza();
    }
} 

这样,第一个例子中的循环不会无效,并且可以轻松扩展。

如果你的代码不需要实时执行,你也不应该过多担心反射性能。我同意,它使代码无法读取,因此在大多数情况下避免使用它会更好。

修改:我已经监督,for(PizzaType value : PizzaType.values() if(type.equals(value)) pizza = value.create(); 是一个枚举,而不是PizzaType。在这种情况下,你可以创建一个或为你的枚举添加一个创建方法,但我不认为后者是非常有利的。

答案 2 :(得分:1)

一般不建议以这种方式使用反射, 但如果动态创建对象是设计中的首要任务, 那么它是可以接受的。

另一个不错的选择可能是不使用不同的Pizza子类型, 但是可以参数化的单个Pizza类。 那会很有意义, 因为我不认为比萨饼表现得如此不同。 在我看来,使用构建器模式可以轻松创建各种各样的比萨饼。

Btw,另一个不好的做法引起了人们的注意, 工厂方法的参数是String, 如果您实际上似乎PizzaTypeenum, 并将String参数与枚举值进行比较。 使用枚举值作为参数会好得多, 和switch而不是链式if-else。 但是,这里的另一个问题是枚举重复了可用的披萨类型。因此,当您添加新的比萨饼类型时,您还必须更新枚举和工厂方法。那个文件太多了。这是代码味道。

我建议尝试单个Pizza类和构建器模式。

答案 3 :(得分:0)

if - else语句不会对您的设计有所贡献。这仅仅确保了披萨的存在 - 在继承层次结构中。尝试使用装饰器模式来最小化层次结构级别并使用具体类(如:plainPizza)来添加装饰。

装饰器模式旨在在运行时动态添加装饰。

答案 4 :(得分:0)

如果您只有少数工厂退回的课程(假设有三个或四个),您应该以{{1​​}}语句的最简单形式留下

if

如果您有更多类类型或者您预测此层次结构中的频繁更改,那么您应切换到if(type.equals("someType"){ return new SomeType(); } else if(type.equals("otherType"){ return new OtherType(); } 将具有可用实现集合的实现。您的工厂类将包含可能的实例类型的字段。

factory class

如何使用元素填充此地图可能会有问题。 你可以:

  • public class MyFactory{ private Map<String, Class<? extends ReturnedClass> availableTypes; } 内的构造函数/ init方法中对它们进行硬编码。注意:每个新添加的类都需要在工厂中进行更改。
  • 在工厂内制作方法Factory。每个返回的类都必须在工厂内注册。注意:添加新课程后,您无需更改工厂
  • (推荐)使用依赖注入来填充地图。