返回 导航

其他

hangge.com

Java - GoF设计模式详解2(简单工厂、工厂方法、抽象工厂模式)

作者:hangge | 2023-02-24 09:38

二、工厂模式

1,基本介绍

(1)工厂模式是一种创建型的面向对象设计模式,目的将创建对象的具体过程包装起来,从而达到更高的灵活性。工厂模式的本质就是用工厂方法代替 new 操作创建一种实例化对象的方式,以提供一种方便地创建有同种类型接口的产品的复杂对象。
(2)工厂模式可以细分如下三类:
  • 简单工厂模式Simple Factory) 
  • 工厂方法模式Factory Method) 
  • 抽象工厂模式Abstract Factory

2,简单工厂模式

(1)简单工厂模式(Simple Factory)又叫做静态工厂方法(Static Factory Method)模式,但不属于 23 GOF 设计模式之一。简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。
注意:虽然简单工厂模式实现了对象的创建和对象的使用分离,但增加新的具体产品需要修改工厂类的判断逻辑代码,违背开闭原则。

(2)该模式中包含的角色及其职责如下:
  • 工厂(Creator)角色:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
  • 抽象产品(Product)角色:简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
  • 具体产品(Concrete Product)角色:是简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。

(3)下面通过创建苹果和华为这两种类型的手机为例,演示简单工厂模式的具体实现。首先我们定义产品接口:
public interface Phone {
  public String info();
}

(4)然后定义华为手机和苹果手机这两个具体实现类:
public class HuaweiPhone implements Phone{
  @Override
  public String info() {
    return "我是华为手机";
  }
}

public class ApplePhone implements Phone{
  @Override
  public String info() {
    return "我是苹果手机";
  }
}

(5)接着定义一个工厂类,工厂类有一个方法 createPhone(),用来根据不同的参数实例化不同品牌的手机类并返回。
public class SimpleFactory {
  public Phone createPhone(String name) {
    if (name.equals("Huawei")) {
      return new HuaweiPhone();
    } else if (name.equals("Apple")) {
      return new ApplePhone();
    } else {
      return null;
    }
  }
}

(6)最后我们使用工厂类来创建不同的产品实例,对调用者来说屏蔽了实例化的细节:
public class Test {
  public static void main(String[] args) {
    SimpleFactory simpleFactory = new SimpleFactory();

    Phone phone1 = simpleFactory.createPhone("Apple");
    System.out.println(phone1.info());

    Phone phone2 = simpleFactory.createPhone("Huawei");
    System.out.println(phone2.info());
  }
}

3,工厂方法模式

(1)工厂方法模式(Factory Method)提供了一种延迟创建类的方法,使用这个方法可以在运行期由子类决定创建哪一个类的实例。简单来说就是父类定义一个创建对象的接口,但由子类决定需要实例化哪一个类。
注意:使用工厂方法模式,新增一个产品时只需多写一个相应的具体工厂类和具体产品类,无须修改原有的代码,不会违法开闭原则。

(2)该模式中包含的角色及其职责如下:
  • 抽象工厂(Creator)角色:声明了工厂方法,该方法返回一个 Product 类型的对象。Creator 也可以定义一个工厂方法的缺省实现,它返回一个缺省的 ConcreteProduct 对象。
  • 具体工厂(Concrete Creator)角色:重定义了工厂方法,以返回一个 ConcreteProduct 实例。
  • 抽象产品(Product)角色:定义了 Factory Method 所创建的对象的接口
  • 具体产品(Concrete Product)角色:用于实现 Product 接口;

(3)下面同样通过创建苹果和华为这两种类型的手机为例,演示工厂方法模式的具体实现。首先我们定义产品接口:
public interface Phone {
  public String info();
}

(4)然后定义华为手机和苹果手机这两个具体实现类:
public class HuaweiPhone implements Phone{
  @Override
  public String info() {
    return "我是华为手机";
  }
}

public class ApplePhone implements Phone{
  @Override
  public String info() {
    return "我是苹果手机";
  }
}

(5)接着是定义工厂接口:
public interface PhoneFactory {
  public Phone createPhone();
}

(6)然后定义创建华为手机和苹果手机的两个工厂实现类:
public class HuaweiPhoneFactory implements PhoneFactory {
  @Override
  public Phone createPhone() {
    return new HuaweiPhone();
  }
}

public class ApplePhoneFactory implements PhoneFactory {
  @Override
  public Phone createPhone() {
    return new ApplePhone();
  }
}

(7)最后我们使用不同的工厂子类来创建不同的产品实例:
public class Test {
  public static void main(String[] args) {
    PhoneFactory applePhoneFactory = new ApplePhoneFactory();
    Phone phone1 = applePhoneFactory.createPhone();
    System.out.println(phone1.info());

    PhoneFactory huaweiPhoneFactory = new HuaweiPhoneFactory();
    Phone phone2 = huaweiPhoneFactory.createPhone();
    System.out.println(phone2.info());
  }
}

4,抽象工厂模式

