我们举一个摘水果的例子,果园中种着苹果树、香蕉树、桔子树等等果树,当有三个小伙伴想要吃苹果时,他们就需要各自去拿各自想要吃的水果,我们最原始朴素的写法如下,谁需要谁就自己去拿就好了:
/**
* 不使用设计模式:最原始的想法,哪里用就直接在哪里new出来相应的对象
*/
public class PeterClient {
//Peter自己吃水果
public static void main(String[] args) {
peterdo();
jamesdo();
lisondo();
}
//Peter自己吃水果
public static void peterdo() {
Fruit fruit = new Apple();
fruit.draw();
//。。。直接啃着吃,吃掉了
System.out.println("-----------------");
}
//送给james,切开吃
public static void jamesdo() {
Fruit fruit = new Apple();
fruit.draw();
//。。。切开吃
System.out.println("-----------------");
}
//送给lison榨汁喝
public static void lisondo() {
Fruit fruit = new Orange("peter", 100);
fruit.draw();
//。。。榨汁运作
System.out.println("-----------------");
}
}
上面代码可以看到,会有很多人到处自己去采摘new所需要的水果,然而我们的果园中的果子都是有一定数量的,此时若没有没有一个统一的生产水果的类去进行管理,那么果园将会混乱不堪。
下面我们就引出解决方案:
简单工厂模式
简单工厂模式(又叫作静态工厂方法模式),其属于创建型设计模式,但是并不属于 23种GoF设计模式之一。提到它是为了让大家能够更好地理解后面讲到的工厂方法模式。
定义:简单工厂模式属于创建型模式,其又被称为静态工厂方法模式,这是由一个工厂对象决定创建出哪一种产品类的实例。
和刚刚大家自己到果园中胡乱采摘果子不同,这里我们抽象出来了一个果园的工厂类,工厂负责各种果树的果子的采摘工作。
简单工厂模式的实现:
我们使用一个单独的类来管理,专门去果园采摘各种果子,简单工厂模式的结构图如图:
我们创建一个水果工厂类,它提供了一个静态方法 getFruit 用来生产各种水果。你只需要传入自己想要的水果种类,它就会实例化相应的水鬼对象,代码如下所示:
public class StaticFactory {
public static final int TYPE_APPLE = 1;//苹果
public static final int TYPE_ORANGE = 2;//桔子
public static final int TYPE_BANANA = 3;//香蕉
/**
* 调用方式一===========================================
*/
public static Fruit getFruit(int type) {
if (TYPE_APPLE == type) {
return new Apple();
} else if (TYPE_ORANGE == type) {
return new Orange("Peter", 80);
} else if (TYPE_BANANA == type) {
return new Banana();
}
return null;
}
/**
* 调用方式二===========================================
* 多方法工厂
*/
public static Fruit getFruitApple() {
return new Apple();
}
public static Fruit getFruitOrange() {
return new Orange("Peter", 80);
}
public static Fruit getFruitBanana() {
return new Banana();
}
}
具体使用方式如下:
public class StaticFactoryClient {
public static void main(String[] args) {
peterdo();
jamesdo();
lisondo();
}
//Peter获取香蕉方式一
public static void peterdo() {
Fruit fruit = StaticFactory.getFruit(StaticFactory.TYPE_BANANA);//调用方式一
fruit.draw();
//。。。直接啃着吃,吃掉了
System.out.println("-----------------");
}
//James获取香蕉方式二
public static void jamesdo() {
Fruit fruit = StaticFactory.getFruitBanana();//调用方式二
fruit.draw();
//。。。切开吃
System.out.println("-----------------");
}
//lison获取苹果
public static void lisondo() {
Fruit fruit = StaticFactory.getFruit(StaticFactory.TYPE_APPLE);
fruit.draw();
//。。。榨汁动作
System.out.println("-----------------");
}
}
上面的例子中,果园工厂负责了所有种类果子的采摘,上面我们只有三种水果:苹果,桔子,香蕉。但是随着果园不断发展,可能还会有西瓜、草莓、荔枝等等新的种类添加到果园中,也就是我们的静态工厂方法中会不断的需要更改,这样就违反了开闭原则,每次添加新的水果品种,都需要去修改下这个静态工厂类,添加更多的水果种类,这显然是不符合软件设计规则的。
因此,静态(简单)工厂设计模式,在比较简单的业务场景中使用是比较简洁高效的,但要是业务产生的种类较多,那么就显得不合时宜了。
如图所示,果园又新增了多个种类的水果:
工厂方法模式:
随着种植的水果品类不断增加,StaticFactory类不断修改,方法扩展极为庞大。水果品种的扩展方式不优雅。为了适应这类业务的发展,我们引出工厂方法模式,来解决上面的问题:
简单工厂模式中,我们把所有的生产任务都交由一个工厂去完成,一是任务重,二是每次新加种类都需要重新修改这个工厂。
为了解决上面的问题,我们想到可以将这一个大杂烩的工厂拆分成多个种类的小工厂,分门别类的小工厂去生产各自负责的任务就好了。
如图所示,当我们需要再新增水果种类时,再添加一个西瓜、草莓、荔枝的工厂即可。
工厂方法模式定义:定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。简单来说就是搞一个工厂类的接口,下面有很多不同的小工厂瓜分了原来静态工厂的职责,后面具体谁需要用到哪个小工厂自己去new出来这个小工厂的实现即可。
如图所示,我们定义一个FruitFactory的工厂接口,下面有苹果工厂、香蕉工厂、桔子工厂这三个实现类,工厂类结构与产品类结构一一对应,每一种产品都对应一个工厂子类。当新增一个产品类型时,新加对应的工厂子类即可,不再需要修改既有类。对应的代码:
工厂接口:
/**
* 工厂方法接口
*/
public interface FruitFactory {
public Fruit getFruit();//摘水果指令
}
三个对应的水果工厂:
苹果工厂:
/**
* 苹果工厂方法模式
*/
public class AppleFactory implements FruitFactory{
public Fruit getFruit(){
return new Apple();
}
}
香蕉工厂:
/**
* 香蕉工厂方法模式
*/
public class BananaFactory implements FruitFactory{
public Fruit getFruit(){
return new Banana();
}
}
桔子工厂:
/**
* 橘子工厂方法模式
*/
public class OrangeFactory implements FruitFactory{
public Fruit getFruit(){
return new Orange("Peter",80);
}
}
工厂方法测试类:
/**
* 工厂方法模式测试
*/
public class FactoryTest {
private static FruitFactory fruitFactory;
public static void main(String[] args) {
//初始化苹果工厂
fruitFactory = new AppleFactory();//spring配置
peterdo();
jamesdo();
lisondo();
}
//Peter自己吃水果
public static void peterdo() {
Fruit fruit = fruitFactory.getFruit();
fruit.draw();
System.out.println("-----------------");
}
//james吃
public static void jamesdo() {
Fruit fruit = fruitFactory.getFruit();
fruit.draw();
System.out.println("-----------------");
}
//lison吃
public static void lisondo() {
Fruit fruit = fruitFactory.getFruit();
fruit.draw();
System.out.println("-----------------");
}
}
因为这里使用的是苹果工厂,最终打印出来的如下:
总结工厂方法模式:将静态工厂的任务拆分到了不同的各个工厂中,当需要更多的商品种类时,我们不断扩充小工厂即可。
我们继续往下看这个场景,为了果园的发展,我们想把成熟的新鲜水果包装起来,产生更多的附加值,为果园增收,因此开起了水果店,当然这时我们需要给各种果子打上包装盒。
和上面的水果工厂功能类似,我们有新添加了水果包装的工厂,如下图所示,
水果工厂和水果包装工厂,需要相互配合才能完成产品的生产。就是苹果工厂生产的苹果需要和苹果包装工厂生产的包装盒相匹配,最终做出苹果礼盒。但是这里有一个问题,就是工人们有可能会把苹果错放到了香蕉盒、或者错放到了桔子盒中,造成顾客买的香蕉礼盒,而里面都是苹果的尴尬场面。代码如下:
/**
* 水果店测试:
* 使用工厂方法测试类,会产生打包错误的情况
*/
public class FruitStoreTest {
private static FruitFactory fruitFactory;
private static BagFactory bagFactory;
public static void main(String[] args) {
pack();
}
private static void pack() {
//初始化苹果工厂
fruitFactory = new AppleFactory();
Fruit fruit = fruitFactory.getFruit();
fruit.draw();
//错误获取到了香蕉的包装
bagFactory = new BananaBagFactory();
Bag bag = bagFactory.getBag();
//错误的把苹果放到了香蕉的包装中
bag.pack();
}
}
我们可以看到,苹果工厂生产的苹果,由于错误获取到了香蕉的包装盒,因此将苹果放到了香蕉礼盒中,最终导致问题的产生,那么如何避免这类问题的产生呢?
因此为了避免这种场景的出现,我们很自然会想到,是否可以将生产苹果工厂和苹果包装工厂进行合并,也就是说一个苹果工厂就完成了苹果采摘和苹果包装的两份工作呢。
下面我们利用抽象工厂来解决上面的问题:
对应的类图:
为了避免苹果工厂生产的苹果错误放到了其他工厂生产的包装盒中,我们可以将苹果工厂和苹果包装工厂的任务统一到一个新的苹果工厂中去。如下改写:
抽象工厂:
/**
* 抽象水果工厂
*/
public abstract class AbstractFactory {
public abstract Fruit getFruit();
public abstract Bag getBag();
}
新苹果工厂:
/**
* 新苹果工厂
*/
public class AppleFactory extends AbstractFactory{
@Override
public Fruit getFruit() {
return new Apple();
}
@Override
public Bag getBag() {
return new AppleBag();
}
}
新香蕉工厂:
/**
* 新香蕉工厂
*/
public class BananaFactory extends AbstractFactory{
@Override
public Fruit getFruit() {
return new Banana();
}
@Override
public Bag getBag() {
return new BananaBag();
}
}
新桔子工厂:
/**
* 新桔子工厂
*/
public class OrangeFactory extends AbstractFactory{
@Override
public Fruit getFruit() {
return new Orange("Peter",50);
}
@Override
public Bag getBag() {
return new OrangeBag();
}
}
抽象工厂测试类:
/**
* 抽象工厂模式测试
* 按订单发送货品给客户
*/
public class OrderSendClient {
public static void main(String[] args) {
sendFruit();
}
public static void sendFruit() {
//初始化工厂
AbstractFactory factory = new AppleFactory();//spring使用注入方式
//得到水果
Fruit fruit = factory.getFruit();
fruit.draw();
//得到包装
Bag bag = factory.getBag();
bag.pack();
//以下物流运输业务。。。。
}
}
测试结果:
总结,抽象工厂就是把原来有配合的工厂方法的工厂组合起来,以防止配合上的出错。