如何使用反射满足工厂模式中的开闭原则?

Posted

技术标签:

【中文标题】如何使用反射满足工厂模式中的开闭原则?【英文标题】:How to satisfy Open Closed Principle in Factory Pattern using Reflection? 【发布时间】:2012-10-14 12:55:01 【问题描述】:

我正在尝试使用 Head First Design Pattern 学习面向对象的设计模式。 这是书中工厂模式的一个示例,我想在不违反开放封闭原则的情况下添加新的披萨项目。 在书中给出的示例代码中,如果我添加新的比萨项目类,我需要修改 PizzaStore 和 PizzaOrder 类。但我只想添加新的披萨项目而不修改其他类。

public class ChicagoPizzaStore extends PizzaStore 

Pizza createPizza(String item) 
        if (item.equals("cheese")) 
                return new ChicagoStyleCheesePizza();
         else if (item.equals("veggie")) 
                return new ChicagoStyleVeggiePizza();
         else if (item.equals("clam")) 
                return new ChicagoStyleClamPizza();
         
            else return null;

此 PizzaStore 类用于创建和订购比萨饼。

public abstract class PizzaStore 

    abstract Pizza createPizza(String item);

public Pizza orderPizza(String type) 
    Pizza pizza = createPizza(type);
    System.out.println("--- Making a " + pizza.getName() + " ---");
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;

这是抽象的 Pizza 类:

public abstract class Pizza 
String name;
String dough;
String sauce;
ArrayList toppings = new ArrayList();

void prepare() 
    System.out.println("Preparing " + name);
    System.out.println("Tossing dough...");
    System.out.println("Adding sauce...");
    System.out.println("Adding toppings: ");
    for (int i = 0; i < toppings.size(); i++) 
        System.out.println("   " + toppings.get(i));
    

这个类用于接受客户的订单。

 public class PizzaTestDrive 

        public static void main(String[] args) 
            PizzaStore nyStore = new NYPizzaStore();
            PizzaStore chicagoStore = new ChicagoPizzaStore();

            Pizza pizza = nyStore.orderPizza("cheese");
            System.out.println("Ethan ordered a " + pizza.getName() + "\n");
    

这是我的新披萨项目类。我想在不修改chicagoPizzaStore 和testDrive 类的情况下订购这个披萨:

public class ChicagoStyleClamPizza extends Pizza 
    public ChicagoStyleClamPizza() 
        name = "Chicago Style Clam Pizza";
        dough = "Extra Thick Crust Dough";
        sauce = "Plum Tomato Sauce";
        toppings.add("Shredded Mozzarella Cheese");
        toppings.add("Frozen Clams from Chesapeake Bay");
    

    void cut() 
        System.out.println("Cutting the pizza into square slices");
    

【问题讨论】:

你手里拿着这本书。怎么没有回答你的问题?它应该你O/C原理,而不是让你好奇。 @Marko 我完成了工厂模式一章,但我无法解决。 :) 那是章末的问题吧? 我是这个 OO 设计模式的新手。所以,不明白。 郑重声明,我不太看好那本书。它的教学风格相当混乱,例子过于复杂。说实话,很难教授仅在相当复杂的程序中才开始有意义的东西。这些东西最好从经验中学习,并且在脑海中浮现基本想法会有所帮助,但您不会通过解决书籍示例来深入了解它们。 【参考方案1】:

就目前而言,每次ChicagoPizzaStore 推出一种新型披萨(Pizza 的新子类),您都需要向具体的创建者方法createPizza(String item) 添加更多功能,以使披萨店能够能够制作这些类型的披萨。

如您所见,这违反了OCP。

下面是这个问题的两种解决方案。

1.在createPizza(String item) 中使用反射来动态创建比萨饼

此解决方案将要求您最后一次违反 OCP 原则,但是使用反射动态创建 Pizza 实例意味着 ChicagoPizzaStore 将在此更改之外,不再需要修改以支持未来的风格比萨饼。

Pizza 类的新类型的名称必须与提供给 create Pizza 方法的键(项目参数)的名称相匹配。该解决方案的工作原理如下:

public class ChicagoPizzaStore extends PizzaStore 

Pizza createPizza(String item) 
        try 
            //some assumptions about the classpath locations made here
            return Class.forName(item).newInstance();
         catch(Exception e) 
            return null;
        

当创建新类型的Pizza 时,可以简单地将它们作为键传递给createPizza(item) 方法,然后它们就会被创建。

