工厂方法(Factory Pattern)

news/2024/11/9 15:04:16

工厂方法模式定义:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。(注:“决定”不是指模式允许子类本身在运行时做决定,而是指在编写创建者类时,不需要知道实际创建的产品是哪一个。选择了使用哪个子类,自然就决定了实际创建的产品是什么)

 

假设我们要开一间披萨店,提供不同口味的披萨。

首先有一个PizzaStore的类,里面提供一个orderPizza的方法,让客户选择要购买的Pizza。

最开始想到的是这样写:

public class PizzaStore {
    
    public Pizza orderPizza(String type) {
        Pizza pizza = null;
        
        if(type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if(type.equals("greek")){
            pizza = new GreekPizza();
        } else if(type.equals("pepperoni")){
            pizza = new PepperoniPizza();
        }
        
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

当披萨店增加比萨口味的时候,必须修改if else部分的代码,这样做没有对修改关闭。

所以,将代码会变化的部分提取出来,封装创建对象的代码到另一个类,就是SimplePizzaFactory。

public class SimplePizzaFactory {
    public Pizza createPizza(String type) {
        Pizza pizza = null;
        
        if(type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if(type.equals("greek")){
            pizza = new GreekPizza();
        } else if(type.equals("pepperoni")){
            pizza = new PepperoniPizza();
        }
        
        return pizza;
    }
}

PizzaStore变成了:

public class PizzaStore {
    
    SimplePizzaFactory factory;
    
    public PizzaStore(SimplePizzaFactory factory) {
        this.factory = factory;
    }
    
    public Pizza orderPizza(String type) {
        Pizza pizza = null;
        
        pizza = factory.createPizza(type);
        
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

问题:是否只是将问题搬到另一个对象?问题依然存在。

这样做有好处,SimplePizzaFactory可以有许多客户,不仅仅是PizzaStore,如果有一个类,PizzaShopMenu要求获得比萨的价格和描述,它只需与SimplePizzaFactory交互。所以,把创建比萨的代码包装进一个类,当以后实现改变时,只需要修改这个类即可。这就是简单工厂方法。

 

披萨店发展的很好,现在要在全球各地开分店,但不同区域的口味有差异,比如(纽约风味、芝加哥风味、加州风味)。

 一种做法是,利用SimplePizzaFactory,写出三种不同的工厂NYPizzaFactory,ChicagoPizzaFactory、CaliforniaPizzaFactory。坏处是:比如在中国要开一个China风味的Pizza,要增加一个ChinaPizzaFactory的类,如果披萨的口味增加,比如增加一种不添加任何料的pizza,那么需要修改全部SimplePiizaFactory的子类。

另一种做法是,在实际中,不太可能单独划分不同工厂来生产不同披萨,而且,制造披萨一般是在披萨店现场制造的。那么就需要将制作披萨与披萨店绑定。

public abstract class PizzaStore {
    
    public Pizza orderPizza(String type) {
        Pizza pizza;
        
        pizza = createPizza(type);
        
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
    abstract Pizza createPizza(String type);
}

PizzaStore现在是一个抽象类,让NYStylePizzaStore,ChicagoStylePizzaStore,CaliforniaPizzaFactory继承PizzaStore实现各自的createPizza方法。让不同的披萨店(子类)来决定披萨的做法。坏处是:每增加一种披萨口味,要增加一个继承Pizza的子类和修改全部继承PizzaStore的子类的代码。这就是工厂方法。

 

现在为确保每家披萨店使用高质量原料,打算建造一家生产原料的工厂,并将原料运送到各家加盟店。问题是,不同地方的原料是不一样的,比如(芝加哥的人喜欢番茄酱料,纽约的人喜欢大蒜番茄酱料。

先定义一个原料工厂接口:

public interface PizzaIngredientFactory {
    public Dough createDough();
    public Sauce createSauce();
    public Cheese createCheese();
    public Veggies[] createVeggies();
    public Pepperoni createPepperoni();
    public Clams createClam();
}

 

现在可为不同区域的披萨店提供不同原料了:

public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
    public Dough createDough() {
        return new ThinCrustDough();
    }
    
    public Sauce createSauce() {
        return new MarinaraSauce();
    }
    public Cheese createCheese() {
        return new ReggianoCheese();
    }
    public Veggies[] createVeggies() {
        Veggies veggies[] = { new Garlic(), new  Onion(), new Mushroom(), new RedPepper() };
        return veggies;
    }
    public Pepperoni createPepperoni() {
        return new SlicedPepperoni();
    }
    public Clams createClam() {
        return new FreshClams();
    }
}

 重做披萨类,使用我们的工厂生产的原材料:

public abstract class Pizza {
    String name;
    Dough dough;
    Sauce sauce;
    Veggies veggies[];
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clam;

    abstract void perpare();        //现在perpare方法声明为抽象,这个方法实现收集特定pizza的原材料

    void bake() {
        System.out.println("Bake for 25 minutes at 350");
    }
    void cut() {
        System.out.println("Place pizza in official PizzaStore box");
    }
    void setName(String name) {
        this.name = name;
    }
    String getName() {
        return name;
    }
    public String toString() {
        System.out.println(name);
    }
}

 

一个Pizza类的子类:

public class CheesPizza extends Pizza {
    PizzaIngredientFactory ingredientFactory;    
    public CheesePizza(PizzaIngredientFactory i) {
        ingredientFactory = i;
    }

    void prepare(){    //现在不同的pizza实现不同的prepare方式
        System.out.println("Preparing " + name);
        dough = ingredientFactory.createDough();
        sauce = ingredientFactory.createSauce();
        cheese = ingredientFactory.createCheese();
    }
}

再回到披萨店:

public class NYPizzaStore extends PizzaStroe {
    public Pizza createPizza(String item) {
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();    //纽约店使用纽约的原料工厂
        
        if(item.equals("cheese")) {
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("New York Style Cheese Pizza");
        } else if(item.equals("veggie")) {
            pizza = new VeggiePizza(ingredientFactory);
            pizza.setName("New York Style Veggie Pizza");
        }
        ......
    
        return pizza;
    }
}    

我们引入一个抽象工厂----原料生产工厂,通过抽象工厂接口,创建产品家族,利用这个接口写代码,就从实际工厂解耦,当增加另一个实际工厂,只需要让它实现这个接口。

这就是抽象工厂模式。

 

转载于:https://www.cnblogs.com/13jhzeng/p/5250169.html


http://www.niftyadmin.cn/n/3745863.html

相关文章

从零开发一款相机APP 第四篇: Camera 常用api和最新框架介绍

本课程内容由 小驰笔记 出品,欢迎关注,获取更多交流信息~ 欢迎访问个人博客:www.xiaochibiji.com这节课,我们主要来学习了解android camera相关api,以及大体看下android camera的最新框架。 一、android camera api介…

idea导入新maven项目后,修改idea的maven配置,不要用idea默认的

idea导入新maven项目后,修改maven的setting路径配置,不要用默认的 file>setting>搜索Maven 三个分别是maven安装路径,配置文件路径,本地Jar仓库路径

使用let's Encrypt给网站加入https同时支持http2

2019独角兽企业重金招聘Python工程师标准>>> 1.如果python版本是2.6请运行 yum install python-argparse.noarch 2.重点 下载脚本 wget https://raw.githubusercontent.com/xdtianyu/scripts/master/lets-encrypt/letsencrypt.conf wget https://raw.githubusercon…

hbase bulkload导入数据

2019独角兽企业重金招聘Python工程师标准>>> 生车file /*** */ package HBaseIA.TwitBase.mapreduce;import java.io.IOException;import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.KeyValue; impo…

安装和使用nltk

1 安装nltk # 我安装的nltk3.6.2,不同的版本可能会有变化 pip install nltk# 如果安装比较慢,使用国内镜像源 pip install nltk -i http://pypi.doubanio.com/simple/# 豆瓣镜像源 http://pypi.doubanio.com/simple/# 可以在地址栏直接输入查找的包&…

git如何退回到某个版本

git log 查看提交历史 commit 后面那一长串就是版本号, 然后 git reset --hard 版本号 即可退回到某次提交的版本

基于spaCy实现pytextrank对英文短语抽取

1 参考学习网站 # 中文的博客 https://www.5axxw.com/wiki/content/475klz ​ # pytextrank的简单使用 https://spacy.io/universe/project/spacy-pytextrank https://derwen.ai/docs/ptr/start/ 2 安装开发环境 (1)安装python3.8 注意:在…

docker安装maven私有仓库(nexus3)

1 下载nexus3 # 不同的版本使用的方法有所不同,建议使用比较稳定的版本 sudo docker pull sonatype/nexus3:3.20.0 2 设置数据卷 # 建立目录 sudo mkdir mvn_repository # 设置文件权限 sudo chmod 777 mvn_repository # 设置目录所有者 sudo chown 用户名:组名 …