设计模式——04 工厂模式

前言:工厂模式是为了解决new的问题

案例分析:

         REQ1Vander作为pizza店的老板,具有一整套制作pizza的流程,准备食材、烘焙、切片、包装,随着pizza种类的渐渐增加,设计如下:

public class PizzaStore {

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

分析随着pizza店的发展,后期发现要制作越来越多的pizza,而且种类也是超级多,多达几十种pizza,而且有些旧类型的pizza,几乎没人点,greek和pepperoni pizza就从菜单上撤离了,而增加了FruitsPizza、HawaiiPizza、DurianPizza,所以上面orderPizza()方法就会频繁地改动,经常改动导致pizza店的订单系统经常处在维护状态,这非常不利于接单,很明显,实例化某些具体类,将使得orderPizza()方法出问题,而且这违反了“开放-关闭原则”,这个方法就无法对修改关闭了;但是现在已经知道哪些是会改变的,哪些是不会改变的部分,接下来用封装来解决上面的问题。

解决方法1:Vander 又开始改进设计了

    将创建pizza的代码封装起来转移到另一个对象中,然后orderPizza()方法专门负责pizza的制作工作。new pizza这件事专门交给其他类去负责。

重新修改PizzaStore的代码:

public class PizzaStore {

	public SimplePizzaFactory simplePizzaFactory;
	
	public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
		this.simplePizzaFactory = simplePizzaFactory;
	}
	
	public Pizza orderPizza(String type) {
		Pizza pizza = simplePizzaFactory.producePizza(type);
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		
		return pizza;
	}
	
}

简单工厂的代码:

public class SimplePizzaFactory {

	public Pizza producePizza(String type) {
		Pizza pizza = new Pizza();
		
		if(type.equals("cheese")) {
			pizza = new CheesePizza();
		} else if(type.equals("greek")) {
			pizza = new GreekPizza();
		} else if(type.equals("pepperoni")) {
			pizza = new Pepperoni();
		}
		return pizza;
	}
	
}

现在相当于,每次修改SimplePizzaFactory的代码的时候,可以暂时不对线上的版本进行影响,修改完SimplePizzaFactory之后再替换掉线上的SimplePizzaFactory就OK了。而orderPizza()方法相当于变成了SimplePizzaFactory的使用者,它使用pizza工厂来完成pizza的new操作。

下面定义简单工厂,实际上简单工厂不算做一个设计模式,更多地是一种编程习惯,我们会经常用到它,所以也是非常重要的。

 

REQ2:Vander的Pizza店名气非常大,吸引了很多大腕来加盟,但是由于区域的差异,每个加盟店都可能需要作出不同风格的pizza,例如四川的希望水果Pizza里面有辣椒,广东的则希望水果Pizza里面多一点菠萝,这受到了开店地区当地人口味的影响。

   首先要完成加盟店,所以PizzaStore的SimpleFactory就成了一些地区PizzaFactory,如GuangDongPizzaFactory、SiChuanPizzaFactory。然后他们各自使用各自的Pizza类,例如广东的创造广东类型的Pizza-GDPizza;四川的创作自己类型的Pizza-SCPizza,然后他们各自再覆盖原来的pizza里面的prepare、bake、cut、box等方法,他们自创了自己的流程,而且box的时候还用了别的商家的盒子,这完全就是打着Vander Pizza店的牌子在乱搞嘛。

    问题来了,怎样才能加强质量把控,又不失弹性,让这些地区Pizza店可以依照自己的口味去。

         解决方法2:工厂方法模式

         Vander思前想后就是想不到合适的办法,这时候他只能去请教Panda大师,Panda大师说这个可以用工厂方法解决,首先要给加盟Pizza点使用框架,先从PizzaStore开始改造:

    由于想控制加盟店制作Pizza类中的bake、cut、box,不能让这些加盟店随便乱弄,也不能让它们使用别的公司的包装,需要把控整个制作过程,把PizzaStore的producePizza方法设为抽象类,让GDPizzaStore和SCPizzaStore去继承,它们在生产完Pizza之后只能调用父类的orderPizza方法,继续完成准备、烘焙、切片、包装。让我们来看看现在的改造吧:

 

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

