工厂模式

2021/8/27 java

# 前言

在项目开发或者迭代过程中,我们经常用到工厂模式。工厂模式的应用场景以及优缺点需要了解清楚,以便我们后面项目开发和设计过程中合理的运用。

意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行

主要解决:主要解决接口选择的问题

应用实例: 1、系统集成多个数据源,根据配置做动态切换。 2、系统集成多个分词工具,根据工厂模式切换不同的实例。 3、系统多种支付方式,外观模式定义统一入口,根据不同类型调用不同的支付实现。

关键字:接口选择

# 简单工厂

系统多种支付方式,定义工厂类,根据不同类型调用不同的支付实现?

  • 定义基础接口及相关实现类

    //定义支付接口
    public interface Pay {
    
        void startPay();
    }
    
    //预付款支付
    public class AdvancePaymentImpl implements Pay{
        @Override
        public void startPay() {
            //预付款支付逻辑
            System.out.printf("advancePayment startPay");
        }
    }
    
    //在线支付
    public class OnlinePaymentImpl implements Pay{
        @Override
        public void startPay() {
            //在线支付逻辑
            System.out.printf("onlinePayment startPay");
        }
    }
    
    //账期支付
    public class DayPaymentImpl implements Pay{
        @Override
        public void startPay() {
            //账期支付逻辑
            System.out.printf("DayPayment startPay");
        }
    }
    
  • 创建工厂类

//定义支付工厂
public class PayFactory {
    //工厂调用方法
    public static Pay getPay(EnumType enumType){
        if(enumType.AdvancePayment.equals(enumType)){
            return new AdvancePaymentImpl();
        }else if(enumType.OnlinePayment.equals(enumType)){
             return new OnlinePaymentImpl();
        }else if(enumType.DayPayment.equals(enumType)){
             return new DayPaymentImpl();
        }
        return null;  
    } 
    //支付类型枚举
    public enum EnumType{
        AdvancePayment,OnlinePayment,DayPayment
    }
    public static void main(String[] args) {
        //预付款支付调用
         PayFactory.getPay(AdvancePayment);
        //在线支付调用
         PayFactory.getPay(OnlinePayment);
         //账期支付调用
         PayFactory.getPay(DayPayment);  
    }
}

思考:该种工厂方式的弊端?每增加一种支付方式则修改工厂类的getPay方法的if...else的判断,违背了开闭原则

改进策略1:通过反射动态创建实例,进行改造

//支付工厂
public class PayFactory {
    //定义反射容器集合
    public static final Map<EnumType,Class> map = new HashMap<>();
    
    //集合添加
     public static void add(EnumType enumType,Class obj) throws IllegalAccessException, InstantiationException {
        map.put(enumType,obj);
    } 
    //工厂调用方法
    //反射其实也很容易就实现了,但是在一些特定的情况下,并不适用,反射机制也会降低程序的运行效果,在对性能要求很高的场景下应该避免这种实现
    public static Pay getPay(EnumType enumType) throws IllegalAccessException, InstantiationException {
        Class shap = map.get(enumType);
        return (Pay) shap.newInstance();
    }
    //支付类型枚举
    public enum EnumType{
        AdvancePayment,OnlinePayment,DayPayment
    }
    public static void main(String[] args) {
        //预付款支付调用
         PayFactory.add(AdvancePaymentm,AdvancePaymentImpl.class);
         PayFactory.getPay(AdvancePayment);
        //在线支付调用
         PayFactory.add(OnlinePayment,OnlinePaymentImpl.class);
         PayFactory.getPay(OnlinePayment);
         //账期支付调用
         PayFactory.add(DayPayment,DayPaymentImpl.class);
         PayFactory.getPay(DayPayment);  
    }
}

改进策略2:每次需要调用PayFactory.add()方法,结合单例模式再次改造

//构建单例支付工厂
public class PayFactory {
    
