後雪寒的专栏

琴棋书画诗酒花茶

设计模式(19):创建型-简单工厂模式(Simple Factory)及工厂方法模式(Factory Method)

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

设计模式分为三种类型,共23种。
创建型模式(5):单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
结构型模式(7):适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
行为型模式(11):(父子类)策略模式、模版方法模式,(两个类)观察者模式、迭代器模式、职责链模式、命令模式,(类的状态)状态模式、备忘录模式,(中间类) 访问者模式、中介者模式、解释器模式。

  工厂模式是最常用的一类创建型设计模式,通常我们所说的工厂模式是指工厂方法模式,它也是使用频率最高的工厂模式。而简单工厂模式是工厂方法模式的“小弟”,它不属于GoF 23种设计模式,但在软件开发中应用也较为频繁,通常将它作为学习其他工厂模式的入门。此外,工厂方法模式还有一位“大哥”——抽象工厂模式。这三种工厂模式各具特色,难度也逐个加大,在软件开发中它们都得到了广泛的应用,成为面向对象软件中常用的创建对象的工具。

一.简单工厂模式(Simple Factory)

  简单工厂模式并不属于GoF 23个经典设计模式,但通常将它作为学习其他工厂模式的基础,它的设计思想很简单,其基本流程如下:
  首先将需要创建的各种不同对象(例如各种不同的Chart对象)的相关代码封装到不同的类中,这些类称为具体产品类,而将它们公共的代码进行抽象和提取后封装在一个抽象产品类中,每一个具体产品类都是抽象产品类的子类;然后提供一个工厂类用于创建各种产品,在工厂类中提供一个创建产品的工厂方法,该方法可以根据所传入的参数不同创建不同的具体产品对象;客户端只需调用工厂类的工厂方法并传入相应的参数即可得到一个产品对象。

定义

  简单工厂模式(Simple Factory Pattern):定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。
  简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。简单工厂模式结构比较简单,其核心是工厂类的设计,其结构如图1所示:
这里写图片描述

结构

在简单工厂模式结构图中包含如下几个角色:

  • Factory(工厂角色):工厂角色即工厂类,它是简单工厂模式的核心,负责实现创建所有产品实例的内部逻辑;工厂类可以被外界直接调用,创建所需的产品对象;在工厂类中提供了静态的工厂方法factoryMethod(),它的返回类型为抽象产品类型Product。
  • Product(抽象产品角色):它是工厂类所创建的所有对象的父类,封装了各种产品对象的公有方法,它的引入将提高系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类对象。
  • ConcreteProduct(具体产品角色):它是简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例。每一个具体产品角色都继承了抽象产品角色,需要实现在抽象产品中声明的抽象方法。
      在简单工厂模式中,客户端通过工厂类来创建一个产品类的实例,而无须直接使用new关键字来创建对象,它是工厂模式家族中最简单的一员。

实现

  在使用简单工厂模式时,首先需要对产品类进行重构,不能设计一个包罗万象的产品类,而需根据实际情况设计一个产品层次结构,将所有产品类公共的代码移至抽象产品类,并在抽象产品类中声明一些抽象方法,以供不同的具体产品类来实现,典型的抽象产品类代码如下所示:
  

public abstract class Product {    
    //所有产品类的公共业务方法    
    public void methodSame() {    
        //公共方法的实现    
    }    

    //声明抽象业务方法    
    public abstract void methodDiff();    
}   

在具体产品类中实现了抽象产品类中声明的抽象业务方法,不同的具体产品类可以提供不同的实现,典型的具体产品类代码如下所示:

public class ConcreteProduct extends Product {    
    //实现业务方法    
    public void methodDiff() {    
        //业务方法的实现    
    }    
}    

简单工厂模式的核心是工厂类,在没有工厂类之前,客户端一般会使用new关键字来直接创建产品对象,而在引入工厂类之后,客户端可以通过工厂类来创建产品,在简单工厂模式中,工厂类提供了一个静态工厂方法供客户端使用,根据所传入的参数不同可以创建不同的产品对象,典型的工厂类代码如下所示:

