Java - GoF设计模式详解2(简单工厂、工厂方法、抽象工厂模式)
二、工厂模式
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)提供了一种延迟创建类的方法,使用这个方法可以在运行期由子类决定创建哪一个类的实例。简单来说就是父类定义一个创建对象的接口,但由子类决定需要实例化哪一个类。
注意:使用工厂方法模式,新增一个产品时只需多写一个相应的具体工厂类和具体产品类,无须修改原有的代码,不会违法开闭原则。
- 抽象工厂(Creator)角色:声明了工厂方法,该方法返回一个 Product 类型的对象。Creator 也可以定义一个工厂方法的缺省实现,它返回一个缺省的 ConcreteProduct 对象。
- 具体工厂(Concrete Creator)角色:重定义了工厂方法,以返回一个 ConcreteProduct 实例。
- 抽象产品(Product)角色:定义了 Factory Method 所创建的对象的接口
- 具体产品(Concrete Product)角色:用于实现 Product 接口;
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(); } }
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)该该模式中包含的角色及其职责如下:
- 抽象工厂(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 外,还提供了更多的配置方式和特性。
<bean id="exampleBean" class="com.example.ExampleBean"> <property name="name" value="John Doe" /> <property name="age" value="35" /> </bean>
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml")); ExampleBean exampleBean = (ExampleBean) factory.getBean("exampleBean");