以下为我个人学习工厂模式的拙见,如有重复,纯属巧合。如有错误不当之处,请在留言区不吝赐教。
工厂模式细分之下有三种:简单工厂模式、工厂方法以及抽象工厂模式。
先上枯燥的概念:
- 工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
- 抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
光看概念,很迷糊。那是因为概念还是比较抽象的。首先撇开概念不看,我们得知道java中工厂都用来干什么的?答案很简单,创建对象的。那为什么还要细分这么个三类呢?我们找一个现实生活中的例子来举例吧。
比如我们需要衣服,在原始社会那都是我们老祖宗自己用树叶和藤条做出来的。类比于java里对象的new。但是不是每个老祖宗都会缝制啊,怎么办,那就专门由一个人来缝制衣服。
随着时代进步衣服的分类开始变得丰富起来,有薄的厚的,有短袖长袖,有裤子裙子等等。这下子一个人就忙不过来了,那就建立一个工厂专门做衣服吧,这个工厂由若干人组成,有人做上衣有人做裤子等等。用户需要什么类型的衣服,直接跟工厂说,工厂直接返回用户要的。这个类比工厂方法,用来生产某一类产品。
再到后来,用料上也很开始复杂起来。有纯羊毛的、涤纶的、蕾丝的还有冰蚕丝的等等。做衣服的工厂还是负责做衣服,但是原材料也需要工厂来做呀。而且需要什么样的布料,染成什么颜色,这个需要做衣服的工厂来知道。这就类似于抽象工厂模式了。
说多了容易乱,上代码!
一、创建对象(工厂的核心作用)
最原始的生产衣服的方法,就是自己动手,丰衣足食:
public class Clothes {
public String toString(){
return "我是一件衣服";
}
}
然后生产这件衣服:
public class ClothesTest {
public static void main(String[] args) {
Clothes clothes = new Clothes();
System.out.println(clothes);
}
}
运行得到的结果:
我是一件衣服
别误会,这可不是设计模式,只是先示范一下对象的创建,毕竟工厂最原始的地方也是这么操作的。
当然java中创建对象的方法不止这一种,这里重点是放在工厂模式上,就不深入其他方法了。
二、简单工厂
时代进步了,衣服种类也多了,创建一个小工厂吧。我们可以将衣服类设置为抽象类或接口,为了要统一衣服的标准,这里采用抽象类的方式,当然接口也是ok的:
public abstract class Clothes {
// 必须告诉大家,你是啥类型的衣服
public abstract String toString();
}
然后新增裤子和衬衫的类型:
public class Trousers extends Clothes {
@Override
public String toString() {
return "我是一条裤子";
}
}
public class Shirt extends Clothes {
@Override
public String toString() {
return "我是一件衬衫";
}
}
public class FeatureClothes extends Clothes {
@Override
public String toString() {
return "未来创新,暂时无法制作";
}
}
新建一个简单的小工厂来根据用户需要生产衣服:
public class SimpleClothesFactory {
/**
* 根据用户需要的类型,生产对应的衣服
* @param clazz
* @return
*/
public static Clothes createClothes(Class<? extends Clothes> clazz){
try {
return clazz.getDeclaredConstructor().newInstance();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
用户的需求来啦,生产衣服啦:
public class SimpleClothesFactoryTest {
public static void main(String[] args) {
// 生产衣服的工厂
SimpleClothesFactory clothesFactory = new SimpleClothesFactory();
// 生产一条裤子
Clothes trousers = clothesFactory.createClothes(Trousers.class);
System.out.println(trousers);
// 生产一件衬衫
Clothes shirt = clothesFactory.createClothes(Shirt.class);
System.out.println(shirt);
}
}
输出的结果:
我是一条裤子
我是一件衬衫
以上就是简单工厂模式实现的一种方式,如果需要新增衣服种类,只需要到SimpleClothesFactory里添加就好了,至于用户,他只需要跟工厂要就好了。
三、工厂方法模式
现在衣服分类越来越多了,裤子有牛仔裤(jeans)、短裙(skirt)等,而衬衫有紧身女衫(blouse)、运动衫(sweater)等。
老规矩,先上衣服:
public abstract class Clothes {
// 必须告诉大家,你是啥类型的衣服
public abstract String toString();
}
public class Jeans extends Clothes {
@Override
public String toString() {
return "我是一条牛仔裤";
}
}
public class Skirt extends Clothes {
@Override
public String toString() {
return "我是一条裙子";
}
}
public class Blouse extends Clothes {
@Override
public String toString() {
return "我是一件紧身女衫";
}
}
public class Sweater extends Clothes {
@Override
public String toString() {
return "我是一件运动衫";
}
}
然后新建一个统一的工厂,这里我们使用接口,必须实现createClothes方法:
public interface ClothesFactory {
// 所有工厂都得实现该方法,根据类型制作需要的衣服
Clothes createClothes(String type);
}
然后就是分别生产上衣和裤子的工厂了:
public class ShirtFactory implements ClothesFactory {
@Override
public Clothes createClothes(String type) {
Clothes clothes = null;
if(type.equalsIgnoreCase("blouse")){
clothes = new Blouse();
}else if(type.equalsIgnoreCase("sweater")){
clothes = new Sweater();
}else{
clothes = new FeatureClothes();
}
return clothes;
}
}
public class TrousersFactory implements ClothesFactory {
@Override
public Clothes createClothes(String type) {
Clothes clothes = null;
if(type.equalsIgnoreCase("jeans")){
clothes = new Jeans();
}else if(type.equalsIgnoreCase("skirt")){
clothes = new Skirt();
}else{
clothes = new FeatureClothes();
}
return clothes;
}
}
最后用户只需要根据自己的需要,找对应的工厂要产品就好了:
public class FactoryMethodTest {
public static void main(String[] args) {
ClothesFactory factory = new TrousersFactory();
Clothes jeans = factory.createClothes("jeans");
System.out.println(jeans);
factory = new ShirtFactory();
Clothes blouse = factory.createClothes("blouse");
System.out.println(blouse);
}
}
输出结果:
我是一条牛仔裤 我是一件紧身女衫
以后生产帽子,就加个帽子工厂,生产连衣裙就来个连衣裙工厂,方便了分类。
可以看出,工厂方法模式比起简单工厂模式的好处就是将许多杂乱的对象进行了分类。每个工厂都负责生产一类产品,无论是单个工厂新增一个产品还是新增一个工厂生产一套极具创意的产品都非常方便。
说完了好处,那就来说说坏处吧,虽然简单的分了类,但是每个工厂依然我行我素,杂乱无章。没有好的统一标准,万一有几个不良工厂用次品布料,产出来的衣服容易让人皮肤过敏那就麻烦了。况且生产不同衣服的原料也各不相同,那操作起来那是相当麻烦。此时就轮到我们最后的压轴,抽象工厂模式登场了。
四、抽象工厂模式
首先我们需要定义几个原料及其具体分类,布料用的是棉布的还是丝绸的。用的线是尼龙的还是棉的。
这里先约定下,棉布棉线生产一类衣服,丝绸尼龙生产一类衣服。您也甭管这啥奇葩的搭配了,咱就先这么约定着。
public interface Cloth {
String toString();
}
public class Cotton implements Cloth{
@Override
public String toString(){
return "棉布";
}
}
public class Silk implements Cloth{
@Override
public String toString(){
return "丝绸";
}
}
public interface Thread {
String toString();
}
public class CottonThread implements Thread {
@Override
public String toString(){
return "棉线";
}
}
public class NylonThread implements Thread {
@Override
public String toString(){
return "尼龙线";
}
}
新建一个原料工厂,规定了接口,必须生产原料:
public interface IngredientFactory {
Thread createThread();
Cloth createCloth();
}
丝绸类原料工厂:
public class SilkIngredientFactory implements IngredientFactory{
@Override
public Thread createThread() {
return new NylonThread();
}
@Override
public Cloth createCloth() {
return new Silk();
}
}
棉类原料工厂:
public class CottonIngredientFactory implements IngredientFactory {
@Override
public Thread createThread() {
return new CottonThread();
}
@Override
public Cloth createCloth() {
return new Cotton();
}
}
对比两个原料工厂区别,两者使用的是不同的原材料。
好了,我们来实现一下衣服吧,需要改进了:
public abstract class Clothes {
public abstract String toString();
}
public class Shirt extends Clothes {
IngredientFactory ingredientFactory;
public Shirt(IngredientFactory ingredientFactory){
this.ingredientFactory = ingredientFactory;
}
@Override
public String toString() {
Cloth cloth = ingredientFactory.createCloth();
Thread thread = ingredientFactory.createThread();
return cloth.toString() + thread.toString() + "衬衫";
}
}
public class Trousers extends Clothes {
IngredientFactory ingredientFactory;
public Trousers(IngredientFactory ingredientFactory){
this.ingredientFactory = ingredientFactory;
}
@Override
public String toString() {
Cloth cloth = ingredientFactory.createCloth();
Thread thread = ingredientFactory.createThread();
return cloth.toString() + thread.toString() + "裤子";
}
}
现在定义一个商店吧:
public abstract class ClothesStore {
public abstract Clothes createClothes(String type);
}
再细分下裤子商店和衣服商店:
public class TrousersStore extends ClothesStore {
@Override
public Clothes createClothes(String type) {
Clothes clothes = null;
IngredientFactory ingredientFactory = null;
if(type.equalsIgnoreCase("silk")){
ingredientFactory = new SilkIngredientFactory();
clothes = new Trousers(ingredientFactory);
}else if(type.equalsIgnoreCase("cotton")){
ingredientFactory = new CottonIngredientFactory();
clothes = new Trousers(ingredientFactory);
}else {
clothes = null;
}
return clothes;
}
}
public class ShirtStore extends ClothesStore {
@Override
public Clothes createClothes(String type) {
Clothes clothes = null;
IngredientFactory ingredientFactory = null;
if(type.equalsIgnoreCase("silk")){
ingredientFactory = new SilkIngredientFactory();
clothes = new Shirt(ingredientFactory);
}else if(type.equalsIgnoreCase("cotton")){
ingredientFactory = new CottonIngredientFactory();
clothes = new Shirt(ingredientFactory);
}else {
clothes = null;
}
return clothes;
}
}
最后我们来测试一下:
public class AbstractFactoryTest {
public static void main(String[] args) {
ClothesStore store = new ShirtStore();
Clothes clothes = store.createClothes("silk");
System.out.println(clothes);
store = new TrousersStore();
clothes = store.createClothes("cotton");
System.out.println(clothes);
clothes = store.createClothes("silk");
System.out.println(clothes);
}
}
输出结果:
丝绸尼龙线衬衫 棉布棉线裤子 丝绸尼龙线裤子
可以看到你想买啥衣服,你就找哪个商店,需要什么材质的,只需要跟这个商店说就可以得到想要材质的衣服啦。
关于抽象工厂我们来分析一下:细心的你应该会发现,抽象工厂模式的实现过程中用到了工厂方法模式,这里总的ClothesStore可以当成一个工厂,然后细分了上衣店和裤子店。用的是工厂方法模式,而生产用到的原料工厂,就是抽象工厂模式啦。通过抽象工厂模式,一步一步的组合原料,最后由商店根据用户的选择去找对应的原料工厂就好了。
后续扩展也是非常方便,新增一个种类的原料,比如纽扣,那就加个纽扣的接口,在原料工厂里增加一个api,create纽扣,这样在生产具体衣服的时候直接调用api就好了。是否觉得扩展起来十分方便呢。
最后总结一下工厂模式用到的设计模式原则:
依赖抽象,不要依赖具体类
可以看到我们都是使用抽象类作为变量类型,而用其子类作为具体实现,这样每一层直接都得到解耦,不会过分依赖,修改起来也十分方便。
这里需要注意的是,工厂模式虽然统一了对象的创建,但是在实际业务场景中,对象的创建是十分复杂的。就比如使用工厂模式最典型的spring创建bean的时候,看过源码的应该都知道,这一块的源码是相当复杂的。而且这里还违背了设计原则中的开闭原则。所以工厂模式的使用需要结合实际业务场景来决定。
学习设计模式,切记并不是编码的时候用的越多越好,而是得用的巧,灵活运用设计模式才能提高代码的质量。