public abstract class Pizza {

	protected String name;
	//面团类型
	protected String dough;
	//酱料
	protected String sauce;
	//佐料	
	protected ArrayList<String> toppings = new ArrayList<String>(10);
	
	public void prepare() {
		System.out.println("Preparing " + name);
		System.out.println(" Tossing dough ...");
		System.out.println(" Adding sauce ...");
		System.out.println(" Adding toppings: ");
		for(int i=0; i<toppings.size(); i++) {
			System.out.println("  " + toppings.get(i));
		}
	}
	
	public void bake() {
		System.out.println("Bake for 25 mins at 350℃");
	}
	
	public void cut() {
		System.out.println("Cutting the pizza into diagonal slices");
	}
	
	public void box() {
		System.out.println("Boxing the pizza");
	}
	
	public String getName() {
		return name;
	}
	
}

public class GDStyleFruitsPizza extends Pizza {

	public GDStyleFruitsPizza() {
		name = "GuangDong Style Fruits Pizza";
		dough = "Thin Crust Dough";
		sauce = "Marinara Sauce";
		
		toppings.add("GuangDong Delicious Cheese");
		toppings.add("GuangDong Pipeapple");
	}
	
}
public class SCStyleFruitsPizza extends Pizza {

	public SCStyleFruitsPizza() {
		name = "SiChuan Style Fruits Pizza";
		dough = "Thick Crust Dough";
		sauce = "Marinara Sauce";
		
		toppings.add("SiChuan Delicious Cheese");
		toppings.add("SiChuan Pepper");
	}
	
}
public class GDPizzaStore extends PizzaStore {

	@Override
	public Pizza producePizza(String type) {
		Pizza pizza = null;
		
		if(type.equals("cheese")) {
			pizza = new GDStyleCheesePizza();
		} else if(type.equals("fruits")) {
			pizza = new GDStyleFruitsPizza();
		} else if(type.equals("durian")) {
			pizza = new GDStyleDurianPizza();
		} else if(type.equals("Hawaii")) {
			pizza = new GDStyleHawaiiPizza();
		}
		return pizza;
	}
	
}
public class SCPizzaStore extends PizzaStore {

	@Override
	public Pizza producePizza(String type) {
		Pizza pizza = null;

		if(type.equals("cheese")) {
			pizza = new SCStyleCheesePizza();
		} else if(type.equals("fruits")) {
			pizza = new SCStyleFruitsPizza();
		} else if(type.equals("durian")) {
			pizza = new SCStyleDurianPizza();
		} else if(type.equals("Hawaii")) {
			pizza = new SCStyleHawaiiPizza();
		}
		return pizza;
	}
	
}

 说明:工厂方法用来处理对象的创建,并将这样的行为封装在子类中,这样客户程序中关于超类的代码就和子类对象创建代码解耦了。也就是说不管以后有多少加盟店,父类PizzaStore都不会去改变了,任由子类PizzaStore天翻地覆,PizzaStore都以不变应万变。工厂方法模式通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。

Vander就纳闷了,我之前用的简单工厂难道不能实现吗,然后Vander就想Panda大师这么干到底有什么优势,接着Vander发现他也能给像Panda那样做到把控整个控制过程。实际上关键的代码就是将PizzaStore的orderPizza()方法写成final,让子类加盟店都无法修改整个制作过程,接着让Pizza类的box()方法也写成final,这样子子类Pizza就无法使用别的商家的包装了。接着Vander给出了他的实现。

 以下是主要的代码:

public class GDPizzaStore extends PizzaStore {

	public SimplePizzaFactory simplePizzaFactory;
	
	public GDPizzaStore(SimplePizzaFactory simplePizzaFactory) {
		super(simplePizzaFactory);
	}
	
}
public class GDPizzaFactory extends SimplePizzaFactory{

