学好设计模式防被祭天:工厂模式

工厂模式

为了防止被“杀”了祭天,学点设计模式,并总结下还是有必要的。

一:模式理解

  1. 工厂模式的作用是新建对象。
  2. 工厂模式的目的是让新建对象的过程更加优雅,减少对业务代码的混淆。
  3. 包含简单工厂,工厂方法,抽象工厂。
  4. 以下几点可以在例子之后再看。
  5. 单独建立用于生成对象的工程类,即为简单工厂,类似于工具类。
  6. 在原有代码上,抽象出一个方法,用于生成对象,即为工厂方法。
  7. 新建一个完全抽象的工厂接口/类,具体生成的对象由该工厂的实现类/子类决定,即为抽象工厂。


二: 例子

你是一个富二代,拥有有一家汽车公司,公司的主要工作是把汽车卖给客户,即sellCar。

主要流程包括原料采购,组装汽车,汽车展示。

为方便模拟,三个步骤分别为:

  1. 原料采购直接sout原料采购。
  2. 组装汽车(assembleCar)生成对应的汽车对象。
  3. 汽车展示调用生成汽车对象的display方法,确保生产正确。
富二代家的汽车公司

有一个汽车类,包含属性为牌子,颜色,还有一个display方法。

// 汽车类
@Data
public class Car {
    private String brand;
    private String color;

    public void display() {
        System.out.println("This is a car");
    }
}

目前你司主营奔驰和宝马车,均继承自父类Car,对应的类分别为:

// 宝马车实体类
public class BMWCar extends Car{
    @Override
    public void display(){
        System.out.println("This is a BMW car");
    }
}

// 奔驰车实体类
public class BenzCar extends Car {
    @Override
    public void display(){
        System.out.println("This is a Benz car");
    }
}

两个类没什么区别,只是分别重写了Car类的display方法。

你司的日常就是卖汽车(sellCar),根据不同的订单,输入不同的carType,新建不同的汽车对象。

public class CarCompany {
    public void sellCar(String carType) {
        System.out.println("原料采购");
        Car car = null;
        if (StringUtils.equals(carType, "BMW")) {
            car = new BMWCar();
        } else if (StringUtils.equals(carType, "Benz")) {
            car = new BenzCar();
        } else {
            car = new Car();
        }
        car.display();
    }

    public static void main(String[] args) {
        CarCompany carCompany = new CarCompany();
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String carType = scanner.next();
            carCompany.sellCar(carType);
        }
    }
}

输入/输出:

BMW
原料采购
This is a BMW car
Benz
原料采购
This is a Benz car
car
原料采购
This is a car

可以看出,你司的业务还是挺简单粗暴,只需判断输入生产不同的车即可。

作为富二代的你并不打算对其做出什么改变。

然而,有一天,你发现奥迪车卖的也不错,准备开始在自己公司卖奥迪车。

此时在业务代码sellCar中增加一个else判断条件。

你的公司越做越大,每天都会需要决定加入或者删除某些车是否生产。

某一次,一个程序员在修改if else的过程中,不小心搞出个bug。由于习惯性复制粘贴,在应该生产QQ车的时候,生产了一辆宾利车。

为此,你“杀”了这位程序员祭天。

有没有办法可以在不修改业务代码的情况下,搞定第二步骤,即组装汽车逻辑的修改呢?

1. 简单工厂

将新建对象的逻辑从业务代码中提取出来。

新建一个简单汽车工厂类来承载新建汽车对象的功能。

public class SimpleCarFactory {
    public static Car assembleCar(String carType) {
        Car car = null;
        if (StringUtils.equals(carType, "BMW")) {
            car = new BMWCar();
        } else if (StringUtils.equals(carType, "Benz")) {
            car = new BenzCar();
        } else {
            car = new Car();
        }
        return car;
    }
}

修改之后,汽车工厂的代码变为:

public class CarCompany {
    public void sellCar(String carType) {
        // 原料采购
        System.out.println("原料采购");
        // 组装汽车
        Car car = SimpleCarFactory.assembleCar(carType);
        // 展示汽车
        car.display();
    }

    public static void main(String[] args) {
        CarCompany carCompany = new CarCompany();
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String carType = scanner.next();
            carCompany.sellCar(carType);
        }
    }
}

