likes
comments
collection
share

设计模式-工厂模式(简单工厂、工厂方法、抽象工厂)

作者站长头像
站长
· 阅读数 27

简单工厂模式

简单工厂模式是指由一个工厂对象决定常见出哪一种产品类的实例,它不属于GOF23中设计模式,简单工厂模式适用于工厂类负责创建类较少的情况,以及需要创建的类不常改动同时也不长增删负责创建的类。调用者只需要传递参数即可,不需要关系创建的逻辑,工厂生产对应的实例给客户端。

下面通过支付场景实践说明一下简单工厂方法

支付示例(无设计模式情况)

public interface IPay {
    String getName();
    Double queryBalance(String uid);
    default void pay(String uid,Double price){
        Double currentAmount  = queryBalance(uid);
        if(currentAmount < price){
            System.out.println(getName() + "余额不足");
        }else{
            System.out.println(getName() + "支付成功");
        }

    }
}
public class AliPay implements IPay{
    @Override
    public String getName() {
        return "支付宝";
    }

    @Override
    public Double queryBalance(String uid) {
        return 900.0;
    }
}
public class UnionPay implements IPay{
    @Override
    public String getName() {
        return "银行卡";
    }

    @Override
    public Double queryBalance(String uid) {
        return 10000.0;
    }
}
public class WeChatPay implements IPay{
    @Override
    public String getName() {
        return "微信支付";
    }

    @Override
    public Double queryBalance(String uid) {
        return 200.0;
    }
}

设计模式-工厂模式(简单工厂、工厂方法、抽象工厂) 从类图中就可以看出客户端是需要依赖实现类的,我们当前举的例子实例化还是很简单的,如果实例化需要更复杂的代码客户端的代码也会看起来很臃肿。

支付示例(简单工厂模式)

创建一个工厂类

public class PayFactory {

    public static IPay create(String patMethod){
        return switch (patMethod) {
            case "WeChatPay" -> new WeChatPay();
            case "UnionPay" -> new UnionPay();
            default -> new AliPay();
        };
    }

}

设计模式-工厂模式(简单工厂、工厂方法、抽象工厂) 可以发现客户端已经和对象的创建解耦了,客户端只需要关心自己要使用那种方式。当然上述的写法还是可以优化的,因为后续继续添加支付方式还要修改create方法还要添加一个case,所以我们可以继续优化一下代码

支付示例(简单工厂模式+反射)

修改一下工厂创建方法:

public class PayFactory {
    public static IPay create(String patMethod){
        if(!(patMethod == null || "".equals(patMethod))){
            try {
                return (IPay) Class.forName(patMethod).getDeclaredConstructor().newInstance();
            }catch (NoSuchMethodException | ClassNotFoundException | InstantiationException | IllegalAccessException |
                    InvocationTargetException e){
                e.printStackTrace();
            }

        }
        return null;
    }
}
public static void main(String[] args) {
    IPay aliPay = PayFactory.create("com.example.demo.test.pay.AliPay");
    aliPay.pay("123123",100.0);
}

这样修改的好处是后续如果想添加支付方式不需要再修改create方法,但是还有个弊端是字符串可控性不好我们可以针对这种情况继续优化一下

支付示例(简单工厂模式+反射优化)

public class PayFactory {
    public static IPay create(Class<? extends IPay> clz){
        try {
            clz.getDeclaredConstructor().newInstance();
        } catch (InstantiationException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
        return null;
    }
}
public static void main(String[] args) {
    IPay aliPay = PayFactory.create(AliPay.class);
    assert aliPay != null;
    aliPay.pay("123123",100.0);
}

这样写就是稍微完美点的简单工厂模式的实现了

简单工厂方法在源码中的使用

Calendar类的创建

public static Calendar getInstance(TimeZone zone)
{
    return createCalendar(zone, Locale.getDefault(Locale.Category.FORMAT));
}
private static Calendar createCalendar(TimeZone zone,
                                       Locale aLocale)
{
    CalendarProvider provider =
        LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
                             .getCalendarProvider();
    if (provider != null) {
        try {
            return provider.getInstance(zone, aLocale);
        } catch (IllegalArgumentException iae) {
            // fall back to the default instantiation
        }
    }

    Calendar cal = null;

    if (aLocale.hasExtensions()) {
        String caltype = aLocale.getUnicodeLocaleType("ca");
        if (caltype != null) {
            cal = switch (caltype) {
                case "buddhist" -> new BuddhistCalendar(zone, aLocale);
                case "japanese" -> new JapaneseImperialCalendar(zone, aLocale);
                case "gregory"  -> new GregorianCalendar(zone, aLocale);
                default         -> null;
            };
        }
    }
    if (cal == null) {
        // If no known calendar type is explicitly specified,
        // perform the traditional way to create a Calendar:
        // create a BuddhistCalendar for th_TH locale,
        // a JapaneseImperialCalendar for ja_JP_JP locale, or
        // a GregorianCalendar for any other locales.
        // NOTE: The language, country and variant strings are interned.
        if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
            cal = new BuddhistCalendar(zone, aLocale);
        } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
                   && aLocale.getCountry() == "JP") {
            cal = new JapaneseImperialCalendar(zone, aLocale);
        } else {
            cal = new GregorianCalendar(zone, aLocale);
        }
    }
    return cal;
}

