Java 设计模式——工厂设计模式(创建型设计模式)进阶(建议详细阅读)

Java 设计模式——工厂设计模式(创建型设计模式)进阶(建议详细阅读),第1张

Java 设计模式——工厂设计模式(创建型设计模式)进阶(建议详细阅读)

原文地址:https://www.cnblogs.com/canglg/p/10456844.html

个人还是非常推荐这篇文章的,特别是后半段的一些设计思想,可以结合好到spring框架中使用,后续会出实战篇的文章

工厂方法模式

《设计模式之禅》中用神话故事女娲造人来比概述工厂方法模式。

女娲采集黄土捏成人的形状,然后放到八卦炉中烧制,最后放置到大地上生长,工艺过程是没有错的,但是意外随时都会发生:

第一次烤泥人,感觉应该熟了,往大地上一放,哇,没烤熟!于是一个白人诞生了!(这也是缺乏经验的最好证明。)

第二次烤泥人,上一次没烤熟,这次多烤一会儿,放到世间一看,嘿,熟过头了,于是黑人诞生了!

第三次烤泥人,一边烧制一边察看,直到表皮微黄,嘿,刚刚好,于是黄色人种出现 了!

该过程涉及三个对象:女娲、八卦炉、三种不同肤色的人。女娲可以使用场景类Client来表示,八卦炉类似于一个工厂,负责制造生产产品(即人类),三种不同肤色的人,他们都是同一个接口下不同实现类,都是人,只是肤色语言不同,对于八卦炉来说都是它生产出的产品。分析出如下所示类图:


类图比较简单,AbstractHumanFactory是一个抽象类,定义了一个八卦炉具有的整体功能,HumanFactory为实现类,完成具体的任务——创建人类;Human接口是人类的总称,其三个实现类分别为三类人种;NvWa类是一个场景类,负责模拟这个场景,执行相关的任务。

我们定义的没个人中都有两个方法:getColor(获得人的皮肤颜色)和talk(交谈),代码如下:

//人类接口
public interface Human{
    //没个人种的皮肤都有相应的颜色
    public void getColor();
    //人类会说话
    public void talk();
}

接口Human是对人类的总称,每个人种都至少具有两个方法,黑色人种、黄色人种、白色人种的代码如下:

//黑色人种
public class BlackHuman implements Human{
    public void getColor(){
        System.out.println("黑色人种的皮肤颜色是黑色的!");
    }
    public void talk(){
        System.out.println("黑人会说话,一般人听不懂!");
    }
}

//黄色人种
public class YellowHuman implements Human{
    public void getColor(){
        System.out.println("黄色人种的皮肤颜色是黄色的!");
    }
    public void talk(){
        System.out.println("黄色人种会说话,一般说的 都是双字节!");
    }
}

//白色人种
public class WhiteHuman implements Human{
    public void getColor(){
        System.out.println("白色人种的皮肤颜色是白色的!");
    }
    public void talk(){
        System.out.println("白色人种会说话,一般都是单字节!");
    }
}
//创建人类抽象工厂
public abstract class AbstractHumanFactory{
    //这里采用了泛型,对createHuman的输入参数产生两层限制
    //1.必须是Class类型
    //2.必须是Human的实现类
    public abstract  T createHuman(Class c);
}

//创建人类工厂
public class HumanFactory extends AbstractHumanFactory{
    public  T createHuman(Class c){
        //定义一个生产的人种
        Human human = null;
        try{
            //产生一个人种
            human = (T)Class.forName(c.getName()).newInstance();
        }catch(Exception e){
            System.out.println("人种生成错误!");
        }
        return (T)human;
    }
}
//女娲类
pubic class NvWa{
    public static void main(String[] args){
        //声明阴阳八卦炉
        AbstractHumanFactory YinYangLu = new HumanFactory();
        //女娲第一次造人,火候不足,于是白人产生了
        System.out.println("造出来的第一批人是白色人种");
        Human whiteHuman = YinYangLu.createHuman(WhiteHuman.class);
        whiteHuman.getColor();
        whiteHuman.talk();
        //女娲第二次造人, 火候过足, 于是黑人产生了
        System.out.println("造出的第二批人是黑色人种");
        Human blackHuman = YinYangLu.createHuman(BlackHuman.class);
        blackHuman.getColor();
        blackHuman.talk();
        //第三次造人, 火候刚刚好, 于是黄色人种产生了
        System.out.println("造出的第三批人是黄色人种");
        Human yellowHuman = YinYangLu.createHuman(YellowHuman.class);
        yellowHuman.getColor();
        yellowHuman.talk();
    }
}

