《Head First设计模式》第四章笔记 工厂模式

之前我们一直在使用new操作符,但是实例化这种行为并不应该总是公开的进行,而且初始化经常会造成耦合问题,工厂模式将摆脱这种复杂的依赖,本次内容包括简单工厂,工厂方法和抽象工厂三种情况。

1

2

3

4

5

6

Duck duck;

if(a){

    duck=new Duck1();

}else{

    duck=new Duck2();

}

如上面代码中使用new实例化一个类时,使用的是实现,而不是接口,代码捆绑着具体类会导致代码更脆弱缺乏弹性,后续的维护、修改等操作容易出错。

使用new操作符会造成的问题:

1.如果你针对接口编程,你可以利用多态,实现该接口并没有太大问题
2.但是如果代码中使用了很多具体类,一旦加入新的类,就必须改变代码,这就违反了开放-关闭原则。

项目举例

假设你有一家 pizza 店,你有很多种 pizza,要在系统中显示你所有pizza种类。
实现这个功能并不难,

使用普通方式实现:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public class PizzaStore {

    Pizza orderPizza(String type) {

        Pizza pizza = null;

        if (type.equals("cheese")) {

            pizza = new CheesePizza();

        else if (type.equals("clam")) {

            pizza = new ClamPizza();

        else if (type.equals("veggie")) {

            pizza = new VeggiePizza();

        }

        pizza.prepare();

        pizza.bake();

        pizza.cut();

        pizza.box();

        return pizza;

    }

}

但是如果新上市一种pizza或者下架一种pizza,你就需要修改这段代码,这样就没有做到对修改关闭。

简单工厂:

于是我们使用之前学到的知识,找到变化的代码和不变的代码,进行封装了:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

public class SimpleFactory {

    public Pizza createPizza(String type) {

        Pizza pizza = null;

        if (type.equals("cheese")) {

            pizza = new CheesePizza();

        else if (type.equals("clam")) {

            pizza = new ClamPizza();

        else if (type.equals("veggie")) {

            pizza = new VeggiePizza();

        }

        return pizza;

    }

}

 

public class PizzaStore {

    SimpleFactory simpleFactory;

    public PizzaStore(SimpleFactory simpleFactory) {

        this.simpleFactory = simpleFactory;

    }

    Pizza orderPizza(String type) {

        Pizza pizza = null;

        pizza = simpleFactory.createPizza(type);

        pizza.prepare();

        pizza.bake();

        pizza.cut();

        pizza.box();

        return pizza;

    }

}

这么做的好处看起来好像只是把原来的代码放进到另外一个类中,但是当以后出现变化时,自需要修改这个类,就可以,并且这样做的好处还在于可能现在只有客人点餐才会用到这段代码,如果可以点外卖的话,仍然可以用这段代码,代码得到了复用


上面的简单工厂并不是一个真正的模式,只是一种编程习惯,这个不能算工厂模式,不过也算是基本满足需求。

工厂方法模式:

背景更新:假如现在你要开分店,各种加盟商进来后,他们都要开发符合本地口味的pizza,那就需要各个地方都有一个工厂,也就是每个地方继承SimpleFactory类,但是每个工厂并不是完全使用你原来的烘培方法。

工厂方法模式类图如下:

对应代码·:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

public abstract class PizzaStore {

    Pizza orderPizza(String type) {

        Pizza pizza = null;

        pizza = createPizza(type);

        pizza.prepare();

        pizza.bake();

        pizza.cut();

        pizza.box();

        return pizza;

    }

    protected abstract Pizza createPizza(String type);

}

 

public class NYPizzaStore extends PizzaStore {

    @Override

    protected Pizza createPizza(String type) {

        if (type.equals("cheese")) {

            return new NYCheesePizza();

        }

    return null;

    }

}

 

public class ChicagoPizzaStore extends PizzaStore {

    @Override

    protected Pizza createPizza(String type) {

        if (type.equals("cheese")) {

            return new ChicagoCheesePizza();

        }

    return null;

    }

}

现在订购这些订单时,自需要实例化各自风格的工厂,就算都是芝士披萨,也是来自不同商店口味的。

 

 

总结:

  • 工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的是哪一个,工厂方法让类把实例化推迟到子类,工厂方法模式的优点在于帮助产品从使用中分离,从使用中解耦。
  • 和简单工厂的区别,两者类似,但是在于createPizza方法,工厂方法让每个上篇自行负责,而简单工厂使用的则是PizzaStore对象。
  • 简单工厂把所有的事情在一个地方干完了,然而工厂方法则是写了一个框架,让子类具体实现,PizzaStore作为工厂的orderPizza方法提供了一个一般的框架,以便创建披萨,其具体依赖工厂的方法来创建具体的披萨。
  • 两者都实现了不让创建对象的代码到处乱跑。

看看对象依赖情况,原来的PizzaStore依赖于所有的Pizza对象,当这些对象发生改变时,可能会影响到PizzaStore,PizzaStore时依赖具体类的,PizzaStore就是高层组件,Pizza各种对象就是低层组件,但是设计原则有一条是:依赖抽象,不要依赖具体类
而使用工厂方法之后就不在出现这种依赖很多具体类的情况了。

 

现在背景有变,有些加盟店,使用低价原料来增加利润,你必须采取一些手段,以免毁掉你的披萨店品牌。
你打算建造一家成产原料的工厂,并将原料运送到各家加盟店,那么剩下最后一个问题,不同的区域原料是不一样的,对于两个加盟店给出了两组不同的原料。

 

类图:

 

建造原料工厂
我们要建造一个工厂来生产原料,这个工厂负责创建原料家族中的每一种原料。

1

2

3

4

5

6

7

8

public interface PizzaIngredientFactory {

    public Dough createDough();

    public Sauce createSauce();

    public Cheese createCheese();

    public Veggies[] createVeggies();

    public Pepperoni createPepperoni();

    public Clams createClams();

}

要做的事情是:
1.为每个区域建造一个工厂,你需要创建一个继承自PizzaIngredientFactory的子类来实现每一个创建方法。
2.实现一组原料类供工厂使用,例如RegianoCheese,RedPeppers,ThickCrustDough.这些类可以在何时的区域间共享。
3.然后你仍然需要将这一切组织起来,将新的原料工厂整合进旧的PizzaStore代码中。

创建纽约的原料工厂

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

class NYPizzaIngredientFactory implements PizzaIngredientFactory{

    @Override

    public Dough createDough() {

        return null;

    }

    @Override

    public Sauce createSauce() {

        return null;

    }

    @Override

    public Cheese createCheese() {

        return null;

    }

    @Override

    public Veggies[] createVeggies() {

        return new Veggies[0];

    }

    @Override

    public Pepperoni createPepperoni() {

        return null;

    }

    @Override

    public Clams createClams() {

        return null;

    }

}

重做披萨

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

abstract class Pizza{

    String name;

    Dough dough;

    Sauce sauce;

    Veggies veggies[];

    Cheese cheese;

    Pepperoni pepperoni;

    Clams clams;

    abstract void prepare();

    void cut(){

        System.out.println("Cutting the pizza into diagonal slices");

    }

    void box(){

        System.out.println("Place pizza in official PizzaStore box");

    }

    void setName(String name){

        this.name=name;

    }

    public String getName(){

        return name;

    }

    public void toString2(){

        //这里打印披萨的代码

    }

}

继续重做披萨

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

public class CheesePizza extends Pizza {

    //这里组合了一个PizzaIngredientFactory对象的引用,用于提供不同的原料

    PizzaIngredientFactory ingredientFactory;

  

    /**

     * 通过传入一个PizzaIngredientFactory原料工厂,我们可以在制作Pizza的时候动态的产生所需要的原料

     * @param ingredientFactory

     */

     

    public CheesePizza(PizzaIngredientFactory ingredientFactory) {

        this.ingredientFactory = ingredientFactory;

    }

  

    void prepare() {

        System.out.println("Preparing " + name);

        dough = ingredientFactory.createDough();

        sauce = ingredientFactory.createSauce();

        cheese = ingredientFactory.createCheese();

    }

}

再回到披萨店

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

public class NYPizzaStore extends PizzaStore {

  

    protected 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");

  

        else if (item.equals("clam")) {

  

            pizza = new ClamPizza(ingredientFactory);

            pizza.setName("New York Style Clam Pizza");

  

        else if (item.equals("pepperoni")) {

            pizza = new PepperoniPizza(ingredientFactory);

            pizza.setName("New York Style Pepperoni Pizza");

  

        

        return pizza;

    }

}

我们做了些什么?
我们引入新类型的工厂,也就是所谓的抽象工厂,来创建披萨原料家族。
通过抽象工厂所提供的接口可以创建产品的家族,利用这个接口书写代码,我们的代码将从实际工厂解耦,以便在不同上下文中实现各式各样的工厂,制造出各种不同的产品。

定义抽象工厂模式

抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

 

工厂方法与抽象工厂

抽象工厂的每个方法都实际上看起来像工厂方法,方法声明成抽象,子类方法去覆盖这些方法去创建对象。
抽象工厂的任务是定义一个负责创造一组产品的接口,每个方法就是创建一种产品,这就是工厂方法,所以在抽象工厂中利用工厂方法来实现生产方法是自然的。

对比:

  • 两者都是用来创建对象,但是工厂方法使用的是继承,而抽象工厂使用的是对象的组合。
  • 比如工厂方法创建pizza是各种PizzaStore继承后覆盖createPizza()方法实现的,让客户从具体类型中解耦。
  • 但是对于抽象工厂,提供了一个用来创建一个产品家族的抽象类型,这个类型的子类定义了产品被产生的方法,要想使用这个工厂必须实例化它,然后将它传入一些针对抽象类型所写的代码中。
  • 比如PizzaIngredientFactory这个接口定义了一堆原料的做法,而对于纽约的原料加工厂先实现这些接口,然后纽约的商店实例化这个原料加工厂,就保证了不同的商店使用到各自不同的原料,把客户从所使用的实际产品中解耦。
  • 一群还是一个,一群产品集合用抽象工厂,具体哪些类中可以确定一个抽象类,子类继承实现使用工厂方法

 

 

设计原则:依赖倒置

依赖倒置原则:要依赖抽象,不要依赖具体类。高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。

用依赖倒置原则重构代码

下面是一些指导方针来避免违反依赖倒置原则:

  • 变量不可以持有具体类的引用。如果使用new,就会持有具体类的引用,你可以改成工厂来避免
  • 不要让类派生自具体类,如果派生自具体类,你就会依赖具体类,请派生自抽象(接口或者抽象类)
  • 不要覆盖基类中已经实现的方法,如果覆盖,那么基类说明就不是一个真正适合被继承的抽象,基类中已经实现的方法,应该有所有的子类共享

要尽量达到这些原则,但不是随时都要遵守

 

总结

工厂:封装对象的创建,处理创建对象的细节

静态工厂:利用静态方法定义一个简单的工厂。优点:不需要创建工厂类的实例化。缺点:不能通过继承改变创建方法行为。

简单工厂:简单工厂并不是一种设计模式,因为只是简单的把创建对象的代码封装起来

工厂模式:在父类定义了一个创建对象的接口,通过让子类决定创建的对象是什么,来达到让对象创建的过程封装的目的。工厂方法让类把实例化推迟到子类

抽象工厂:提供一个接口,用于创建相关或依赖对象的家族,而不需要指明具体的类

  • 工厂方法用来处理对象的创建,并将这样的行为封装在子类中。这样客户程序中超类的代码就和子类对象的创建部分解耦了
  • 简单工厂vs工厂模式:简单工厂把全部的事情在一个地方做完了,而工厂模式是一个创建框架,让子类决定如何实现
  • 抽象工厂vs工厂模式
    • 抽象工厂的方法经常以工厂方法的方式实现,抽象工厂的任务是定义一个负责创建一组产品的接口
    • 工厂方法使用继承,抽象工厂使用组合
    • 工厂方法只是用来创建一种产品,而抽象工厂创建的是一个产品家族
    • 使用工厂模式意味着需要扩展一个类并覆盖它的工厂方法。抽象工厂提供了一个创建产品家族的抽象类型,类型的子类定义了产品生产的方式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/445426.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《Head First设计模式》第五章笔记-单件模式

单件模式 定义:确保一个类只有一个实例,并提供全局访问点。 编写格式: 1 2 3 4 5 6 public class MyClass{ private MyClass(){}//构造方法私有化 public static MyClass getInstance(){ //提供全局访问点 return new My…

《Head First设计模式》第六章笔记-命令模式

封装调用-命令模式 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦。 本篇中将不再描述书中所引入的“巴斯特家电自动化公司”的遥控器控制案例,而使用简单易懂的餐厅案例。 在开始之前,让我们通过一个现实中的例子来了解命令模式。 理解…

一文读懂机器学习库graphLab

文章目录目录什么是graphlab为什么使用graphlab?如何安装graphlab?graphlab的简单使用。目录 什么是graphlab GraphLab 是由CMU(卡内基梅隆大学)的Select 实验室在2010 年提出的一个基于图像处理模型的开源图计算框架,框架使用C语言开发实…

《Head First设计模式》第七章-适配器模式、外观模式

适配器模式 适配器模式是什么,你一定不难理解,因为现实中到处都是。比如说: 如果你需要在欧洲国家使用美国制造的笔记本电脑,你可能需要使用一个交流电的适配器…… 当你不想改变现有的代码,解决接口不适配问题&#…

《Head First设计模式》第八章笔记-模板方法模式

模板方法模式 之前所学习的模式都是围绕着封装进行,如对象创建、方法调用、复杂接口的封装等,这次的模板方法模式将深入封装算法块,好让子类可以在任何时候都将自己挂接进运算里。 模板方法定义:模板方法模式在一个方法中定义一…

机器学习基础-吴恩达-coursera-(第一周学习笔记)----Introduction and Linear Regression

课程网址:https://www.coursera.org/learn/machine-learning Week 1 —— Introduction and Linear Regression 目录 Week 1 Introduction and Linear Regression目录一 介绍1-1 机器学习概念及应用1-2 机器学习分类 二 单变量的线性回归2-1 假设函数hypothesis2…

常见8种机器学习算法总结

简介 机器学习算法太多了,分类、回归、聚类、推荐、图像识别领域等等,要想找到一个合适算法真的不容易,所以在实际应用中,我们一般都是采用启发式学习方式来实验。通常最开始我们都会选择大家普遍认同的算法,诸如SVM&a…

redis——数据结构(字典、链表、字符串)

1 字符串 redis并未使用传统的c语言字符串表示,它自己构建了一种简单的动态字符串抽象类型。 在redis里,c语言字符串只会作为字符串字面量出现,用在无需修改的地方。 当需要一个可以被修改的字符串时,redis就会使用自己实现的S…

Hotspot虚拟机的对象

创建 Step1:类加载检查 虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。 Step2:分…

redis——数据结构(整数集合,压缩列表)

4、整数集合 整数集合(intset)是 Redis 用于保存整数值的集合抽象数据结构, 可以保存 int16_t 、 int32_t 、 int64_t 的整数值, 并且保证集合中不会出现重复元素。 实现较为简单: typedef struct intset {// 编码方…

机器学习知识总结系列- 知识图谱(0-0)

文章目录目录机器学习知识图谱目录 本系列的文章只是根据个人的习惯进行总结,可能结构与一些书籍上不太一样,开始的内容比较简单,会随着后续的深入,不断丰富和更新图谱,同时也期待有相同兴趣的朋友一起给我留言一起丰富…

跳表介绍和实现

想慢慢的给大家自然的引入跳表。 想想,我们 1)在有序数列里搜索一个数 2)或者把一个数插入到正确的位置 都怎么做? 很简单吧 对于第一个操作,我们可以一个一个比较,在数组中我们可以二分,这…

机器学习知识总结系列- 基本概念(1-0)

文章目录目录1. 机器学习的定义2. 机器学习的分类2.1根据是否在人类监督下进行训练监督学习非监督学习半监督学习强化学习2.2根据是否可以动态渐进的学习在线学习批量学习2.3根据是否在训练数据过程中进行模式识别实例学习基于模型的学习3. 机器学习中的一些常见名词4. 机器学习…

剑指offer(刷题21-30)--c++,Python版本

文章目录目录第 21题:解题思路:代码实现:cpython第22 题:解题思路:代码实现:cpython第23 题:解题思路:代码实现:cpython第24 题:解题思路:代码实现…

剑指offer(刷题41-50)--c++,Python版本

文章目录目录第41题:解题思路:代码实现:cpython第42题:解题思路:代码实现:cpython第43题:解题思路:代码实现:cpython第44题:解题思路:代码实现&am…

redis——持久化

因为redis是内存数据库,他把数据都存在内存里,所以要想办法实现持久化功能。 RDB RDB持久化可以手动执行,也可以配置定期执行,可以把某个时间的数据状态保存到RDB文件中,反之,我们可以用RDB文件还原数据库…

剑指offer(刷题51-60)--c++,Python版本

文章目录目录第51题:解题思路:代码实现:cpython第52题:解题思路:代码实现:cpython第53题:解题思路:代码实现:cpython第54题:解题思路:代码实现&am…

2017第一届河北省大学生程序设计竞赛题解

超级密码 小明今年9岁了,最近迷上了设计密码!今天,他又设计了一套他认为很复杂的密码,并且称之为“超级密码”. 说实话,这套所谓的“超级密码”其实并不难:对于一个给定的字符串,你只要提取其中…

大数的四则运算(加法、减法、乘法、除法)

大数的四则运算(加法、减法、乘法、除法) 前言: 在计算机中数字表示的范围是有限制的,比如我们熟知的 int、float、double 等数据类型所能表示的范围都是有限的,如果我们要对位数达到几十位、几百位、上千位的大整数进…