(1)抽象工厂模式(Abstract Factory)提供了一个接口来创建一系列具有相似基类或相似接口的对象。简单来说工厂父类提供一个创建产品族的接口,每个子类可以创建一系列相关或者相互依赖的对象,而无需指定它们具体的类。
1,抽象工厂模式既符合也不符合开闭原则:
  • 如果增加产品族只需要增加相应的具体工厂类和具体产品类,不需要修改原有代码,符合开闭原则。
  • 如果产品族中需要增加一个新的产品,就需要修改所有的抽象工厂类和具体工厂类,不符合开闭原则。
2,工厂方法模式和抽象工厂模式区别:
  • 工厂方法模式只有一个抽象产品类和一个抽象工厂类,但可以派生出多个具体产品类和具体工厂类,每个具体工厂类只能创建一个具体产品类的实例。
  • 抽象工厂模式拥有多个抽象产品类(产品族)和一个抽象工厂类,同样可以派生出多个具体产品类和具体工厂类,不过每个具体工厂类可以创建多个具体产品类的实例
3,当抽象工厂模式中每一个具体工厂类只创建一个产品对象,抽象工厂模式退化成工厂方法模式。

(2)该该模式中包含的角色及其职责如下:
  • 抽象工厂Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
  • 具体工厂Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。

(3)当一个产品族中需要被设计在一起工作时,通过抽象工厂模式,能够保证客户端始终只使用同一个产品族中的对象。比如华为工厂既可以生产华为手机,也可以生产华为电脑,苹果同理。下面通过样例演示抽象工厂模式的具体实现。首先我们定义手机产品接口和具体实现类:
public interface Phone {
  public String info();
}

public class HuaweiPhone implements Phone{
  @Override
  public String info() {
    return "我是华为手机";
  }
}

public class ApplePhone implements Phone{
  @Override
  public String info() {
    return "我是苹果手机";
  }
}

(4)接着定义电脑产品接口和具体实现类:
public interface Computer {
  public String work();
}

public class HuaweiComputer implements Computer{
  @Override
  public String work() {
    return "使用华为电脑工作";
  }
}

public class AppleComputer implements Computer{
  @Override
  public String work() {
    return "使用苹果电脑工作";
  }
}

(5)接着是定义工厂接口:
public interface AbstractFactory {
  public Phone createPhone();
  public Computer createComputer();
}

(6)然后分别创建华为工厂和苹果工厂,实现工厂接口:
public class HuaweiFactory implements AbstractFactory {
  @Override
  public Phone createPhone() {
    return new HuaweiPhone();
  }

  @Override
  public Computer createComputer() {
    return new HuaweiComputer();
  }
}

public class AppleFactory implements AbstractFactory{
  @Override
  public Phone createPhone() {
    return new ApplePhone();
  }

  @Override
  public Computer createComputer() {
    return new AppleComputer();
  }
}

(7)最后我们分别使用两个工厂类来创建不同的产品实例:
public class Test {
  public static void main(String[] args) {
    AbstractFactory factory1 = new HuaweiFactory();
    Phone phone1 = factory1.createPhone();
    System.out.println(phone1.info());
    Computer computer1 = factory1.createComputer();
    System.out.println(computer1.work());

    AbstractFactory factory2 = new AppleFactory();
    Phone phone2 = factory2.createPhone();
    System.out.println(phone2.info());
    Computer computer2 = factory2.createComputer();
    System.out.println(computer2.work());
  }
}

附一:JDK 中的工厂模式

1,简单工厂模式

java.util.Calendar 类通过 getInstance() 方法提供了一种简单的工厂方式来创建 Calendar 对象。
注意:虽然 Calendar.getInstance() 名字看起来像是单例模式实际上并不是,因为每次调用 Calendar.getInstance() 都会返回一个新的日历对象。
// 获取的是当前时区和语言环境的日历对象
Calendar calendar1 = Calendar.getInstance();
System.out.println("当前时间1: " + calendar1.getTime());

// 获取指定时区和语言环境的日历对象
TimeZone tz = TimeZone.getTimeZone("UTC");
Locale locale = Locale.CHINA;
Calendar calendar2 = Calendar.getInstance(tz, locale);
System.out.println("当前时间2:" + calendar2.getTime());

2,工厂方法模式

(1)java.util.Collection 接口中定义了一个抽象的 iterator() 方法,该方法就是一个工厂方法。对于 iterator() 方法来说:
  • Collection 就是一个根抽象工厂,下面还有 List 等接口作为抽象工厂,再往下有 ArrayList 等具体工厂。
  • java.util.Iterator 接口是根抽象产品,下面有 ListIterator 等抽象产品,还有 ArrayListIterator 等作为具体产品。使用不同的具体工厂类中的 iterator 方法能得到不同的具体产品的实例。

