定义
定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类中 。
这是一种创建型的模式,它不属于23中设计模式,或可称之为一种编程习惯,但他同样满足创建型模式所要求的“创建与使用相分离”的特点,在特定场景的编码实践中,仍具有较高的使用价值。
模式结构
该模式包含三种元素,工厂、产品、客户端,他们之间的关系为:工厂按客户端要求生产产品,供客户端使用。
-
工厂:类或接口。工厂实例按需创建产品对象。其内部逻辑主要是创建各种产品对象的分支,通过接收客户端传入的产品对象参数,确定要创建产品的类型。工厂元素封装了产品变化部分,一旦产品类型发生变化,需要修改其代码逻辑。
-
产品:根据客户端业务需求,产生的各种实体类。由于无法一次性得知全部产品的类型,可使用接口定义产品规范,产品实现类实现这些规范。
-
客户端:调用工厂获取产品。客户端需要给工厂提供一个产品类型参数,供工厂确定要创建的产品。
三者之间的关系如下图:
特点
简单工厂模式,以及后面的工厂方法模式和抽象工厂模式,他们都包含“工厂”二字,顾名思义,这些模式首要功能都是创建产品对象,然后将对象提供给客户端。他们的优缺点基于将创建产品这一操作从客户端移入到工厂中,统一放入工厂中管理。
-
优点
- 客户端不必关注创建对象操作。客户端需要创建对象时直接给工厂类发个产品类型消息就可以拿到结果对象;
- 对于扩展,可以直接修改工厂类的方法,修改更新操作对客户端是透明的;
- 静态方法创建对象可以使用类直接调用,不必创建工厂类;
-
缺点
- 违反开闭原则,当出现新的产品时,需要修改已经生效的工厂类;
- 工厂类统一了“制造”环节,一定程度上其他客户端都需要依赖工厂类,造成垄断现象,即强耦合。一旦工厂类异常,相关所有客户端都无法工作;
- 增加类的个数,包括产品接口、工厂类。利用对类抽象达到解耦是设计模式的通用做法,因此增加类数量是通病;
- 工厂类使用static修饰创建方法,造成无法子类无法通过继承修改创建方法;
- 如果产品类型较多,工厂类的创建逻辑可能比较复杂,不利于维护;
代码实践
- 在确定使用简单工厂模式后,首先可以对产品进行抽象化,即创建一个产品接口,抽象类和接口都可以。然后编码实现若干实现类。
//产品接口
public interface Product {
void printProductName();
}
//产品实例1
public class ProductOne implements Product {
@Override
public void printProductName() {
System.out.println("this is productOne");
}
}
//产品实例2
public class ProductTwo implements Product {
@Override
public void printProductName() {
System.out.println("this is productTwo");
}
}
- 工厂类主要负责创建具体的产品对象
//工厂类
public class ProductFactory {
//静态方法
public static Product createProduct(String productType){
Product p = null;
if("typeOne".equalsIgnoreCase(productType)){
return p =new ProductOne();
}else if ("typeTwo".equalsIgnoreCase(productType)){
return p =new ProductTwo();
}
return p;
}
}
- 客户端,调用工厂类,获得具体产品
//客户端
public class Test {
public static void main(String[] args) {
//向工厂类的静态工厂方法传递产品类型
//获得一个抽象产品
Product p = ProductFactory.createProduct("typeOne");
p.printProductName();
}
}
源码解析
在jdk8中,Calendar类的部分类图如下图所示。其中Calendar是抽象类,定义了getMinimum等抽象方法;GregorianCalendar 、JapaneseImperialCalendar 、BuddhistCalendar是三个实现子类,实现了Calendar中的抽象方法。对比简单工厂模式,可以认为这个四个类之间的关系符合产品接口和产品的关系特点(实例类型数量不多)。
阅读Calendar源码,可以看到该类中有4个获取实例的getInstance方法,如下图。
在这些getInstance中,存在根据caltype字段创建不同类型子类实例的代码逻辑。对比简单工厂模式,可以认为Calendar类符合简单工厂模式中的工厂类的特点(根据外部条件创建不同的实例对象)。
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
特别地,此处产品接口和工厂类都是Calendar。
适用场景
在考虑是否使用简单工厂模式时,首要条件是用于创建对象。其次考虑避免该模式最后一条缺点,适用于一些产品种类相对较少的场景。
编程知识点
-
关于static关键字的一些结论
-
情况1 子类和父类中存在同名的static变量和方法时,两者相互独立的,不存在任何的重写关系。父类对象调用父类的static变量或方法,子列调用子类的static变量或方法。
-
情况2 父类存在static变量和方法,子类不存在同名static变量和方法时,子类是不继承父类的static变量和方法的,因为这是属于类本身的。但是子类是可以访问的,即子类对象可以直接调用父类的static变量和方法。
在第二种情况下,子类不能称继承了父类的static方法。假设是继承,那么第一种情况中应该存在重写,但实际上未重写,得出子类不能继承父类的static方法和变量。只是能操作这一块内存空间,只能操作的原因或许同static修饰的变量和方法常驻内存相关。博文Java中子类是否可以继承父类的static变量和方法而呈现多态特性的评论有误
-
-
在idea的默认情况下,在一个空包A中创建一个新的空包B时,两个包会直接折叠在一起,形成A.B的样式。此时想要在A下面创建一个包C时,C直接在A.B下,而不是理想的 A下的 B和C。此时只需点击IDEA目录最上面一行右侧的设置按钮(齿轮图标),关闭“Hide Empty Middle Packages”(低版本idea)或者“compact middle package”(高版本idea)即可。
-
override注解有两个作用
- 强制子类重写父类方法
- 实现接口方法,此处jdk5以后生效