	public Pizza producePizza(String type) {
		Pizza pizza = null;
		
		if(type.equals("cheese")) {
			pizza = new GDStyleCheesePizza();
		} else if(type.equals("fruits")) {
			pizza = new GDStyleFruitsPizza();
		} else if(type.equals("durian")) {
			pizza = new GDStyleDurianPizza();
		} else if(type.equals("Hawaii")) {
			pizza = new GDStyleHawaiiPizza();
		}
		return pizza;
	}
	
}
public class PizzaStore {

	public SimplePizzaFactory simplePizzaFactory;
	
	public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
		this.simplePizzaFactory = simplePizzaFactory;
	}
	
	public final Pizza orderPizza(String type) {
		Pizza pizza = simplePizzaFactory.producePizza(type);
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		
		return pizza;
	}
	
}
public abstract class SimplePizzaFactory {

	public abstract Pizza producePizza(String type); 
	
}

 说明:从代码上看来,其实确实跟工厂方法模式差不多,只是把producePizza方法放到了简单工厂而已,但是这么操作最后在运行的时候,必须要实例化相应的工厂,例如GDPizzaStore需要传入GDPizzaFactory,则需要实例化GDPizzaFactory,并且还不能传错,传错了就会导致最后广东的Pizza店卖的确是其它工厂造出来的Pizza。

    下面比对一下工厂方法模式和简单工厂是怎么造出Pizza的。

简单工厂的制作Pizza方法:

public class Main {
	public static void main(String[] args) {
		GDPizzaFactory gdFactory = new GDPizzaFactory();
		GDPizzaStore gdPizzaStore = new GDPizzaStore(gdFactory);
		gdPizzaStore.orderPizza("fruits");
		
		SCPizzaFactory scFactory = new SCPizzaFactory();
		SCPizzaStore scPizzaStore = new SCPizzaStore(scFactory);
		scPizzaStore.orderPizza("fruits");
	}
}

 工厂方法的制作Pizza方法:

public class Main {
	public static void main(String[] args) {
		GDPizzaStore gdPizzaStore = new GDPizzaStore();
		Pizza gdFruitsPizza1 = gdPizzaStore.orderPizza("fruits");
		SCPizzaStore scPizzaStore = new SCPizzaStore();
		Pizza scfruitsPizza2 = scPizzaStore.orderPizza("fruits");
	}
}

 最后Vander发现原来他的方法需要先实例化GDPizzaFactory,然后将Factory作为参数放进GDPizzaStore里面,Vander就想,既然都是GDPizzaFactory对应GDPizzaStore,SCPizzaFactory对应SCPizzaStore,那我直接在GDPizzaStore里面实例化GDPizzaFactory,SCPizzaStore里面实例化SCPizzaFactory不就好了。接着又进行了一轮改造:

 

public class GDPizzaStore extends PizzaStore {
	
	public GDPizzaStore() {
		simplePizzaFactory = new GDPizzaFactory();
	}
	
}
public class PizzaStore {

	protected SimplePizzaFactory simplePizzaFactory;
	
	public final Pizza orderPizza(String type) {
		Pizza pizza = simplePizzaFactory.producePizza(type);
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		
		return pizza;
	}
	
}

   说明:最后虽然在运行时的调用是一样的,但是这里相当于直接让GDPizzaStore和GDPizzaFactory直接绑定在一起了,也增加了代码的耦合度。而且每次多开一个加盟店,都需要实现PizzaStore和PizzaFactory多一次,确实也没多大必要。

   REQ3:由于Pizza大家已经吃腻了,所以Vander为了招来更多的客户,想扩展菜单了,现在不只是卖Pizza了,现在Pizza店学习必胜客的方式,还卖鸡翅、汉堡并且还出售各种独家饮料。那么怎么扩展接口呢?在PizzaStore基类中继续添加produceFiredChicken()、produceBurger()、produceBeverage(),这么做的后果就是所有的加盟店都得跟着做改变,但是有些加盟店Pizza生意做得火热,觉得根本没必要添加这么多种类进去。这时候Vander想到了他之前实现的SimpleFactory2,就是将简单工厂编程“抽象工厂”,让子类工厂去完成产品的创建,这样做的好处就是加盟店的PizzaStore都可以不用动,而且把这些食物的创建都放到了一个大工厂去做这么一件事情了。

         下面是Vander的设计:

 

