实用的设计模式02-好好聊聊简单工厂和工厂方法

对于工厂的用法其实并没有可说的,就是当一个抽象类或接口有多个实现类,在应用时需要实例化某个实现类对象时,不是直接new,而是用工厂类获取对象实例。但是根据实际场景的复杂程度也需要选择最优方案,就是简单工厂、工厂方法、抽象工厂。
下面就通过业务场景来这三个模式和他们的关系、区别和演进。
由于篇幅较大,分成两篇,第一篇聊工厂方法和简单工厂,第二篇聊抽象工厂,抽象工厂中在研究一下Mybatis中对工厂模式的应用。

目录

  • 1、先介绍两个程序设计原则
    • 1.1 开放-封闭原则
    • 1.2 依赖倒转原则
  • 2、不是设计模式的简单工厂模式
    • 2.1 案例
    • 2.2 实现
    • 2.3 使用简单工厂改造
      • 2.3.1 改造思路
      • 2.3.2 类图
      • 2.3.3 代码
    • 2.4 再谈开放-封闭原则
    • 2.5 使用反射优化简单工厂模式
  • 3、工厂方法模式
    • 3.1 业务要求
    • 3.2 使用工厂方法改造
      • 3.2.1 改造思路
      • 3.2.2 类图
      • 3.2.3 代码
    • 3.3 工厂方法模式总结

1、先介绍两个程序设计原则

关于下面两条设计原则,我们先做简单阐述,在应用具体案例之后在根据案例详细讲解。

1.1 开放-封闭原则

  • 原则:软件实体(类、模块、函数等等)应该可以扩展,而不可以修改
  • 特点:对于扩展是开放的,对修改时封闭的。也就是说在开发软件系统时,针对以后可能的需求的变更,在设计上应该力求对程序的改动是通过新增代码完成的,而不是修改代码。

1.2 依赖倒转原则

  • 原则:A.高层模块不应该依赖低层模块,两个都应该依赖抽象。B.抽象不应该依赖细节,细节应该依赖抽象。
    说白了,就是针对接口编程。
    举例说明:
    在软件系统都要使用数据库,数据库算是低层模块,业务系统是高层模块。为了代码复用,我们一般会将数据库访问的代码做封装。这样我们再去做其他项目时就可以服用这部分代码了。但是考虑一下如果要换数据库呢,这就很麻烦了,我们的高层代码都是和低层代码绑定在一起,这样改动会非常大。如何解决呢?这就要用到依赖倒转原则,也就是针对接口编程。详细的应用和讲解请看下边抽象工厂模式的讲解。

2、不是设计模式的简单工厂模式

2.1 案例

要求:我准备开一个披萨店,有各种风味的披萨。用户订购是根据披萨的名称制作。
类:PizzaStore(披萨店)、Pizza(抽象的披萨)、各种风味的具体的披萨、Client(客户端)

2.2 实现

Pizza.class:披萨抽象类

public abstract class Pizza {
    /**pizza名称**/
    String name;
    /**面团**/
    String dough;
    /**酱**/
    String sauce;
    /**配料**/
    ArrayList<String> toppings = new ArrayList<>();
    void prepare(){
        System.out.println("准备"+name);
        System.out.println("揉面...");
        System.out.println("制作酱料...");
        System.out.println("添加配料: ");
        for (String topping : toppings) {
            System.out.println(topping+"    ");
        }
    }
    void bake(){
        System.out.println("350℃烘烤25分钟");
    }
    void cut(){
        System.out.println("对角切");
    }
    void box(){
        System.out.println("装盒");
    }
    public String getName(){
        return this.name;
    }
}

AStyleCheesePizza.class:A风味的披萨

public class AStyleCheesePizza extends Pizza {
    public AStyleCheesePizza() {
        name = "A风味的pizza";
        dough = "脆脆的面团";
        sauce = "大蒜蘸料";
        toppings.add("配料是意大利高级干酪");
    }
}