Calendar为什么使用简单工厂模式其实也是很好理解的,因为时区、国家这些事基本上很难改变的,所以这个工厂创建完成之后工厂方法是基本上不需要改动的,所以就简单点使用简单工厂模式比较适合,代码也好理解。

所以在我们平常开发的过程中也不是说必须的要符合开闭原则,类似的情况也可以使用简单工厂模式,找到最适合你们代码逻辑的模式,不是强加硬套。

适用场景

  1. 创建需要大量重复代码
  2. 工厂方法不会轻易改动的(这个比较重要如果经常改动建议使用工厂方法模式或者抽象工厂)

工厂方法模式

上文也说道了简单工厂模式。简单工厂模式有一个很大的弊端就是不符合开闭原则,那么在使用的时候限制就比较多。工厂方法模式在这方面做了优化,那什么是工厂方法模式呢。

工厂方法模式是指定义一个创建对象的接口,但是实现让这个接口的类来决定实例化那个类,工厂方法让类的实例化推迟到了子类中进行。在工厂方法模式中用户只需要关系所需要产品对应的工厂,无需关心创建的细节,而且加入新的产品符合开闭原则。

通俗点说就是将工厂抽象出来,一个产品类对应的是一个工厂,客户端在需要实例化的时候选择指定的工厂拿到对应类就可,和简单工厂的区别在于简单工厂只有一个工厂,所有的类的实例化都揉在一个方法里面(当然这里可能有些人会反驳我,应为上文中最后一种优化完全不用写if else使用反射来实例化,但是实际情况可能每种类的实例化都不一样这个时候就不能再使用反射)而工厂方法是多个工厂一个产品对应一个工厂

支付场景(工厂方法实现)

  1. 将创建方法提出来创建一个抽象类
     public interface IPayFactory {
         IPay create();
     }
    
  2. 创建多个工厂
    public class AliPayFactory implements IPayFactory {
      @Override
      public IPay create() {
          return new AliPay();
      }
    }
    
    public class WeChatFactory implements IPayFactory {
        @Override
        public IPay create() {
            return new WeChatPay();
        }
    }
    
    public class UnionPayFactory implements IPayFactory {
        @Override
        public IPay create() {
            return new UnionPay();
        }
    }
    

最后在客户端调用

public class Test {

    public static void main(String[] args) {
        AliPayFactory aliPayFactory = new AliPayFactory();
        aliPayFactory.create().pay("123",100.0);
    }
}

再看一下类图: 设计模式-工厂模式(简单工厂、工厂方法、抽象工厂) 此时客户端只需要关心它需要使用哪个工厂即可,如果需要新增或者删除一个支付方式只需要新增对应的工厂以及具体支付类或者删除对应的即可完全符合了开闭原则。

相较于简单工厂模式优缺点

优点:

  1. 符合开闭原则
  2. 工厂职责单一化易于维护

缺点:

  1. 类变多了(从上文中的类图中就可以明显看出来的缺点)

适用场景

  1. 创建对象需要大量的重复代码
  2. 客户端不依赖于产品类示例如何被创建、实现等细节

抽象工厂模式

上文说到的工厂方法模式,假如是一个产品族(就是一个工厂不再是单一的只生产一种商品,而是生产多种商品,比如以前的苹果公司只生产电脑,后来社会在发展苹果公司需要迎合市场的需求开始生产手机,慢慢的又有平板电脑,现在又在造车),使用工厂方法模式就会发现类越来越多,多到不容易维护,难于理解。这个时候就比较适合使用抽象工厂方法。

抽象工厂模式是指提供一个创建一系列相关或者项目依赖对象的接口,无需指定他们的具体类,客户端不依赖于产品类示例如何被创建实现等细节,强调的是一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码,需要提供一个产品类的库,所有的产品以同样的接口出现,从而是客户端不依赖于具体实现

代码示例