工厂方法模式的定义

工厂模式使用的频率非常高,在我们日常的开发中总能见到它的身影。其定义为:

Define an interface for creating an object, but let subclass decide which class to instantiate. Factory Method lets a class defer instantiate to subclasses(定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法是一个类的实例化延迟到其子类)

工厂模式的通用类图如下:

在工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事物最抽象的定义;Creator为抽象创建类,也就是抽象工厂,具体如何创建产品类是由具体实现工厂ConcreteCreator完成的。

工厂方法模式常见的应用场景

1.工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度。

​ 2.需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。万物皆对象,那万物也就皆产品类,例如需要设计一个连接邮件服务器的框架,有三种网络协议可供选择:POP3、IMAP、HTTP,我们就可以把这三种连接方法作为产品类,定义一个接口如IConnectMail,然后定义对邮件的 *** 作方法,用不同的方法实现三个具体的产品类(也就是连接方式)再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式。如此设计,我们做到完美的扩展,如某些邮件服务器提供了WebService接口,很好,我们只要增加一个产品类就可以了。

​ 3.工厂方法模式可以用在异构项目中,例如通过WebService与一个非Java项目交互,虽然WebService号称是可以做到异构系统的同构化,但是在实际的开发中,还是会碰到很多问题,如类型问题、WSDL文件的支持问题,等等。从WSDL中产生的对象都认为是一个产品,然后由一个具体的工厂类进行管理,减少与外围系统的耦合。

​ 4.可以使用在测试驱动开发的框架下。例如,测试一个类A,就需要把与类A有关联关系的类B也同时产生出来,我们可以使用工厂方法模式把类B虚拟出来,避免类A与类B的耦合。目前由于JMock和EasyMock的诞生,改使用场景已经弱化了,遇到这种情况时直接考虑使用JMock或EasyMock。

工厂方法模式的优点

1.良好的封装性,代码结构清晰。一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要知道这个产品的类名(或约束字符串)就可以了,不用知道创建对象的艰辛过程,降低模块间的耦合。

2.工厂方法模式的扩展性非常优秀。在增加产品类的情况下,只要适当地修改具体的工厂类或扩展一个工厂类,就可以完成"拥抱变化"。例如在我们的例子中,需要增加一个棕色人种,则只需添加一个BrownHuman类,工厂类不用任何修改就可完成系统扩展。

​ 3.屏蔽产品类。这一特点非常重要,产品类的实现如何变化,调用者都不需要关心,他只需关心产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。因为产品类的实例化工作是有工厂类负责的,一个产品对象具体由哪一个产品生成是由工厂类决定的。在数据库开发中,能够深切的体会到工厂方法模式的好处:如果使用JDBC连接数据库。数据库从MySQL切换到Oracle,需要改动的地方就是切换一下数据库驱动(前提条件是SQL语句是标准语句),其他的都不需要修改,这是工厂方法模式灵活性的一个直接实例。

​ 4.工厂方法模式是典型的解耦框架。高层模块值需要知道产品的抽象类,其他的实现类都不用关心,符合迪米特法则,不需要的就不要去交流;也符合依赖倒置原则,只依赖产品的抽象,当然也符合里氏替换原则,使用产品子类替换产品父类。

单例模式的代码实现
//抽象产品类
public abstract class Product{
    //产品类的公共方法
    public void method1(){
        //业务逻辑处理
    }
    //抽象方法
    public abstract void method2();
}

具体的产品类。可以有多个,都继承于抽象产品类。其代码如下:

//产品1继承自抽象产品类,实现抽象方法
public class ConcreteProduct1 extends Product{
    public void method2(){
        //具体的业务逻辑
    }
}
//产品2继承自抽象产品类,实现抽象方法
public class ConcreteProduct2 extends Product{
    public void method2(){
        //具体的业务逻辑
    }
}

抽象工厂类负责定义产品对象的产生,代码如下:

//抽象工厂类
public abstract class Creator{
    //创建一个产品对象,其输入参数类型可以自行设置。
    //通常为String、Enum、Class等,也可以为空
    public abstract  T createProduct(Class c);
}

具体如何产生一个产品的对象,是由具体的工厂类实现的,代码如下:

//具体工厂类
public class ConcreteCreator extends Creator{
    public  T createProduct(Class c){
        Product product = null;
        try{
            product = (Product)Class.forName(c.getName()).newInstance();    
        }catch(Exception e){
            //异常处理
        }
        return (T)product;
    }
}

场景类的调用方法代码如下:

//场景类
public class Client{
    public static void main(String[] args){
        Creator creator = new ConcreteCreator();
        Product product = creator.createProduct(ConcreteProduct1.class);
        //继续业务逻辑
    }
}

该通用代码是一个比较实用、易于扩展的框架,读者可以根据实际项目需要进行扩展

工厂方法模式的扩展

厂方法模式有很多扩展,而且与其他模式结合实用威力更大。下面是工厂方法模式的四种扩展。

缩小为简单工厂模式

我们这样考虑一个问题,一个模块仅需要一个工厂类,没有必要把它生产出来,使用静态的方法就可以了,根据这一要求,我们把上例中的AbstractHumanFactory修改一下,类图如下:

在类图中去掉了AbstractHumanFactory抽象类,同时把createHuman方法设置为静态类型,简化了类的创建过程,变更的代码仅仅是HumanFactory和NvWa类,HumanFactory代码如下:

//简单工厂模式中的工厂类
public class HumanFactory{
    public static  T createHuman(Class c){
        //定义一个生产的人种
        Human human = null;
        try{
            //产生一个人种
            human = (T)Class.forName(c.getName()).newInstance();
        }catch(Exception e){
            System.out.println("人种生成错误!");
        }
        return (T)human;
    }
}

HumanFactory类仅有两个地方发生变化:去掉继承抽象类,并在createHuman前增加static关键字:工厂类发生变化,同时也引起了调用者NvWa的变化。代码如下:

//女娲类
pubic class NvWa{
    public static void main(String[] args){
        //女娲第一次造人,火候不足,于是白人产生了
        System.out.println("造出来的第一批人是白色人种");
        Human whiteHuman = HumanFactory.createHuman(WhiteHuman.class);
        whiteHuman.getColor();
        whiteHuman.talk();
        //女娲第二次造人, 火候过足, 于是黑人产生了
        System.out.println("造出的第二批人是黑色人种");
        Human blackHuman = HumanFactory.createHuman(BlackHuman.class);
        blackHuman.getColor();
        blackHuman.talk();
        //第三次造人, 火候刚刚好, 于是黄色人种产生了
        System.out.println("造出的第三批人是黄色人种");
        Human yellowHuman = HumanFactory.createHuman(YellowHuman.class);
        yellowHuman.getColor();
        yellowHuman.talk();
    }
}

运行结果没有发生变化,但是类图变简单了,而且调用者也比较简单,该模式是工厂方法模式的弱化,因为简单,所以称为简单工厂模式(Simple Factory Pattern),也叫作静态工厂模式。在实际项目中,采用该该方法的案例还是比较多的,其缺点是工厂类扩展比较困难,不符合开闭原则,但它仍然是一个非常实用的设计模式。

升级为多个工厂类

当我们在做一个比较复杂的项目时,经常会遇到初始化一个对象很耗费精力的情况,所有的产品类都放在一个工厂方法中进行初始化会使代码结构不清晰。例如,一个产品类有5个具体实现,每个实现类的初始化(不仅仅是new,初始化包括new一个对象,并对对象设置一定的初始值)方法都不相同,如果写在一个工厂方法中,势必会导致该方法巨大无比

考虑到需要结构清晰,我们就为每个产品定义一个创造者,然后由调用者自己去选择与那个工厂方法关联。我们还是以女娲造人为例,每个人种都有一个固定的八卦炉,分别造出黑色人种、白色人种、黄色人种,修改后类图如下:

每个人种(具体的产品类)都对应了一个创建者,每个创建者都独立负责创建对应的产品对象,非常符合单一职责原则,按照这种模式代码如下:

//多工厂模式的抽象工厂类
public abstract class AbstractHumanFactory{
    //抽象方法中已经不再需要传递相关参数了,因为每一个具体的工厂都已经非常明确自己的职责:创建自己负责的产品类对象。
    public abstract Human createHuman();
}

//黑色人种的创建工厂实现
public class BlackHumanFactory extends AbstractHumanFactory{
    public Human createHuman(){
        return new BlackHuman();
    }
}

//黄色人种的创建工厂实现
public class YellowHumanFactory extends AbstractHumanFactory{
    public Human createHuman(){
        return new YellowHuman();
    }
}

//白色人种的创建工厂实现
public class WhiteHumanFactory extends AbstractHumanFactory{
    public Human createHuman(){
        return new WhiteHuman();
    }
}

三个具体的创建工厂都非常简单,但是,如果一个系统比较复杂时工厂类也会相应地变复杂。场景类NvWa修改后的代码如下:

//场景类NvWa
public class NvWa{
    public static void main(String[] args){
       //女娲第一次造人,火候不足,于是白色人种产生了
        System.out.println("造出的第一批人是白色人种");
        Human whiteHuman = (new WhiteHumanFactory()).createHuman();
        whiteHuman.getColor();
        whiteHuman.talk();
        //女娲第二次造人,火候过足,于是黑色人种产生了
        System.out.println("造出的第二批人是黑色人种");
        Human blackHuman = (new BlackHumanFactory()).createHuman();
        blackHuman.getColor();
        blackHuman.talk();
          //女娲第三次造人,火候刚刚好,于是黄色人种产生了
        System.out.println("造出的第三批人是黄色人种");
        Human yellowHuman = (new YellowHumanFactory()).createHuman();
        yellowHuman.getColor();
        yellowHuman.talk();
    }
}

运行结果还是相同。每一个产品类都对应了一个创建类,好处就是创建类的职责清晰,而且结构简单,但是给可扩展性和可维护性带来了一定的影响。如果要扩展一个产品类,就需要建立一个相应的工厂类,这样就增加了扩展的难度。因为工厂类和产品类的数量相同,维护时需要考虑两个对象之间的关系。当然,在复杂的应用中一般采用多工厂的方法,然后再增加一个协调类,避免调用者与 各个子工厂交流,协调类的作用是封装子工厂类,对高层模块提供统一的访问接口。

替代单例模式

单例模式以及多例模式都有一定的缺点,我们可以采用工厂方法模式来实现单例的功能。单例模式的核心要求就是在内存中只有一个对象,通过工厂方法模式也可以只在内存中生产一个对象。类图如下:

Singleton定义了一个private的无参构造函数,目的是不允许通过new的方式创建一个对象,代码如下:

//单例类
public class Singleton{
  //不允许通过new产生一个对象
  private Singleton(){
  }
  public void doSomething(){
    //业务处理
  }
}

Singleton保证不能通过正常的渠道建立一个对象,那么SingletonFactory需要通过反射的方式创建。

//负责生成单例的工厂类
public class SingletonFactory{
  private static Singleton singleton;
  static{
    try{
      Class cl = Class.forName(Singleton.class.getName());
      //获得无参构造
      Constructor constructor = cl.getDeclaredConstructor();
      //设置无参构造是可访问的
      constructor.setAccessible(true);
      //产生一个实例对象
      singleton = (Singleton)constructor.newInstance();
    }catch(Exception e){
      //异常处理
    }
  }
  public static Singleton getSingleton(){
    return singleton;
  }
}

通过获得类构造器,然后设置访问权限,生成一个对象,然后提供外部访问,保证内存中对象的唯一。当然其他类也可以通过反射的方式建立一个单例对象。

以上通过工厂方法模式创建了一个单例对象,该框架可以继续扩展,在一个项目中可以产生一个单例构造器,所有需要产生单例的类都遵循一定的规则(构造方法是private),然后通过扩展该框架,只需输入一个类型就可以获取唯一的实例。

延迟初始化 推荐阅读

何为延迟初始化(Lazy initialization)?一个对象被消费完毕后,并不立刻释放,工厂类保持其初始化状态,等待再次被使用。延迟初始化是工厂方法模式的一个扩展应用,其通用类图如下:

ProductFactory负责产品类对象的创建工作,并且通过prMap变量产生一个缓存,对需要再次被重用的对象保留,Product和ConcreteProduct是一个示例代码,代码如下:

ProductFactory代码如下:

//延迟加载的工厂类
public class ProductFactory{
  private static final Map prMap = new HashMap();
  public static synchronized Product createProduct(String type) throws Exception(){
    Product product = null;
    //如果Map中已经有这个对象
    if(prMap.containsKey(type)){
      product = prMap.get(type);
    }else{
      if(type.equals("Product1")){
        product = new concreteProduct1();
      }else{
        product = new concreteProduct2();
      }
      //同时把对象放到缓存容器中
      prMap.put(type,product);
    }
    return product;
  }
}

代码比较简单,通过定义一个Map容器,容纳所有产生的对象,如果在Map容器中已经有的对象,则直接取出返回;如果没有,则根据需要的类型产生一个对象并放到Map容器中,以方便下次调用。

延迟加载框架是可以扩展的,例如限制某一个产品类的最大实例化数量,可以通过判断Map中已有的对象来实现,这样的处理是非常有意义的,例如JDBC连接数据库,都会要求设置一个MaxConnections最大连接数量,该数量就是内存中最大实例化的数量。

延迟加载还可以用在对象初始化比较复杂的情况下,例如硬件访问,涉及多方面的交互,则可以通过延迟加载降低对象的产生和销毁带来的复杂性。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5677424.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存