    //提供全局唯一访问点
    private static PayFactory payFactory = new PayFactory();

    private PayFactory() {

    }
    public static PayFactory getPayFactory (){
        return payFactory;
    }
    
    //定义反射容器集合
    public static final Map<EnumType,Class> map = new HashMap<>();
    
    //集合添加
     public static void add(EnumType enumType,Class obj) throws IllegalAccessException, InstantiationException {
        map.put(enumType,obj);
    } 
    //工厂调用方法
    //反射其实也很容易就实现了,但是在一些特定的情况下,并不适用,反射机制也会降低程序的运行效果在对性能要求很高的场景下应该避免这种实现
    public static Pay getPay(EnumType enumType) throws IllegalAccessException, InstantiationException {
        Class shap = map.get(enumType);
        return (Pay) shap.newInstance();
    }
    //支付类型枚举
    public enum EnumType{
        AdvancePayment,OnlinePayment,DayPayment
    }
    public static void main(String[] args) {
        //预付款支付调用
         PayFactory.getPay(AdvancePayment);
        //在线支付调用
         PayFactory.getPay(OnlinePayment);
         //账期支付调用
         PayFactory.getPay(DayPayment);  
    }
}


//预付款支付
public class AdvancePaymentImpl implements Pay{
    //每个支付实现类,容器启动自动加载到反射容器集合中
    @PostConstruct
    public void init() throws InstantiationException, IllegalAccessException {
        PayFactory.getPayFactory().add(AdvancePayment,AdvancePaymentImpl.class);
    }
}
.....其他两种不在赘述

# 工厂方法

理解了简单工厂之后,工厂方法就是将获取创建实例的工厂类定义工厂接口或抽象工厂,创建实例由实现工厂接口或继承的子类的具体工厂类来创建

/**
 * 定义方法工厂
 */
public interface BaseFactory {
    Pay createPay();
}

//账期支付工厂类
public class AdvancePaymentFactory implements BaseFactory {
    @Override
    public Pay createPay() {
        return new AdvancePaymentImpl();
    }
}

public static void main(String[] args) {
     BaseFactory baseFactory = new AdvancePaymentFactory();
    //预付款支付调用
    baseFactory.createPay().startPay();
    //在线支付调用
    ...
    //账期支付调用
   ...
}

# 抽象工厂

# 抽象和接口

设计模式里面用到大量的抽象和接口,针对抽象方法和接口有什么区别?

1 Java接口和Java抽象类最大的一个区别,就在于Java抽象类可以提供某些方法的部分实现,而Java接口不可以(就是interface中只能定义方法,而不能有方法的实现,而在abstract class中则可以既有方法的具体实现,又有没有具体实现的抽象方法),这大概就是Java抽象类唯一的优点吧,但这个优点非常有用。如果向一个抽象类里加入一个新的具体方法时,那么它所有的子类都一下子都得到了这个新方法,而Java接口做不到这一点,如果向一个Java接口里加入一个 新方法,所有实现这个接口的类就无法成功通过编译了,因为你必须让每一个类都再实现这个方法才行,这显然是Java接口的缺点。

2 一个抽象类的实现只能由这个抽象类的子类给出,也就是说,这个实现处在抽象类所定义出的继承的等级结构中,而由于Java语言的单继承性,所以抽象类作为类型定义工具的效能大打折扣。在这一点上,Java接口的优势就出来了,任何一个实现了一个Java接口所规定的方法的类都可以具有这个接口的类型,而一个类可以实现任意多个Java接口,从而这个类就有了多种类型。(使用抽象类,那么继承这个抽象类的子类类型就比较单一,因为子类只能单继承抽象类;而子类能够同时实现多个接口,因为类型就比较多。接口和抽象类都可以定义对象,但是只能用他们的具体实现类来进行实例化。)

3 抽象类的主要作用是作为对已有功能的核心扩张,接口是主要是定义规范