同样,如果从菜单中删除了 Pizza 类型,则从类路径中删除此类 Pizza 的类定义将导致 createPizza(item) 为折扣风味返回 null。

由于各种原因,对反射的使用受到批评,但对反射的批评超出了这个问题的范围,而且它也不是实现工厂问题的完全有效的解决方案。开闭原则。

2。子类 ChicagoPizzaStore

正如 SOLID 中的 O 所说,类是“对扩展开放,对修改关闭”。因此,您的问题的解决方案就是扩展ChicagoPizzaStore

public class ExtendedChicagoPizzaStore extends ChicagoPizzaStore 

    Pizza createPizza(String item) 
            if (item.equals("spicy")) 
                    return new RidiculouslySpicyPizza();
             else 
                    return super.createPizza(item);
           
    

此解决方案的优点是应用时不会违反OCP。

【讨论】:

如果你想删除一个旧披萨? @4imble 当现有产品更改或从目录中删除时,我认为从 OCP 的角度来看,工厂模式会崩溃,除非产品的定义和解决方案是动态的。在这一点上,我更新了反射示例以评论如何使用这种方法从菜单中删除披萨。【参考方案2】:

使用 switch 语句会破坏 OC。 要解决这个问题,多态性可能是要走的路。 也许是一个抽象工厂?

或者工厂通常是错误的,您想使用构建器模式。 毕竟披萨就是披萨就是披萨。 所以你只需要以不同的方式构建它们。 就像使用 StringBuilder...

【讨论】:

【参考方案3】:

这是一个运行示例

class FactoryClosedForModification 
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException 
            ShapeFactory sf = new ShapeFactory();
            Shape shape = (Shape) sf.getShape(Triangle.class.getName());
            shape.draw();
        
    


class ShapeFactory 

    public Object getShape(String shapeName)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException 
        return Class.forName(shapeName).newInstance();
    


class Shape 
    public void draw() 
        System.out.println("Drawing a shape.");
    


class Triangle extends Shape 
    @Override
    public void draw() 
        System.out.println("Drawing a Triangle.");
    


class Circle extends Shape 
    @Override
    public void draw() 
        System.out.println("Drawing Circle.");
    

【讨论】:

【参考方案4】:

工厂方法设计模式的重点是使客户端和具体产品松散耦合。客户端仅与接口或基类交互。因此,将来如果您需要添加新的具体产品类(在您的情况下为 Pizza),新类不应导致客户端代码的更改(在您的情况下为 PizzaTestDrive)。要添加新产品(Pizza),您只需要修改 Concrete Factory 类(在您的情况下为 ChicagoPizzaStore)。

我认为您对工厂方法设计模式的实现是正确的。对于添加新的 Pizza,客户端代码不会改变,只有 Concrete Factory 类在改变。

【讨论】:

“你应该只需要修改 Concrete Factory 类” - OP 的问题是找到一种方法来扩展 ChicagoPizzaStore 的功能而不对其进行任何编码更改(这将违反 OCP)。我不相信你已经回答了 OP 的问题。【参考方案5】:

如果你使用反射来满足开闭原则,你就是在牺牲性能。相反,您可以使用其他简单的技术按照开闭原则来制作您的工厂。 Factory Design Patterns and Open-Closed Principle (OCP), the ‘O’ in SOLID 对此给出了更恰当的解释。文章还讲述了如何调整简单工厂以遵循开放封闭原则。

【讨论】:

有两件事要提。第一件事:反射的性能与 OP 的问题无关——它是所描述问题的有效解决方案。第二件事:您提供了一个详细说明“其他简单技术”的链接,但您的链接使用的 SimpleFactory 问题的解决方案实际上是反射。【参考方案6】:

在您的情况下使用反射并不是很好。最好使用属性文件之类的东西 ChicagoPizzaStore 将项目映射到班级... 例如:

cheese=ChicagoStyleCheesePizza
veggie=ChicagoStyleVeggiePizza
clam=ChicagoStyleClamPizza

【讨论】:

以上是关于如何使用反射满足工厂模式中的开闭原则?的主要内容,如果未能解决你的问题,请参考以下文章

设计模式-工厂模式

LARAVEL:如何使用 SOLID 原则的开闭原则?

开发模式-工厂模式

设计模式 C++工厂方法模式

设计模式 C++工厂方法模式

使用C语言实现工厂模式