输入和输出都与之前一样。

  1. 严格意义上,简单工厂并不是一种设计模式,更像是一种重构。
  2. 重构之后,CarCompany的sellCar代码变得非常清晰,特别是引入方法assembleCar后,一看就可以知道该步骤是在组装汽车。
  3. 对于刚看代码的新手而言,就算不理解assembleCar具体操作,也可以根据该方法名知道该方法的作用。
  4. 经过程序员的一顿改,你特别高兴,自己家又多了一个工厂,又多了个厂长的身份,还高兴地freestyle了一番。

2. 方法工厂

工厂的目的是生成一个对象,简单工厂为此新建了一个类。

此外,也可以在CarCompany类下增加一个新建汽车对象的私有方法。

该方法的作用和简单工厂一致,不同的是以方法的形式出现,将其称为方法工厂。

public class CarCompany {
    public void sellCar(String carType) {
        System.out.println("原料采购");
        Car car = assembleCar(carType);
        car.display();
    }

    private Car assembleCar(String carType) {
        Car car = null;
        if (StringUtils.equals(carType, "BMW")) {
            car = new BMWCar();
        } else if (StringUtils.equals(carType, "Benz")) {
            car = new BenzCar();
        } else {
            car = new Car();
        }
        return car;
    }

    public static void main(String[] args) {
        CarCompany carCompany = new CarCompany();
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String carType = scanner.next();
            carCompany.sellCar(carType);
        }
    }
}

当然,如果只是将方法的实现移动一个位置,那也算不上是设计模式。

好不容易多了个厂长的身份,程序员竟然这么改。你顿时不高兴了,回头就准备掏出自己40米的大刀。

此时,程序员急忙开始解释。

如果将CarCompany中的assembleCar方法申明为抽象方法,具体实现由子类来决定。

public abstract class CarCompany {
    public void sellCar(String carType) {
        System.out.println("原料采购");
        Car car = assembleCar(carType);
        car.display();
    }

    protected abstract Car assembleCar(String carType);
}

此时,你完全可以开多家公司用于生产不同的车,它们都将继承自CarCompany。

CarCompany类规定了sellCar方法的步骤,留下组装汽车的方法让不同的公司自己去实现。

新建两个公司,一个卖宝马,一个卖奔驰,分开之后,业务得到了扩展,两个工厂都可以生产轿车和SUV。

public class BMVCarCompany extends CarCompany {
    @Override
    protected Car assembleCar(String carType) {
        Car car = null;
        if (StringUtils.equals(carType, "car")) {
            car = new BMWCar();
        } else if (StringUtils.equals(carType, "suv")) {
            car = new BMWSUVCar();
        } else {
            car = new Car();
        }
        return car;
    }
}
public class BenzCarCompany extends CarCompany {
    @Override
    protected Car assembleCar(String carType) {
        Car car = null;
        if (StringUtils.equals(carType, "car")) {
            car = new BenzCar();
        } else if (StringUtils.equals(carType, "suv")) {
            car = new BMWSUVCar();
        } else {
            car = new Car();
        }
        return car;
    }
}
public class Client {
    public static void main(String[] args) {
        CarCompany bmwCarCompany = new BMVCarCompany();
        CarCompany benzCarCompany = new BenzCarCompany();
        bmwCarCompany.sellCar("car");
        benzCarCompany.sellCar("car");
        bmwCarCompany.sellCar("suv");
        benzCarCompany.sellCar("suv");
    }
}

输入/输出:

原料采购
This is a BMW car
原料采购
This is a Benz car
原料采购
This is a BMW SUV car
原料采购
This is a BMW SUV car

两个公司可以完全独立运行,在需要新建一个公司的时候,只需要增加新的公司类即可,如AudiCarCompany。

听了程序员这番解释,你虽然没听懂,但觉得可以一下子多搞几个分公司,内心还是有点小激动的,随手收回了自己那把40米的大刀。

3. 抽象工厂

由于成本变动,宝马和奔驰子公司都需要更换组装汽车的过程,第一时间想到是直接修改两个公司的assembleCar的代码。

然而现在宝马公司又需要生产纪念版的宝马车,和普通宝马不同的是,特别版的宝马使用了特殊的红色油漆。

面对这样的需求,虽贵为富二代的你,还是准备讨好下程序员,以一起去彻夜鼓掌作为诱惑,希望程序员尽快完成。

在之前的步骤中,已经将组装汽车的过程抽象成工厂,那么更改/修改汽车组装的过程,可以抽象成换了一家代工厂。

