理解设计模式中的单例模式一文中介绍了创建型模式中最为简单的一种:单例模式,并推荐了几种可以在实际生产中使用的线程安全的形式。本文将继续介绍创建型模式中的工厂方法模式和抽象工厂模式,同样使用代码和类图来直观的理解工厂模式究竟是什么,及如何使用。希望大家阅读完之后,对于工厂模式所涉及的三种形式有一个初步的理解,以便在后续的使用工具中做到举一反三。

前言

设计模式(Design_Pattern).png

工厂模式属于创建型模式中的一种,它的目的仍然是用于创建对象实例,不过它为实例的创建了一种新的思路。顾名思义,工厂模式就是将实例的创建交给类对应的工厂类去做,而对象类本身只负责和对象相关的一些其他工作。工厂模式可以分为:

  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式

工厂模式

1. 简单工厂模式

简单工厂模式.png

520刚刚过去,美妆类的产品恐怕是不少男生在礼物挑选时一种不会出错的选择。当然,美妆类的产品有很多,例如口红、唇膏、眼影、腮红、高光……我们以其中的口红(Lip Stick)为例来理解工厂模式。假设Forlogen想要买一支口红,但他不知道口红有YSL、TF、Dior、Chanel、Guerlain、Givenchy等等众多的品牌,他应该怎么做呢?一种最简单的方法就是去咨询旁边的朋友,询问之后朋友推荐了YSL、TF和Dior,最后他就去准备购买口红。

如果他选择去综合类的美妆店或是直播间去进行购买,这里使用的其实就是简单工厂模式,如何理解呢?Forlogen在购买口红时只知道那三个牌子,因此,他开始咨询导购员:

Forlogen:请问有YSL的口红嘛? 导购员:有,请问你需要那个色号的呢? Forlogen:你来介绍一下吧 导购员:blabla…… Forlogen:那TF的有什么推荐嘛? 导购员:blabla…… Forlogen:有没有Dior呢? 导购员:不好意思,本店里暂时没有Dior的口红,……

最终Forlogen可能会根据导购员的推荐选择购买了其中的几支,安全的度过了平凡的一天~

下面我们就用代码来解释下上面的整体流程,从而看一下它是如何体现简单工厂模式的思想的。首先创建一个接口LipStick,接口中只有一个抽象方法show()用于显示购买的口红的信息。

  1. public interface LipStick {
  2. void show();
  3. }

每一个品牌的口红的实现类都需要实现这个接口,假设美妆店里只有YSL的小金条和TF的黑管:

  1. public class YSL implements LipStick {
  2. @Override
  3. public void show(){
  4. System.out.println("YSL -- Small gold bars");
  5. }
  6. }
  1. public class TF implements LipStick{
  2. @Override
  3. public void show(){
  4. System.out.println("TOM FORD -- Black bars");
  5. }
  6. }

那么创建一个工厂类Store表示美妆店,通过Store中的getLipStick()就可以根据品牌名购买各种口红:

  1. public class Store {
  2. public static LipStick getLipStick(String brand){
  3. if (brand.equalsIgnoreCase("YSL")){
  4. return new YSL();
  5. } else if (brand.equalsIgnoreCase("TF")){
  6. return new TF();
  7. } else {
  8. return null;
  9. }
  10. }
  11. }

Forlogen购买时只需要和导购说口红的品牌即可,而不需要知道口红是怎么来的。

  1. public class Consumer {
  2. public static void main(String[] args) {
  3. LipStick ls = Store.getLipStick("YSL");
  4. if (ls == null) {
  5. System.out.println("no this lipstick...");
  6. } else {
  7. ls.show();
  8. }
  9. }
  10. }

最后Forlogen选择了YSL的小金条。

  1. YSL -- Small gold bars

最后,我们来分析一下简单工厂模式有什么优缺点:

  • 优点:易于理解,使用简单
  • 缺点:如果此时店里引入了一个新的品牌,那么此时除了新建口红的实现类之外,还需要在Store类中多加一组if-else判断。而则就违反了七大设计原则中的开闭原则:对扩展开放对修改关闭

因此,简单工厂模式通常用来生产同一品类中的任意产品,但是对于新产品需要修改已有的代码。