public class Factory {    
    //静态工厂方法    
    public static Product getProduct(String arg) {    
        Product product = null;    
        if (arg.equalsIgnoreCase("A")) {    
            product = new ConcreteProductA();    
            //初始化设置product    
        }    
        else if (arg.equalsIgnoreCase("B")) {    
            product = new ConcreteProductB();    
            //初始化设置product    
        }    
        return product;    
    }    
}    

在客户端代码中,我们通过调用工厂类的工厂方法即可得到产品对象,典型代码如下所示:

 public class Client {    
    public static void main(String args[]) {    
        Product product;     
        product = Factory.getProduct("A"); //通过工厂类创建产品对象    
        product.methodSame();    
        product.methodDiff();    
    }    
}   

图表库的设计

Sunny软件公司欲基于Java语言开发一套图表库,该图表库可以为应用系统提供各种不同外观的图表,例如柱状图、饼状图、折线图等。Sunny软件公司图表库设计人员希望为应用系统开发人员提供一套灵活易用的图表库,而且可以较为方便地对图表库进行扩展,以便能够在将来增加一些新类型的图表。
为了将Chart类的职责分离,同时将Chart对象的创建和使用分离,Sunny软件公司开发人员决定使用简单工厂模式对图表库进行重构,重构后的结构如图所示:
这里写图片描述
在图中,Chart接口充当抽象产品类,其子类HistogramChart、PieChart和LineChart充当具体产品类,ChartFactory充当工厂类。完整代码如下所示:

//抽象图表接口:抽象产品类    
public interface Chart {    
    public void display();    
}    

//柱状图类:具体产品类    
class HistogramChart implements Chart {    
    public HistogramChart() {    
        System.out.println("创建柱状图!");    
    }    

    public void display() {    
        System.out.println("显示柱状图!");    
    }    
}    

//饼状图类:具体产品类    
class PieChart implements Chart {    
    public PieChart() {    
        System.out.println("创建饼状图!");    
    }    

    public void display() {    
        System.out.println("显示饼状图!");    
    }    
}    

//折线图类:具体产品类    
class LineChart implements Chart {    
    public LineChart() {    
        System.out.println("创建折线图!");    
    }    

    public void display() {    
        System.out.println("显示折线图!");    
    }    
}    

//图表工厂类:工厂类    
public class ChartFactory {    
    //静态工厂方法    
    public static Chart getChart(String type) {    
        Chart chart = null;    
        if (type.equalsIgnoreCase("histogram")) {    
            chart = new HistogramChart();    
            System.out.println("初始化设置柱状图!");    
        }    
        else if (type.equalsIgnoreCase("pie")) {    
            chart = new PieChart();    
            System.out.println("初始化设置饼状图!");    
        }    
        else if (type.equalsIgnoreCase("line")) {    
            chart = new LineChart();    
            System.out.println("初始化设置折线图!");                
        }    
        return chart;    
    }    
}   

编写如下客户端测试代码:

public class Client {    
    public static void main(String args[]) {  
        //通过静态工厂方法创建产品      
        Chart chart = ChartFactory.getChart("histogram"); 
        chart.display();    
    }    
}   

输出结果如下:

创建柱状图!
初始化设置柱状图!
显示柱状图!

  在客户端测试类中,我们使用工厂类的静态工厂方法创建产品对象,如果需要更换产品,只需修改静态工厂方法中的参数即可。

模式的简化

  有时候,为了简化简单工厂模式,我们可以将抽象产品类和工厂类合并,将静态工厂方法移至抽象产品类中,如图3所示:
这里写图片描述
  在图中,客户端可以通过产品父类的静态工厂方法,根据参数的不同创建不同类型的产品子类对象,这种做法在JDK等类库和框架中也广泛存在。

模式总结