就拿上文的苹果公司做示例苹果公司会生产手机平板电脑、电脑、汽车,同样的我们中国的小米也生产这些产品

  1. 首先将产品族定义一个抽象工厂(也就是说这个产品族能成产那些产品)

      public abstract class CompanyFactory {
    
          private String companyName;
    
          public String getCompanyName() {
              return companyName;
          }
    
          public void init() {
              //这里可以放一些公共逻辑
              System.out.println("初始化基础数据工作");
          }
    
          public abstract IPhone createPhone();
          public abstract ICar createCar();
          public abstract IPad createPad();
          public abstract IComputer createComputer();
    
      }
    

    这里定义了一个公司的工厂类(并不说生产公司,而是将所有公司能干的事情抽离抽象出来)

  2. 定义每个产品的抽象类

    public interface ICar {
        void running();
    }
    
    public interface IComputer {
        void calculate();
    }
    
    public interface IPad {
        void seeAMovie();
    }
    
    public interface IPhone {
        void listenToMusic();
    }
    
  3. 定义某个公司的工厂及产品实现类 苹果公司的工厂及实现类:

    public class AppleFactory extends CompanyFactory{
       @Override
       public IPhone createPhone() {
           super.init();
           return new ApplePhone();
       }
    
       @Override
       public ICar createCar() {
           super.init();
           return new AppleCar();
       }
    
       @Override
       public IPad createPad() {
           super.init();
           return new ApplePad();
       }
    
       @Override
       public IComputer createComputer() {
           super.init();
           return new AppleComputer();
       }
    }
    
    public class AppleCar implements ICar {
        @Override
        public void running() {
            System.out.println("我正在用苹果公司的汽车拉货");
        }
    }
    
    public class AppleComputer implements IComputer {
        @Override
        public void calculate() {
            System.out.println("我正在用苹果公司的电脑计算");
        }
    }
    
    public class ApplePad implements IPad {
        @Override
        public void seeAMovie() {
            System.out.println("我正在用苹果公司的平板电脑看电影");
        }
    }
    
    public class ApplePhone implements IPhone {
        @Override
        public void listenToMusic() {
            System.out.println("我正在用苹果公司的手机听歌");
        }
    }
    

    小米公司的工厂及实现类

     public class MiuiFactory extends CompanyFactory{
         @Override
         public IPhone createPhone() {
             super.init();
             return new MiuiPhone();
         }
    
         @Override
         public ICar createCar() {
             super.init();
             return new MiuiCar();
         }
    
         @Override
         public IPad createPad() {
             super.init();
             return new MiuiPad();
         }
    
         @Override
         public IComputer createComputer() {
             super.init();
             return new MiuiComputer();
         }
     }
    
    public class MiuiCar implements ICar {
        @Override
        public void running() {
            System.out.println("我正在用小米公司的汽车拉货");
        }
    }
    
    public class MiuiComputer implements IComputer {
        @Override
        public void calculate() {
            System.out.println("我正在用小米公司的电脑计算");
        }
    }
    
    public class MiuiPad implements IPad {
        @Override
        public void seeAMovie() {
            System.out.println("我正在用小米公司的平板电脑看电影");
        }
    }
    
    public class MiuiPhone implements IPhone {
        @Override
        public void listenToMusic() {
            System.out.println("我正在用小米公司的手机听歌");
        }
    }
    
  4. 调用:

    public class Test {
        public static void main(String[] args) {
            CompanyFactory factory = new AppleFactory();
            ICar car = factory.createCar();
            IPad pad = factory.createPad();
            IComputer computer = factory.createComputer();
            IPhone phone = factory.createPhone();
            car.running();
            pad.seeAMovie();
            computer.calculate();
            phone.listenToMusic();
    
        }
    }
    

至此抽象工厂的举例实现就完成了 我们可以看一下类图: 设计模式-工厂模式(简单工厂、工厂方法、抽象工厂) 大家可以发现如果还是使用工厂方法设计模式一个产品对应一个工厂方法目前两个公司就对应着2x4个工厂类再添加一个公司的话就是3x4个工厂类这里有些人可能听不懂为什么是2x4、3x4我们可以画图示意一下: 设计模式-工厂模式(简单工厂、工厂方法、抽象工厂) 抽象工厂方法的工厂是按照产品族走的也就是说上图的公司走的,一个工厂可以生产多个产品,所以抽象工厂方法有几个公司就有几个工厂。而工厂方法是跟产品走的,所以有几个产品对应应该有几个工厂。所以相较于工厂方法可以少创建很多类

优缺点

根据上文中的实例以及类图可以总结如下优缺点: 优点:

  1. 相比于工厂方法可以少创建很多类

缺点:

  1. 首先显而易见的是不符合开闭原则,如果增加一个产品那么冲总工厂到具体实现工厂都要增加对应的方法
  2. 增加了系统的抽象性和理解难度

适用场景

其实几个工厂方法使用场景都差不太多,抽象工厂比较适合于大型产品设计,虽然抽象工厂不符合开闭原则,但其实在我们实际开发中只要不是频繁的升级修改也是可以不遵循开闭原则的