2. 工厂方法模式

image-20200523154008949.png

如果Forlogen是一个没有朋友的人,那么他就无法通过他人来知道哪些品牌的口红比较好。因此,他选择直接去商场一层的专柜进行购买,这里使用的就是工厂方法模式。此时,口红的购买不再是通过咨询综合美妆店里的导购员,而是直接通过专柜进行购买。而且如果此时商场引入了一个新的品牌,只需要在商场中建立一个新的专柜即可。

这里同样有口红的接口LipStick,以及YSL和TF的实现类:

  1. public interface LipStick {
  2. void show();
  3. }
  1. public class YSL implements LipStick{
  2. @Override
  3. public void show() {
  4. System.out.println("YSL -- Small gold bars");
  5. }
  6. }
  1. public class TF implements LipStick{
  2. @Override
  3. public void show() {
  4. System.out.println("TOM FORD -- Black bars");
  5. }
  6. }

此时,口红是直接通过专柜来进行购买,因此需要创建每个品牌对应的工厂类。首先同样创建一个工厂的接口LipStickFactory,接口中只有一个方法LipStickFactory

  1. public interface LipStickFactory {
  2. LipStick getLipStick();
  3. }
  1. public class YSL_Factory implements LipStickFactory{
  2. @Override
  3. public YSL getLipStick(){
  4. return new YSL();
  5. }
  6. }
  1. public class TF_Factory implements LipStickFactory{
  2. @Override
  3. public TF getLipStick(){
  4. return new TF();
  5. }
  6. }

Forlogen直接去想要的专柜进行购买即可,如果商场中设置了某品牌的专柜,那么就可以购买,否则就无法购买。

  1. public class Consumer {
  2. public static void main(String[] args) {
  3. TF_Factory tf_factory = new TF_Factory();
  4. TF lipStick = tf_factory.getLipStick();
  5. lipStick.show();
  6. YSL lipStick1 = new YSL_Factory().getLipStick();
  7. lipStick1.show();
  8. }
  9. }

最后Forlogen购买了YSL的小金条和TF的黑管。

  1. TOM FORD -- Black bars
  2. YSL -- Small gold bars

最后来分析一个工厂方法模式的优缺点:

  • 优点:工厂方法模式为每个产品都设置了专门的工厂类,用户直接通过各个类对应的工厂进行购买,满足开闭原则
  • 缺点:每新增一个商品就需要新建一个对象类和对应的工厂类,代码量成倍的增加

因此,工厂方法模式通常用来生产同一品类中的固定产品,它支持任意的增加新产品。

3. 抽象工厂模式

抽象工厂模式.png

如果此时Forlogen觉得只买口红太单一,他决定再买些其他的美妆产品。对于每一个品牌来说,他们不只是生产口红,同样会生产香水、气垫等其他相关的产品。因此,工厂方法模式就不能很好的满足需求,需要对工厂模式做进一步的抽象。假设现在有口红和香水(perfume)两类产品,按照工厂模式的整体思想,我们首先需要创建它们各自的工厂接口。例如口红的工厂接口LipStickFactory,它包含两个抽象方法:

  • setColor():它用来表示购买的口红的色号,是正红、番茄色、豆沙色还是其他的色号
  • serEffect():它用来表示口红的质感,是珠光、雾面哑光还是丝绒哑光等
  1. public interface LipStickFactory {
  2. void setColor();
  3. void setEffect();
  4. }

同样有香水的接口PerfumeFactory,它其中包含三个方法分别用于表示香水的前调、中调和后调。

  1. public interface PerfumeFactory {
  2. void setTopNote();
  3. void setMiddleNot();
  4. void setBaseNote();
  5. }

对于YSL来说,它的口红类要实现LipStickFactory接口,香水类要实现PerfumeFactory接口,并重写其中对应的抽象方法

  1. public class YSL_LipStick implements LipStickFactory{
  2. @Override
  3. public void setColor() {
  4. System.out.println("Red..."); // 正红
  5. }
  6. @Override
  7. public void setEffect() {
  8. System.out.println("shimmer..."); // 珠光
  9. }
  10. }
  1. // YSL LIBRE自由之水
  2. public class YSL_Perfume implements PerfumeFactory{
  3. @Override
  4. public void setTopNote() {
  5. System.out.println("citrus"); // 橙花,柑橘
  6. }
  7. @Override
  8. public void setMiddleNot() {
  9. System.out.println("lavender..."); // 薰衣草 橙花
  10. }
  11. @Override
  12. public void setBaseNote() {
  13. System.out.println("vanilla..."); // 香草 龙涎香
  14. }
  15. }