简单工厂模式提供了专门的工厂类用于创建对象,将对象的创建和对象的使用分离开,它作为一种最简单的工厂模式在软件开发中得到了较为广泛的应用。
1. 主要优点
(1) 工厂类包含必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的职责,而仅仅“消费”产品,简单工厂模式实现了对象创建和使用的分离。
(2) 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以在一定程度减少使用者的记忆量。
(3) 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
2. 主要缺点
(1) 由于工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响。
(2) 使用简单工厂模式势必会增加系统中类的个数(引入了新的工厂类),增加了系统的复杂度和理解难度。
(3) 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
(4) 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。
3. 适用场景
在以下情况下可以考虑使用简单工厂模式:
(1) 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
(2) 客户端只知道传入工厂类的参数,对于如何创建对象并不关心。
练习
使用简单工厂模式设计一个可以创建不同几何形状(如圆形、方形和三角形等)的绘图工具,每个几何图形都具有绘制draw()和擦除erase()两个方法,要求在绘制不支持的几何图形时,提示一个UnSupportedShapeException。

二.工厂方法模式(Factory Method)

  在简单工厂模式中只提供一个工厂类,该工厂类处于对产品类进行实例化的中心位置,它需要知道每一个产品对象的创建细节,并决定何时实例化哪一个产品类。简单工厂模式最大的缺点是当有新产品要加入到系统中时,必须修改工厂类,需要在其中加入必要的业务逻辑,这违背了“开闭原则”。此外,在简单工厂模式中,所有的产品都由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,具体产品与工厂类之间的耦合度高,严重影响了系统的灵活性和扩展性,而工厂方法模式则可以很好地解决这一问题,下面将介绍第二种工厂模式——工厂方法模式。

定义

  在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构。工厂方法模式定义如下:
   工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。工厂方法模式是一种类创建型模式。
   Factory Method Pattern:Define an interface for creating an object,but let subclass decide which class to instantiate.Factory Method lets a class defer instantiation to subclass.

结构

  工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来具体实现工厂方法,创建具体的产品对象。工厂方法模式结构如图所示:
这里写图片描述
在工厂方法模式结构图中包含如下几个角色:

  • Product(抽象产品):它是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的公共父类。
  • ConcreteProduct(具体产品):它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应。
  • Factory(抽象工厂):在抽象工厂类中,声明了工厂方法(Factory Method),用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。
  • ConcreteFactory(具体工厂):它是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例。

实现

  与简单工厂模式相比,工厂方法模式最重要的区别是引入了抽象工厂角色,抽象工厂可以是接口,也可以是抽象类或者具体类,其典型代码如下所示:
  

public interface Factory {    
    public Product factoryMethod();    
}    

在抽象工厂中声明了工厂方法但并未实现工厂方法,具体产品对象的创建由其子类负责,客户端针对抽象工厂编程,可在运行时再指定具体工厂类,具体工厂类实现了工厂方法,不同的具体工厂可以创建不同的具体产品,其典型代码如下所示:

public class ConcreteFactory implements Factory {    
    public Product factoryMethod() {    
        return new ConcreteProduct();    
    }    
}   

在实际使用时,具体工厂类在实现工厂方法时除了创建具体产品对象之外,还可以负责产品对象的初始化工作以及一些资源和环境配置工作,例如连接数据库、创建文件等。
在客户端代码中,只需关心工厂类即可,不同的具体工厂可以创建不同的产品,典型的客户端类代码片段如下所示:

……    
Factory factory;    
factory = new ConcreteFactory(); //可通过配置文件实现    
Product product;    
product = factory.factoryMethod();    
……    

三.日志记录器的设计

  Sunny软件公司欲开发一个系统运行日志记录器(Logger),该记录器可以通过多种途径保存系统的运行日志,如通过文件记录或数据库记录,用户可以通过修改配置文件灵活地更换日志记录方式。在设计各类日志记录器时,Sunny公司的开发人员发现需要对日志记录器进行一些初始化工作,初始化参数的设置过程较为复杂,而且某些参数的设置有严格的先后次序,否则可能会发生记录失败。如何封装记录器的初始化过程并保证多种记录器切换的灵活性是Sunny公司开发人员面临的一个难题。
  Sunny公司开发人员决定使用工厂方法模式来设计日志记录器,其基本结构如图所示:
这里写图片描述
在图中,Logger接口充当抽象产品,其子类FileLogger和DatabaseLogger充当具体产品,LoggerFactory接口充当抽象工厂,其子类FileLoggerFactory和DatabaseLoggerFactory充当具体工厂。完整代码如下所示:

//日志记录器接口:抽象产品    
public interface Logger {    
    public void writeLog();    
}    

//数据库日志记录器:具体产品    
class DatabaseLogger implements Logger {    
    public void writeLog() {    
        System.out.println("数据库日志记录。");    
    }    
}    

//文件日志记录器:具体产品    
class FileLogger implements Logger {    
    public void writeLog() {    
        System.out.println("文件日志记录。");    
    }    
}    

//日志记录器工厂接口:抽象工厂    
public interface LoggerFactory {    
    public Logger createLogger();    
}    

//数据库日志记录器工厂类:具体工厂    
class DatabaseLoggerFactory implements LoggerFactory {    
    public Logger createLogger() {    
            //连接数据库,代码省略    
            //创建数据库日志记录器对象    
            Logger logger = new DatabaseLogger();     
            //初始化数据库日志记录器,代码省略    
            return logger;    
    }       
}    

//文件日志记录器工厂类:具体工厂    
class FileLoggerFactory implements LoggerFactory {    
    public Logger createLogger() {    
            //创建文件日志记录器对象    
            Logger logger = new FileLogger();     
            //创建文件,代码省略    
            return logger;    
    }       
}   

客户端测试代码:

public class Client {    
    public static void main(String args[]) {  
        LoggerFactory factory = new FileLoggerFactory(); //可引入配置文件实现    
        Logger logger = factory.createLogger();    
        logger.writeLog();    
    }    
}  

输出结果如下:


文件日志记录。

 
  为了让系统具有更好的灵活性和可扩展性,Sunny公司开发人员决定对日志记录器客户端代码进行重构,使得可以在不修改任何客户端代码的基础上更换或增加新的日志记录方式。
  在客户端代码中将不再使用new关键字来创建工厂对象,而是将具体工厂类的类名存储在配置文件(如XML文件)中,通过读取配置文件获取类名字符串,再使用Java的反射机制,根据类名字符串生成对象。在整个实现过程中需要用到两个技术:Java反射机制与配置文件读取。软件系统的配置文件通常为XML文件,我们可以使用DOM (Document Object Model)、SAX (Simple API for XML)、StAX (Streaming API for XML)等技术来处理XML文件。关于DOM、SAX、StAX等技术的详细学习大家可以参考其他相关资料,在此不予扩展。  

四.重载的工厂方法

  Sunny公司开发人员通过进一步分析,发现可以通过多种方式来初始化日志记录器,例如可以为各种日志记录器提供默认实现;还可以为数据库日志记录器提供数据库连接字符串,为文件日志记录器提供文件路径;也可以将参数封装在一个Object类型的对象中,通过Object对象将配置参数传入工厂类。此时,可以提供一组重载的工厂方法,以不同的方式对产品对象进行创建。当然,对于同一个具体工厂而言,无论使用哪个工厂方法,创建的产品类型均要相同。如图所示:
这里写图片描述
  引入重载方法后,抽象工厂LoggerFactory的代码修改如下:

interface LoggerFactory {    
    public Logger createLogger();    
    public Logger createLogger(String args);    
    public Logger createLogger(Object obj);    
}   

具体工厂类DatabaseLoggerFactory代码修改如下:

class DatabaseLoggerFactory implements LoggerFactory {    
    public Logger createLogger() {    
            //使用默认方式连接数据库,代码省略    
            Logger logger = new DatabaseLogger();     
            //初始化数据库日志记录器,代码省略    
            return logger;    
    }    

    public Logger createLogger(String args) {    
            //使用参数args作为连接字符串来连接数据库,代码省略    
            Logger logger = new DatabaseLogger();     
            //初始化数据库日志记录器,代码省略    
            return logger;    
    }       