BStyleCheesePizza.class:B风味的披萨

public class BStyleCheesePizza extends Pizza {
    public BStyleCheesePizza() {
        name = "B风味的pizza";
        dough = "脆脆的面团";
        sauce = "大蒜蘸料";
        toppings.add("配料是意大利高级干酪");
    }
}

CStyleCheesePizza.class:C风味的披萨

public class CStyleCheesePizza extends Pizza {
    public CStyleCheesePizza() {
        name = "C风味的pizza";
        dough = "脆脆的面团";
        sauce = "大蒜蘸料";
        toppings.add("配料是意大利高级干酪");
    }
}

PizzaStore.class

public class PizzaStore {
    Pizza pizza = null;
    public Pizza orderPizza(String type){
        switch (type){
            case "A":
                pizza = new AStyleCheesePizza();
                break;
            case "B":
                pizza = new BStyleCheesePizza();
                break;
            case "C":
                pizza = new CStyleCheesePizza();
                break;
            default:
                break;
        }
        pizza = SimplePizzaFctory.getPizza(type);
        
        pizza.prepare();
        pizza.bake();
        pizza.box();
        pizza.cut();
        //其他相关代码,比如处理订单等
        return pizza;
    }
}

Client.class

public class Client {
    public static void main(String[] args) {
        PizzaStore store = new PizzaStore();

        Pizza pizza = store.orderPizza("A");
        System.out.println();
        System.out.println();

        System.out.println(pizza.getName()+"制作完成了");
    }
}

输出:
实用的设计模式02-好好聊聊简单工厂和工厂方法_第1张图片

2.3 使用简单工厂改造

2.3.1 改造思路

既然要改造,肯定是有不足之处。上述代码的问题在PizzaStore中,如果现在我们要增加一种口味的披萨,那么就要打开PizzaStore类的orderPizza方法进行修改,PizzaStrore中的业务是非常多的,应该尽量避免修改它。同时我们看到在pizzaStore中创建Pizza实例时是使用的new关键字,这是针对一个具体的实现类,虽然PizzaStrore中使用的是抽象类Pizza,但是在new时确实使用的具体类。问题就是,如果有一种披萨要改名,那仍然要去改PizzaStore。上述的就是代码中会发生变化的部分,除了变化的部分其他的是相对稳定的,这个时候就要把变化的部分抽离出来,也就是说将创建Pizza实例的这部分被代码抽离出来。

2.3.2 类图

实用的设计模式02-好好聊聊简单工厂和工厂方法_第2张图片

2.3.3 代码

各种类型的披萨类(AStyleCheesePizza,BStyleCheesePizza,CStyleCheesePizza)和Pizza抽象类,包括客户端(Client)都不需要变。
SimpleFactory.java:工厂类

public class SimplePizzaFctory {
    public static Pizza createPizza(String type){
        Pizza pizza = null;
        switch (type){
            case "A":
                pizza = new AStyleCheesePizza();
                break;
            case "B":
                pizza = new BStyleCheesePizza();
                break;
            case "C":
                pizza = new CStyleCheesePizza();
                break;
            default:
                break;
        }
        return pizza;
    }
}

PizzaStore.java:披萨店类

public class PizzaStore {
    Pizza pizza = null;

    public Pizza orderPizza(String type){
        pizza = SimplePizzaFctory.createPizza(type);
        
        pizza.prepare();
        pizza.bake();
        pizza.box();
        pizza.cut();

        //其他相关代码,比如处理订单等

        return pizza;
    }
}

解析:有的同学说了,之前改PizzaStore现在改的话要改SimpleFactory,有什么区别?NoNoNo,这样就实现了,PizzaStore和具体Pizaa类之间的解耦,两者不在紧密相连,谁有问题就该谁,另一方不用动,这不好很多了吗?当然,还不够好,我们继续往下看工厂方法和抽象工厂。