这个设计图看起来有一定的复杂度,但是其实就是在SimpleFactory2上的PizzaFactory改为了FoodFactory添加了几个生产其他产品的方法,本来只有一个Pizza的抽象类,现在多了Beverage、Burger、FiredChicken这几个抽象类,相当于现在FoodFactory是一个大工厂来生产各种各样的产品。可能你会发现这FoodFactory里面实际上不就是工厂方法模式嘛,没错!就是这样相当于FoodFactory里面生产各种各样的食物,但是生产的食物是什么样的Pizza、什么样的炸鸡,都是由子类做决定的。

    实际上SimpleFactory2已经不算是简单工厂了,简单工厂应该是更单纯一点的,通过一个含参的方法来实例化产品类。SimpleFactory2经过改进之后已经成为了抽象工厂模式了。

   抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道实际产出的具体产品是什么,这样一来,客户就从具体的产品中被解耦了。上面这段话通俗地说,就是像子类工厂完成Pizza的创建,然后PizzaStoreorderPizza()方法中直接是拿基类Pizza类来进行preparebakecutbox等操作,而不用理会是哪种口味哪个区域的Pizza。从而达到了PizzaStore与具体的Pizza类的解耦。

    下面我们梳理一下整个过程,首先刚开始PizzaStore依赖于具体的Pizza类,如下图所示:

 而且随着Pizza类型的增加,依赖将会越来越多,代码里面减少具体的类的依赖是必须要做的,接下来我们看看我们后面的实现:

 

这样就依赖抽象的类Pizza。(遵循了依赖抽象而不是具体的类的原则)

我们发现高层组件(PiizaStore)和底层组件(具体的Piizza子类)都依赖于Pizza的抽象类。我们一般进行设计的时候,首先设计PizzaStorePizzaStore要能生产出很多具体的不同类型的Pizza,其实这时候你可以倒过来想,从具体的Pizza想起,发现应该先抽象出一个Pizza基类,然后这个Pizza基类直接给PizzaStore用,然后就发现这个抽象的Pizza基类就像桥梁一样连接着PizzaStore和具体的Pizza类。这里面就包含了“依赖倒置原则”,即在从上往下的设计的同时,不妨先想想能不能从下往上。

    下面有三个指导方针来避免违反“依赖倒置原则”:

       1、变量不可以持有具体类的引用。(如果需要用new,用工厂代替)

       2、不要让类派生自具体类。(如果派生具体类了,你就会依赖具体类,请派生一个抽象)

       3、不要覆盖基类中已实现的方法(基类已经实现了,说明具有通用性,否则请设为抽象)

 

最后的最后,我们又来总结我们现在现有的设计模式武器。

面向对象基础

         抽象、封装、多态、继承

四大原则

设计原则一:封装变化

设计原则二:针对接口编程,不针对实现编程。

         设计原则三:多用组合,少用继承。

         设计原则四:为交互对象之间的松耦合设计而努力

设计原则五:对扩展开放,对修改关闭

设计原则六:依赖抽象,不要依赖于具体的类

 

 

模式

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

 

 

抽象工厂模式:提供一个接口,用于创建相关对象或者是依赖对象的家族,而不需要明确指定具体的类(说白了就是一系列同类型的工厂方法集合)。

 

最后献上此次设计的源码,源码中包括了一些起初错误的实现,以及后期经过思考后正确的实现,在此处出现的所有源码均有实现,有需要的小伙伴可以下载来运行一下,首先先自己进行设计,然后再参考,这样才能加深工厂模式的理解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值