    public Logger createLogger(Object obj) {    
            //使用封装在参数obj中的连接字符串来连接数据库,代码省略    
            Logger logger = new DatabaseLogger();     
            //使用封装在参数obj中的数据来初始化数据库日志记录器,代码省略    
            return logger;    
    }       
}    

//其他具体工厂类代码省略   

在抽象工厂中定义多个重载的工厂方法,在具体工厂中实现了这些工厂方法,这些方法可以包含不同的业务逻辑,以满足对不同产品对象的需求。

五.工厂方法的隐藏

  有时候,为了进一步简化客户端的使用,还可以对客户端隐藏工厂方法,此时,在工厂类中将直接调用产品类的业务方法,客户端无须调用工厂方法创建产品,直接通过工厂即可使用所创建的对象中的业务方法。
  如果对客户端隐藏工厂方法,日志记录器的结构图将修改为图所示:
  这里写图片描述
  抽象工厂类LoggerFactory的代码修改如下:
  

//改为抽象类    
abstract class LoggerFactory {    
    //在工厂类中直接调用日志记录器类的业务方法writeLog()    
    public void writeLog() {    
        Logger logger = this.createLogger();    
        logger.writeLog();    
    }    

    public abstract Logger createLogger();      
}    

客户端代码修改如下:

class Client {    
    public static void main(String args[]) {    
        LoggerFactory factory;    
        factory = (LoggerFactory)XMLUtil.getBean();    
        factory.writeLog(); //直接使用工厂对象来调用产品对象的业务方法    
    }    
}    

  通过将业务方法的调用移入工厂类,可以直接使用工厂对象来调用产品对象的业务方法,客户端无须直接使用工厂方法,在某些情况下我们也可以使用这种设计方案。

六.工厂方法模式总结

  工厂方法模式是简单工厂模式的延伸,它继承了简单工厂模式的优点,同时还弥补了简单工厂模式的不足。工厂方法模式是使用频率最高的设计模式之一,是很多开源框架和API类库的核心模式。

1. 主要优点
工厂方法模式的主要优点如下:
(1) 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
(2) 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够让工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,就正是因为所有的具体工厂类都具有同一抽象父类。
(3) 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了,这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。

2. 主要缺点
工厂方法模式的主要缺点如下:
(1) 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
(2) 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

3. 适用场景
在以下情况下可以考虑使用工厂方法模式:
(1) 客户端不知道它所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。
(2) 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
练习
使用工厂方法模式设计一个程序来读取各种不同类型的图片格式,针对每一种图片格式都设计一个图片读取器,如GIF图片读取器用于读取GIF格式的图片、JPG图片读取器用于读取JPG格式的图片。需充分考虑系统的灵活性和可扩展性。

以上内容来自于博文:
JAVA设计模式(24):创建型-工厂模式【工厂方法模式】(Factory Method)
JAVA设计模式(01):创建型-工厂模式【简单工厂模式】(Simple Factory)
【作者:刘伟(Sunny) http://blog.csdn.net/lovelion
参考电子书下载:设计模式的艺术–软件开发人员内功修炼之道_刘伟(2013年).pdf

《道德经》第二十二章:
曲则全,枉则直,洼则盈,敝则新,少则得,多则惑。是以圣人抱一为天下式。不自见,故明;不自是,故彰,不自伐,故有功;不自矜,故长。夫唯不争,故天下莫能与之争。古之所谓”曲则全”者,岂虚言哉?诚全而归之。
译文:委曲便会保全,屈枉便会直伸;低洼便会充盈,陈旧便会更新;少取便会获得,贪多便会迷惑。所以有道的人坚守这一原则作为天下事理的范式,不自我表扬,反能显明;不自以为是,反能是非彰明;不自己夸耀,反能得有功劳;不自我矜持,所以才能长久。正因为不与人争,所以遍天下没有人能与他争。古时所谓“委曲便会保全”的话,怎么会是空话呢?它实实在在能够达到。

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/houxuehan/article/details/79976373
个人分类: 设计模式
上一篇设计模式(18):结构型-享元模式(Flyweight)
下一篇设计模式(20):创建型-抽象工厂模式(Abstract Factory)
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