2.4 再谈开放-封闭原则

上面使用简单工厂对代码进行更改就是为了遵循开放封闭原则,以及解耦。将可能产生变化的代码从相对稳定的代码中提取出来,使得代码尽量不去改动。但是我们也看到了,SimpleFactory类并没有对修改封闭,这也正是将要写到的,无论模块多么封闭,都会存在一些无法对其封闭的变化,既然不可能完全封闭,那么设计人员就要对其设计的模块应该对那些变化封闭做出选择,你必须看到那些最有可能变化的部分,并采取办法如构造抽象去隔离它

2.5 使用反射优化简单工厂模式

  • 下面采用java的反射去实现简单工厂模式
    SimpleFactory.java:工厂类
public class SimplePizzaFctory {
    public static Pizza createPizza(Class<?> type) {
        Pizza pizza = null;
        try {
            pizza = (Pizza)type.getConstructor().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return pizza;
    }

}

PizzaStore.java:披萨店类

public class PizzaStore {
    Pizza pizza = null;

    public Pizza orderPizza(Class<?> type) {
        pizza = SimplePizzaFctory.createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.box();
        pizza.cut();

        //其他相关代码,比如处理订单等

        return pizza;
    }
}

Client.java:披萨店类

public class Client {
    public static void main(String[] args) {
        PizzaStore store = new PizzaStore();

        Pizza pizza = store.orderPizza(AStyleCheesePizza.class);
        System.out.println();
        System.out.println();

        System.out.println(pizza.getName()+"制作完成了");
    }
}

3、工厂方法模式

3.1 业务要求

披萨店经营的不错,要开加盟店了,我希望加盟店中能继续用我目前的软件系统,以保证披萨的流程能一致。同时t由于各地区人们对披萨口为的喜好不同,因此各个加盟店所制作的披萨口味不尽相同。所以我希望能够创建一个框架,能把加盟店和披萨制造绑定到一起,但是又不能像最初的代码那样(制造披萨的代码放到PizzaStore的orderPizza()中),披萨制作和订购紧紧耦合在一起,我想即绑定到一起,又有一定的弹性处理变化。

3.2 使用工厂方法改造

3.2.1 改造思路

根据需求我们可以采用工厂方法模式,把PizzaStore编程一个抽象类,将披萨的创建工作放到PizzaStore的的一个方法中,并且将这个方法抽象化,交给子类去实现,来决定制作什么风味的披萨。这样做的好处是稳定的代码都在父类中,子类只需要实现工厂方法,我们仍然把变化限制了在了一个很小的范围中,并且把Pizza的创建个披萨店绑定到了一起。

3.2.2 类图

实用的设计模式02-好好聊聊简单工厂和工厂方法_第3张图片

  • PizzaStore.java:抽象披萨工厂
  • NYPizzaStore.java:纽约披萨工厂
  • ChicagoPizzaStore.java:芝加哥的披萨工厂
  • Pizza.java:抽象披萨
  • AStylePizza.java:A口味的披萨
  • NYStylePizza.java:纽约口味的披萨
  • ChicagoStylePizza.java:芝加哥口味的披萨
  • Client.java:客户端

3.2.3 代码

PizzaStore.java:抽象披萨工厂

public abstract class PizzaStore {
    Pizza pizza = null;

    public Pizza orderPizza(String type) {
        pizza = this.createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.box();
        pizza.cut();

        //其他相关代码,比如处理订单等

        return pizza;
    }

