在前面的学习中(参见前面的博客),我们学到了很多OO原则:

  • 封装变化
  • 多用组合,少用继承
  • 针对接口/超类编程,不针对实现编程
  • 松耦合
  • 开闭原则

让我们从一个简单的类开始,看看如何将之改造成符合OO原则的类以及工厂模式在解耦中的威力。

class FoodStore {
	public Food  orderFood()   //通过此方法顾客从食物商店中得到食物
	{
		 Food food=new Food();
		 food.prepare();    // 准备食物
		 food.cut();        // 将食物切好
		 food.box();  // 包装好
		 return food;
	}
}

这样写还不够,食物店里又不是只有一种food,我们要让食物店提供更多!

class FoodStore {
	public Food orderFood(String type) {
		Food food = null;
		if (type.equals("bread")) {
			food = new bread();
		}
		else if(type.endsWith("chicken"))
		{
			food=new chicken();
		}
		food.prepare();
		food.cut();
		food.box();
		return food;
	}
}

这样我们让食物店提供了面包和鸡肉,他们分别是Food的子类,那我们要在添加一种食物(pizza)呢?这就又需要修改FoodStore的代码!这可不符合开闭原则!但是,从上面两个不同版本的类来看,可以分析出类中变化的部分:食物类型!

既然我们已经找到了变化的部分,根据封装变化的原则,就把这部分(对象的创建)独立开来。

class SimpleFactory {
	 public Food creatFood(String type)
	 {
	  Food food = null;
	  if (type.equals("bread")) {
		food = new bread();
	  }
	  else if(type.endsWith("chicken"))
	  {
		food=new chicken();
	  }
	  return food;
	 }
}

这样,我们就完成了今天第一个“模式”——简单工厂,在这个过程中,其实我们只做了一件事:将foodstore中创建对象的部分与其隔离开(其实工厂模式的目的就是封装对象的创建),即:封装变化

下面是封装之后的FoodStore:

class FoodStore {
	SimpleFactory factory;
	public FoodStore(SimpleFactory factory)
	{
		this.factory=factory;
	}
	public Food orderFood(String type) {
		Food food = factory.creatFood(type);
		food.prepare();
		food.cut();
		food.box();
		return food;
	}
}

由于创建对象的过程交给了工厂,所以如果再添加食物类别,只需要修改SimpleFactory类,这样FoodStore就符合了对修改关闭原则!
简单工厂已经很厉害了!但是还不足以应付所有的情况,让我们看看工厂方法和抽象工厂的表现。

问题1:假如有很多商店都向简单工厂去取对象,那么所有商店取得的面包对象和鸡肉对象都是一样的!我们想让不同商店里取得不同口味的食物!

问题2:假如一个商店想要出售两种风味的鸡肉,简单工厂并不能很好的实现。

对于这两个问题,虽然可以在简单工厂中加入多种xxxbread和xxxchicken 来解决,但是会产生很多问题!

分析: 对于问题一,假设第n个商店分别需要n_bread和n_chicken,那么你需要在简单工厂里加入2*n个条件语句,但是,这2*n个语句里,其实只有2个是对其对应的商店有效的,其他的都是累赘!

对于问题二, 假如有n种食物,m种口味,那就是m*n个条件语句,对某个商店来说,有用的其实只有m1*n1个,仍然是有很多冗余代码!

细心的读者会发现,问题一可以看做问题二的子问题,并且都指向了代码的复用性!虽然简单工厂可以复用,但效率太低了!

针对问题一,我们可以将那个具有2*n个条件语句的简单工厂分成n个工厂,与商店一一对应,这样解决了效率问题(不同的商店调用不同的工厂),但是,每个工厂类其实只为对应的商店服务,也就是说,每个工厂类都不需要复用却被我们独立开来!在前文提到过,工厂模式的目的是将对象的创建进行封装,那么能不能将这种封装放在“特殊”的地方呢?当然能!工厂方法模式就是将其放到子类上!

工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

方法就是将对象创建部分声明为抽象方法,交给子类去实现,如下:

abstract class Store {
	public Food orderFood(String type) {
		Food food = creatFood(type);
		food.prepare();
		food.cut();
		food.box();
		return food;
	}
	public abstract Food creatFood(String type); //抽象方法,父类并不知道具体的对象类型,这将由子类决定
}