(2)下面是一个简单的使用样例:
public class CollectionExample {
    public static void main(String[] args) {
        // 创建一个新的 ArrayList
        ArrayList<String> list = new ArrayList<>();
        list.add("apple");
        list.add("banana");
        list.add("cherry");
        
        // 获取该列表的迭代器
        Iterator<String> iterator = list.iterator();
        
        // 迭代该列表并打印每个元素
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

3,抽象工厂模式

(1)Java.sql 包中的 Connection 类就使用了抽象工厂模式,比如下面是一个使用 JDBC 链接数据库的简单示例:
try {  
    Connection con = null; // 定义一个MYSQL链接对象  
    Class.forName("com.mysql.jdbc.Driver").newInstance(); // MYSQL驱动  
    con = DriverManager.getConnection(  
            "jdbc:mysql://127.0.0.1:3306/test", "root", "root"); // 链接本地MYSQL  
  
    Statement stmt; // 创建声明  
    stmt = con.createStatement();  
  
    // 新增一条数据  
    stmt.executeUpdate("INSERT INTO user (username, password) VALUES ('hangge', '123456')");  
    ResultSet res = stmt.executeQuery("select LAST_INSERT_ID()");  
    // 代码省略  
} catch (Exception e) {  
    e.printStackTrace();  
}

(2)其中 DriverManager 中的 getConnection 方法如下。我们看到 getConnection(String, String, String) 函数调用了 getConnection(Stringurl, java.util.Propertiesinfo,Class<?>caller) 函数,在该函数中遍历以注册到 DriverManager 中的驱动,即 registeredDrivers, 获取相应的驱动之后,链接到数据库,最后将该链接返回, 这样就获取到了具体的 Connection
@CallerSensitive  
    public static Connection getConnection(String url,  
        String user, String password) throws SQLException {  
        java.util.Properties info = new java.util.Properties();  
  
        if (user != null) {  
            info.put("user", user);  
        }  
        if (password != null) {  
            info.put("password", password);  
        }  
  
        return (getConnection(url, info, Reflection.getCallerClass()));  
    }  
  
//  Worker method called by the public getConnection() methods.  
    private static Connection getConnection(  
        String url, java.util.Properties info, Class<?> caller) throws SQLException {  
        /* 
         * When callerCl is null, we should check the application's 
         * (which is invoking this class indirectly) 
         * classloader, so that the JDBC driver class outside rt.jar 
         * can be loaded from here. 
         */  
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;  
        synchronized (DriverManager.class) {  
            // synchronize loading of the correct classloader.  
            if (callerCL == null) {  
                callerCL = Thread.currentThread().getContextClassLoader();  
            }  
        }  
  
        if(url == null) {  
            throw new SQLException("The url cannot be null", "08001");  
        }  
  
        println("DriverManager.getConnection(\"" + url + "\")");  
  
        // Walk through the loaded registeredDrivers attempting to make a connection.  
        // Remember the first exception that gets raised so we can reraise it.  
        SQLException reason = null;  
  
        for(DriverInfo aDriver : registeredDrivers) {  
            // If the caller does not have permission to load the driver then  
            // skip it.  
            if(isDriverAllowed(aDriver.driver, callerCL)) {  
                try {  
                    println("    trying " + aDriver.driver.getClass().getName());  
                    Connection con = aDriver.driver.connect(url, info);  
                    if (con != null) {  
                        // Success!  
                        println("getConnection returning " + aDriver.driver.getClass().getName());  
                        return (con);  
                    }  
                } catch (SQLException ex) {  
                    if (reason == null) {  
                        reason = ex;  
                    }  
                }  
  
            } else {  
                println("    skipping: " + aDriver.getClass().getName());  
            }  
  
        }  
  
        // if we got here nobody could connect.  
        if (reason != null)    {  
            println("getConnection failed: " + reason);  
            throw reason;  
        }  
  
        println("getConnection: no suitable driver found for "+ url);  
        throw new SQLException("No suitable driver found for "+ url, "08001");  
    }

(3)最开始的代码我们是以 MYSQL 驱动为例,设置 JDBC 驱动以后,使用 DriverManager.getConnection 来获取具体的链接实现,然后通过这个 Connection 来创建一个 Statement 来提交 SQL 语句。除了可以创建 Statement 外,Connection 还可以创建 clob, blob, sqlxml 等对象,即 Connection 就是抽象工厂,而具体的工厂实现则在不同的数据库驱动包种。
关于 MYSQL JDBC 驱动是什么时候注册到 DriverManager 中的,可以参考我后续的文章:
  • Java - GoF设计模式详解7(桥接模式)

附二:Spring 中的工厂模式

(1)Spring 中的 BeanFactory 就是简单工厂模式的体现,其 getBean() 方法是相对应的 bean 的工厂方法。该方法根据传入一个唯一的标识来获得 Bean 对象。
提示:简单工厂模式同样体现在 ApplicationContext 中,它是 BeanFactory 的子类。除了同样可以通过工厂方法来创建 Bean 外,还提供了更多的配置方式和特性。

(2)举个例子,假设我们有一个配置文件 beans.xml
<bean id="exampleBean" class="com.example.ExampleBean">
    <property name="name" value="John Doe" />
    <property name="age" value="35" />
</bean>

(3)我们可以使用 BeanFactory 读取配置文件并使用简单工厂模式创建了一个 ExampleBean 类型的 Bean,并且根据配置文件中设置的属性值来初始化这个 Bean
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
ExampleBean exampleBean = (ExampleBean) factory.getBean("exampleBean");
评论

全部评论(0)

回到顶部