    public abstract Pizza createPizza(String type);
}

NYPizzaStore.java:纽约披萨工厂

public class NYPizzaStore extends PizzaStore {
    @Override
    public Pizza createPizza(String type) {
        Pizza pizza = null;
        switch (type){
            case "A":
                pizza = new AStyleCheesePizza();
                break;
            case "B":
                pizza = new BStyleCheesePizza();
                break;
            case "C":
                pizza = new CStyleCheesePizza();
                break;
            case "NY":
                pizza = new NYStyleCheesePizza();//新增纽约口为的披萨
                break;
            default:
                break;
        }
        return pizza;
    }
}

ChicagoPizzaStore.java:芝加哥的披萨工厂

public class ChicagoPizzaStore extends PizzaStore {
    @Override
    public Pizza createPizza(String type) {
        Pizza pizza = null;
        switch (type){
            case "A":
                pizza = new AStyleCheesePizza();
                break;
            case "B":
                pizza = new BStyleCheesePizza();
                break;
            case "C":
                pizza = new CStyleCheesePizza();
                break;
            case "Chicago":
                pizza = new ChicagoStyleCheesePizza();//新增芝加哥口为的披萨
                break;
            default:
                break;
        }
        return pizza;
    }
}

Pizza.java:抽象披萨

public abstract class Pizza {
    /**pizza名称**/
    String name;
    /**面团**/
    String dough;
    /**酱**/
    String sauce;
    /**配料**/
    ArrayList<String> toppings = new ArrayList<>();

    void prepare(){
        System.out.println("准备"+name);
        System.out.println("揉面...");
        System.out.println("制作酱料...");
        System.out.println("添加配料: ");
        for (String topping : toppings) {
            System.out.println(topping+"    ");
        }
    }

    void bake(){
        System.out.println("350℃烘烤25分钟");
    }

    void cut(){
        System.out.println("对角切");
    }

    void box(){
        System.out.println("装盒");
    }

    public String getName(){
        return this.name;
    }


}

AStylePizza.java:A口味的披萨

public class AStyleCheesePizza extends Pizza {
    public AStyleCheesePizza() {
        name = "A风味的pizza";
        dough = "脆脆的面团";
        sauce = "大蒜蘸料";
        toppings.add("配料是意大利高级干酪");
    }
}

NYStylePizza.java:纽约口味的披萨

public class NYStyleCheesePizza extends Pizza {
    public NYStyleCheesePizza() {
        name = "纽约风味的pizza";
        dough = "脆脆的面团";
        sauce = "大蒜蘸料";
        toppings.add("配料是意大利高级干酪");
    }
}

ChicagoStylePizza.java:芝加哥口味的披萨

public class ChicagoStyleCheesePizza extends Pizza {
    public ChicagoStyleCheesePizza() {
        name = "芝加哥风味的pizza";
        dough = "蘸料是小番茄";
        sauce = "面团的";
        toppings.add("配料是意大利白干酪");
    }

    @Override
    void cut() {
        System.out.println("切成方的");
    }
}

Client.java:客户端

public class Client {
    public static void main(String[] args) {
        PizzaStore store = new NYPizzaStore();

        Pizza pizza = store.orderPizza("NY");
        System.out.println();
        System.out.println();

        System.out.println(pizza.getName()+"制作完成了");
    }
}

输出:
实用的设计模式02-好好聊聊简单工厂和工厂方法_第4张图片

这样我们就实现了披萨制作和订购绑定到一起,又有一定的弹性处理变化。但又新增pizza的需求的时候,只需要打开相应的披萨店(子类披萨店只决定如何生产pizza)修改代码就行了,披萨店父类中的orderPizza()并不知道订单中披萨具体的种类,它只是对一个披萨(pizza父类)做操作,所以即使父类需要用到子类创造的对象,但由于抽象的存在,子类的改变任不会影响到父类。

3.3 工厂方法模式总结

  • 工厂方法模式的定义:定义了一个创建对象的接口(抽象方法),但由子类决定要实例化的类是哪一个。工厂方法模式让类把实例化推迟到子类。
  • 所有的工厂模式都是用来封装对象的创建,工厂方法模式通过让子类决定创建的对象是什么,来达到将创建过程封装的目的。

你可能感兴趣的:(设计模式,设计模式,java)