工厂方法模式相比简单工厂更具有弹性,可以变更正在创建的产品,但是它也放弃了复用性!那怎么能够既保有弹性又能实现复用呢(这正是问题二所面临的问题)?

不用多说,这个自然就是我们的抽象工厂模式,先看看定义吧!

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

如下:

interface abstract_Factory {
  public Food creatBread();
  public Food creatChicken();
}

做两个实现类,如河北工厂和河南工厂,分别提供河北风味的面包,鸡肉和河南风味的面包,鸡肉,如下:

河北工厂
——————————————

class Hebei_Factory implements abstract_Factory {

	@Override
	public Food creatBread() {
		// TODO Auto-generated method stub

		return new Hebei_bread();
	}

	@Override
	public Food creatChicken() {
		// TODO Auto-generated method stub
		return new Hebei_chicken();
	}
}

河南工厂
——————————————

class Henan_Factory implements abstract_Factory {

	@Override
	public Food creatBread() {
		// TODO Auto-generated method stub
		return new Henan_bread();
	}

	@Override
	public Food creatChicken() {
		// TODO Auto-generated method stub
		return new Henan_chicken();
	}

}

相应的商店及运行代码
——————————————

class abstract_Factory_Story {

	Food food;

	public Food orderFood(String type,abstract_Factory factory) {
		if("bread".equals(type)){
			food = factory.creatBread();
		}
		else if("chicken".equals(type))
		{
			food = factory.creatChicken();
		}
		food.prepare();
		food.cut();
		food.box();
		return food;
	}
	public static void main(String[] args)
	{
		abstract_Factory_Story afs=new abstract_Factory_Story();
		afs.orderFood("bread", new Hebei_Factory());
		afs.orderFood("chicken", new Henan_Factory());
	}
}

运行结果
——————————————

Hebei_bread:prepare............
Hebei_bread:cutting............
Hebei_bread:boxed............
Henan_chicken:prepare............
Henan_chicken:cutting............
Henan_chicken:boxed............

可以看出,运行时我们只要传入不同的工厂,就能得到不同风味的食物,各个工厂子类也能复用,在商店类中并不依赖具体类型(food是所有食物的超类),能够轻易的进行扩展而不用修改代码(type的判断放在工厂中会更好)。

总结

纵览三种设计模式,可以发现简单工厂和工厂方法都可以看做抽象工厂的子模式,抽象工厂本身就是工厂方法组合而成(将对象的创建延迟到子工厂),而相应的每个子工厂,也都可以看做是一个简单工厂,只不过在抽象工厂里,运用了面对接口/超类编程的方法将商店类与子工厂具体类型解耦,使之更具有弹性。在很多情况下,简单工厂和工厂方法都能很好的替代抽象工厂,例如,在不需要复用的对象创建封装时,工厂方法比抽象工厂更合适,在创建对象的类型相对确定时(不需要弹性),用简单工厂也能胜任。