但无论怎么换代工厂,都要求这些工厂有assembleCar的功能,所以需要一个抽象类或者接口来约束。其中的组装方法是抽象的,具体实现在子类中定义。

这就是抽象工厂这个名称的含义。

public abstract class AbstractCarFactory {
    public abstract Car assembleCar(String carType);
}

分别新建两家代工厂。

// 宝马车代工厂
public class BMWCarFactory extends AbstractCarFactory {
    @Override
    public Car assembleCar(String carType) {
        Car car = null;
        if (StringUtils.equals(carType, "car")) {
            car = new BMWCar();
        } else if (StringUtils.equals(carType, "suv")) {
            car = new BMWSUVCar();
        } else {
            car = new Car();
        }
        return car;
    }
}

// 奔驰车代工厂
public class BenzCarFactory extends AbstractCarFactory {
    @Override
    public Car assembleCar(String carType) {
        Car car = null;
        if (StringUtils.equals(carType, "car")) {
            car = new BenzCar();
        } else if (StringUtils.equals(carType, "suv")) {
            car = new BMWSUVCar();
        } else {
            car = new Car();
        }
        return car;
    }
}

两家公司分别持有AbstractCarFactory对象,在assembleCar方法中,只需直接返回对应工厂组装的汽车即可,不用关注具体的工艺。

public class BMVCarCompany extends CarCompany {
    private AbstractCarFactory carFactory;

    public BMVCarCompany(AbstractCarFactory carFactory) {
        this.carFactory = carFactory;
    }

    @Override
    protected Car assembleCar(String carType) {
        return carFactory.assembleCar(carType);
    }
}
public class BenzCarCompany extends CarCompany {
    private AbstractCarFactory carFactory;

    public BenzCarCompany(AbstractCarFactory carFactory) {
        this.carFactory = carFactory;
    }

    @Override
    protected Car assembleCar(String carType) {
        return carFactory.assembleCar(carType);
    }
}
public class Client {
    public static void main(String[] args) {
        AbstractCarFactory bmwCarFactory = new BMWCarFactory();
        CarCompany bmwCarCompany = new BMVCarCompany(bmwCarFactory);
        AbstractCarFactory benzCarFactory = new BenzCarFactory();
        CarCompany benzCarCompany = new BenzCarCompany(benzCarFactory);
        bmwCarCompany.sellCar("car");
        benzCarCompany.sellCar("car");
        bmwCarCompany.sellCar("suv");
        benzCarCompany.sellCar("suv");
    }
}

当某个工厂需要更换代工厂时候,只需新建一个继承自AbstractCarFactory抽象工厂的具体工厂即可,如AudiCarFactory。

并将此工厂作为carCompany的属性置入,如果只希望增加代码而不是修改代码,可以新建AudiCarCompany即可。

以下例子为宝马公司更换特别版生产工厂之后,生产宝马车的过程,在display中同时展示宝马车的颜色。

可以看到,在更换特别版工厂之后,生产出来的宝马车加上了特别的红色。

而作为富二代的你,开上这辆特别版的宝马,突然发现一下子多了好多一夜成长的机会。

此时,你已经忘了答应给程序员的鼓掌奖励。/(ㄒoㄒ)/~~

public class BMWCarFactoryForSpecialEdition extends AbstractCarFactory {
    @Override
    public Car assembleCar(String carType) {
        Car car = null;
        if (StringUtils.equals(carType, "car")) {
            car = new BMWCar();
        } else if (StringUtils.equals(carType, "suv")) {
            car = new BMWSUVCar();
        } else {
            car = new Car();
        }
        car.setColor("Red for special edition");
        return car;
    }
}
public class BMWCar extends Car {
    @Override
    public void display() {
        System.out.println("This is a BMW car with " + getColor());
    }
}

输入/输出:

原料采购
This is a BMW car with Red for special edition


三: 再理解

  1. 简单工厂只是一个代码重构的过程,为组装汽车的过程新建了一个类,一个静态方法。
  2. 方法工厂可以分为简单方法工厂和抽象方法工厂。简单方法工厂和简单方法差不多,只是少新建了一个工厂类。抽象方法工厂,将生成对象的方法申明为抽象,在子类中实现,方便建立新的公司,业务拆分。
  3. 抽象工厂,定义一个什么都不做,只约定做什么的工厂,即将工厂的作用抽象出来。抽象工厂和策略模式很类似,只是抽象工厂的目的是生成对象,而策略模式的目的主要在于为不同类别的对象执行不同的步骤。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容