TF的口红类和香水类也要实现相应的接口,并重写其中的方法

  1. public class TF_LipStick implements LipStickFactory{
  2. @Override
  3. public void setColor() {
  4. System.out.println("cameo brown..."); // 豆沙色
  5. }
  6. @Override
  7. public void setEffect() {
  8. System.out.println("matte..."); // 雾面哑光
  9. }
  10. }
  1. public class Tf_Perfume implements PerfumeFactory{
  2. @Override
  3. public void setTopNote() {
  4. System.out.println("rose..."); // 玫瑰
  5. }
  6. @Override
  7. public void setMiddleNot() {
  8. System.out.println("lavender..."); // 薰衣草 橙花
  9. }
  10. @Override
  11. public void setBaseNote() {
  12. System.out.println("citrus"); // 橙花,柑橘
  13. }
  14. }

而此时购买某一品牌的商品时仍然是从专柜来购买,但是此时的专柜不仅卖口红,还买香水。因此,首先需创建工厂的工厂接口ProductFactory,其中的抽象方法表示一个美妆产品专柜应该卖哪些产品。

  1. public interface ProductFactory {
  2. LipStickFactory getLipStick();
  3. PerfumeFactory getPerfume();
  4. }

YSL和TF对应的工厂类就需要实现这个抽象工厂接口,并重写其中的方法

  1. public class YSL_Factory implements ProductFactory
  2. {
  3. @Override
  4. public LipStickFactory getLipStick() {
  5. return new YSL_LipStick();
  6. }
  7. @Override
  8. public PerfumeFactory getPerfume() {
  9. return new YSL_Perfume();
  10. }
  11. }
  1. public class TF_Factory implements ProductFactory{
  2. @Override
  3. public LipStickFactory getLipStick() {
  4. return new TF_LipStick();
  5. }
  6. @Override
  7. public PerfumeFactory getPerfume() {
  8. return new Tf_Perfume();
  9. }
  10. }

此时,Forlogen通过综合类的专柜不仅可以购买到口红,还可以购买香水。

  1. public class Consumer {
  2. public static void main(String[] args) {
  3. TF_Factory tf_factory = new TF_Factory();
  4. LipStickFactory lipStick = tf_factory.getLipStick();
  5. lipStick.setColor();
  6. lipStick.setEffect();
  7. System.out.println("----------------");
  8. YSL_Factory ysl_factory = new YSL_Factory();
  9. PerfumeFactory perfume = ysl_factory.getPerfume();
  10. perfume.setTopNote();
  11. perfume.setMiddleNot();
  12. perfume.setBaseNote();
  13. }
  14. }

最后Forlogen购买了一支TF家哑光质感的豆沙色口红和一支YSL的LIBRE系列的香水。

  1. cameo brown...
  2. matte...
  3. ----------------
  4. citrus
  5. lavender...
  6. vanilla...

最后同样来总结一下抽象工厂模式的优缺点:

  • 优点:抽象工厂模式首先具有工厂方法模式的优点,即它可以任意的增加新产品,只需要实现相应的工厂接口即可。此外,它最主要的优点是可以在类的内部对产品族进行约束,即通过工厂可以生产同一品牌不同类别的产品
  • 缺点:使用抽象工厂模式扩展较为麻烦,如果此时新增了一类产品,那么要创建产品的工厂接口,每个品牌的新产品类要实现该接口,此外抽象工厂接口和每个品牌的抽象接口都需要更新。因此,如果产品族中产品类品较多且固定时,抽象工厂方法是一个较好的选择

4. 总结

不同的工厂模式的实现方式有各自对应的较为合适的应用场景,但彼此的划分并不是绝对的,因此,在实际的使用过程中,我们应该根据具体的应用场景来选则具体的方式进行使用。