Java设计模式之简单工厂、工厂方法和抽象工厂的更多相关文章

  1. Java设计模式---工厂模式(简单工厂、工厂方法、抽象工厂)

    工厂模式:主要用来实例化有共同接口的类,工厂模式可以动态决定应该实例化那一个类.工厂模式的形态工厂模式主要用一下几种形态:1:简单工厂(Simple Factory).2:工厂方法(Factory M ...

  2. 设计模式3---工厂模式(Factory Pattern简单工厂、工厂方法、抽象工厂)

    工厂模式:主要用来实例化有共同接口的类,工厂模式可以动态决定应该实例化那一个类.工厂模式的形态工厂模式主要用一下几种形态:1:简单工厂(Simple Factory).2:工厂方法(Factory M ...

  3. 设计模式(Python)-简单工厂,工厂方法和抽象工厂模式

    本系列文章是希望将软件项目中最常见的设计模式用通俗易懂的语言来讲解清楚,并通过Python来实现,每个设计模式都是围绕如下三个问题: 为什么?即为什么要使用这个设计模式,在使用这个模式之前存在什么样的 ...

  4. Java设计模式—工厂方法模式&抽象工厂模式

    工厂方法模式与抽象工厂模式都是设计模式中重要而且常见的模式.       工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类.工厂方法使一个类的实例化延迟到其子类. 通用类图如下: 在 ...

  5. c# 设计模式 之:简单工厂、工厂方法、抽象工厂之小结、区别

    很多时候,我发现这三种设计模式难以区分,常常会张冠李戴闹了笑话.很有必要深入总结一下三种设计模式的特点.相同之处和不同之处. 1 本质 三个设计模式名字中都含有“工厂”二字,其含义是使用工厂(一个或一 ...

  6. Java设计模式(2)——创建型模式之工厂方法模式(Factory Method)

    一.概述 上一节[简单工厂模式]介绍了通过工厂创建对象以及简单的利弊分析:这一节来看看工厂方法模式对类的创建 工厂方法模式: 工厂方法与简单工厂的不同,主要体现在简单工厂的缺点的改进: 工厂类不再负责 ...

  7. 结合JDK源码看设计模式——简单工厂、工厂方法、抽象工厂

    三种工厂模式的详解: 简单工厂模式: 适用场景:工厂类负责创建的对象较少,客户端只关心传入工厂类的参数,对于如何创建对象的逻辑不关心 缺点:如果要新加产品,就需要修改工厂类的判断逻辑,违背软件设计中的 ...

  8. 结合实例分析简单工厂模式&工厂方法模式&抽象工厂模式的区别

    之前写过一篇关于工厂模式(Factory Pattern)的随笔,里面分析了简单工厂模式,但对于工厂方法和抽象工厂的分析较为简略.这里重新分析分析三者的区别,工厂模式是java设计模式中比较简单的一个 ...

  9. java之设计模式工厂三兄弟之抽象工厂模式

    [学习难度:★★★★☆,使用频率:★★★★★]  工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致系统中存在大量的工 ...

随机推荐

  1. 支持向量机通俗导论(理解SVM的三层境地)

    支持向量机通俗导论(理解SVM的三层境地) 作者:July :致谢:pluskid.白石.JerryLead.出处:结构之法算法之道blog. 前言 动笔写这个支持向量机(support vector ...

  2. Linux设备驱动中断机制

    [主要内容] Linux设备驱动编程中的中断与定时器处理 [正文] 一.基础知识 1.中断 所谓中断是指CPU在执行程序的过程中,出现了某些突发事件急待处理,CPU必须暂停执行当前的程序,转去处理突发 ...

  3. MySQL-视频跟随练习笔记

    在表中添加某列 alter table tbl_user add email varchar(50); 在表中删除某列 alter table tbl_user drop email; 重命名某列 a ...

  4. 3_Linux_文件搜索指令

    .3文件搜索命令 1)which 查找一个命令所在的路径 whereis 提供命令的帮助文件的信息 whatis 显示命令的概要信息whatis ls which提供命令的别名信息 2)find,基本 ...

  5. ASP.Net中的编码与解码

    当javascript传递的参数中有中文时,服务端获得的将是乱码,此时需要用到编码和解码 javascript中编码与解码的三种方法 escape方法返回一个可在所有计算机上读取的编码 String ...

  6. Swift - 38 - 枚举的基本语法

    //: Playground - noun: a place where people can play import UIKit // 创建一个枚举类型就相当于创建了一个新的数据类型, 所以首字母应 ...

  7. mysql文件导入到数据库load data infile into table 的使用例子

    load data infile "C:/Users/Administrator/Desktop/1.txt"into table 要一个已经存的表名 字段默认用制表符隔开 文件 ...

  8. 多线程11-Semaphore信号灯

    1.概念 Semaphore可以维护当前访问自身的线程个数,并提供了同步机制.使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数 2.案例 package org ...

  9. phpcms v9 分页

    phpcms的分页很简单,只需在需要分页的地方写入如下代码即可: <div id="pages">{$pages}</div> 连样式都有了,如果你是调用的 ...

  10. MySQL添加中文字符集问题 --- java.sql.SQLException: Incorrect string value

    今天在做开关记录的时候,数据库表中description字段用于记录当前版本的描述(需要存储中文),在测试程序的时候发现如果用户输入中文,那么后台会报错.错误信息如下: java.sql.SQLExc ...