}
private static final Map<String, IProduct> INSTANCE_MAP = new HashMap<>();
static {
INSTANCE_MAP.put(“A”, new ProductA());
INSTANCE_MAP.put(“B”, new ProductB());
}
}
看似if-else
没有了,其实是转移到了Map
中,此时如果新增产品,就需要改到Map
。
3、反射创建对象
用反射也可以让代码中没有if-else
。再次强调if-else
没有被消除,而是转移到了class
和实例对象中,且是以性能为代价。这次新增产品也不需要改到SimpleFactory
,但是增加了一点客户端交互成本,客户端必须知道生产的产品对象的class
对象是什么。
public class SimpleFactory {
public static IProduct getInstance2(Class<? extends IProduct> clazz) {
if (clazz == null) {
throw new IllegalArgumentException(“class is null”);
}
try {
return clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
4、反射+Map创建单例对象
单纯用Map缓存事先创建好的对象,无法达到懒加载的效果,而单纯用反射,每次创建的对象都是新的,如果不需要每次创建新的对象,且还想有懒加载的效果,有新产品也不想改SimpleFactory
,那就可以用反射+Map,看下面代码,是不是有些熟悉,单例模式的double check
:
public class SimpleFactory {
public static IProduct getInstance(Class<? extends IProduct> clazz) {
if (clazz == null) {
throw new IllegalArgumentException(“class is null”);
}
IProduct product = INSTANCE_MAP.get(clazz.getSimpleName());
if (product != null) {
return product;
}
synchronized (SimpleFactory.INSTANCE_MAP) {
// double check
product = INSTANCE_MAP.get(clazz.getSimpleName());
if (product != null) {
return product;
}
try {
product = clazz.newInstance();
INSTANCE_MAP.put(clazz.getSimpleName(), product);
return product;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
private static final Map<String, IProduct> INSTANCE_MAP = new HashMap<>();
}
5、注解+反射+Map创建对象
前面3、4用到反射创建对象,客户端需要知道某个产品的class
是什么,通过class
的名称获取实例对象,且这个名称不能自定义。如果想要对象名称自定义,可以使用注解:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Product {
public String name() default “”;
}
@Product(name = “A”)
public class ProductA implements IProduct {
@Override
public void doSomeThing() {
System.out.println(“ProductA…doSomeThing”);
}
}
@Product(name = “B”)
public class ProductB implements IProduct {
@Override
public void doSomeThing() {
System.out.println(“ProductB…doSomeThing”);
}
}
@Product
public class ProductC implements IProduct {
@Override
public void doSomeThing() {
System.out.println(“ProductC…doSomeThing”);
}
}
public class SimpleFactory {
public static IProduct getInstance(String name) {
IProduct product = INSTANCE_MAP.get(name);
if (product == null) {
throw new UnsupportedOperationException(“not match product, name=” + name);
}
return product;
}
private static final Map<String, IProduct> INSTANCE_MAP = new HashMap<>();
static {
init();
}
private static void init() {
System.out.println(“init…”);
Set<Class<?>> classSet = ClassUtil.scanPackageByAnnotation(“com.stefan.designPattern.factory”, Product.class);
for (Class<?> clazz : classSet) {
boolean isProduct = false;
in : for (Class<?> i : clazz.getInterfaces()) {
if (IProduct.class.equals(i)) {
isProduct = true;
break in;
}
}
if (!isProduct) {
continue;
}
Product product = clazz.getAnnotation(Product.class);
String name = product.name();
// 虽然指定name default是“”,但是这里取的话会隐式new String()
// 所以必须用equals比较,不能用==
if (“”.equals(name)) {
name = clazz.getSimpleName();
}
try {
INSTANCE_MAP.put(name, (IProduct) clazz.newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
这里用到了hutool
的ClassUtil.scanPackageByAnnotation
扫描类的工具,方法使用很简单,但是过程复杂一些,包扫面首先会获取当前类加载器并调用getResources
获取指定目录下的所有类资源,如果是目录,扫描目录下的类文件或者jar文件,如果是jar包,则直接从jar包中获取类名(比较考验类加载机制和反射基本功)。
6、简单工厂在JDK中的体现
JDK中用到工厂模式的地方老多了,下面简单举几个例子:
(1)Calendar.getInstance()
,可以根据不同时区TimeZone
、地区Locale
获取不同Calendar
实例(不是单例),最终会调用到CalendarProvider
的getInstance
:
(2)DateFormat
的创建,最终会调用到DateFormatProvider
创建对象的方法:
(3)几个包装类型,如String
、Interger
、Long
、Double
等的valueOf
方法都是会创建对象或者从缓存中获取。
(4)线程工厂java.util.concurrent.ThreadFactory#newThread
。
(其他开源框架如Tomcat中也会大量用到简单工厂,暂时不再举例。)
7、简单工厂优缺点
(1)优点:简单工厂的优点就是简单,容易理解,还有就是解耦,隔离变化。
(2)缺点:简单工厂一般以静态方法为主,工厂类单一,难以扩展;当产品基数增多时,工厂类代码可能会比较臃肿,虽然可以通过缓存、反射、自定义注解等方式缓解if-else
,但是需要牺牲一些内存和性能,且有一定的使用场景和开发成本。
工厂方法,外国人起的破名字,和抽象工厂傻傻分不清楚,且看定义和类图:
工厂方法就是为了解决简单工厂难以扩展问题的,依然是生产同类产品,但是每个产品生产较为复杂且细节不尽相同,所以给每个产品分配一个专属工厂:
1、通用写法
产品类和简单工厂一样,假装创建对象的过程复杂且不同;每个产品一个工厂,为了面向接口编程,可扩展性,所以建立一个工厂接口,所有工厂实现这个接口:
public interface IProduct {
void doSomeThing();
}
public class ProductA implements IProduct {
@Override
public void doSomeThing() {
System.out.println(“ProductA…doSomeThing”);
}
}
public class ProductB implements IProduct {
@Override
public void doSomeThing() {
System.out.println(“ProductB…doSomeThing”);
}
}
public class ProductC implements IProduct {
@Override
public void doSomeThing() {
System.out.println(“ProductC…doSomeThing”);
}
}
public interface IFactory {
IProduct create();
}
public class AFactory implements IFactory {
@Override
public IProduct create() {
return new ProductA();
}
}
public class BFactory implements IFactory {
@Override
public IProduct create() {
return new ProductB();
}
}
public class CFactory implements IFactory {
@Override
public IProduct create() {
return new ProductC();
}
}
public class Test {
public static void main(String[] args) {
IFactory factoryA = new AFactory();
IProduct productA = factoryA.create();
productA.doSomeThing();
IFactory factoryB = new BFactory();
IProduct productB = factoryB.create();
productB.doSomeThing();
}
}
很明显类增加了不少,也使得整体变复杂了,再看客户端怎么使用工厂生产产品,需要先实例化对应工厂类,然后创建对应产品。如果客户端不知道使用哪个工厂那还得if-else
选择工厂,咋还回去了,越来越复杂了。。。那就再给工厂加个简单工厂,把if-else
移到一个简单工厂里,生产不同的工厂方法,真的是复杂了,达咩!注意工厂方法的使用场景!
工厂方法的出现是因为每个对象的创建过程较复杂且有自己的创建细节,所以才给每个产品对象分配了一个工厂;且客户端调用、是有不同的场景对应不同的工厂方法,不需要再在工厂方法上面加简单工厂,这是最佳适配。
如果后期新增产品,同时新增工厂,不需要改到旧代码。
2、工厂方法在slf4j+logback中的体现
在开源日志框架门面slf4j
中提供了Logger
接口和ILoggerFactory
供第三方框架扩展(如logback
)。不同的工厂负责生产不同的日志框架,基本类图如下:
可以看出slf4j+logback
既使用了工厂方法,又使用了简单工厂去管理工厂方法。
3、工厂方法优缺点
(1)优点:易扩展,新增产品同时新增工厂,隔离变化,隔离复杂,理想状态完全符合开闭原则、迪米特原则(最少知道原则)、依赖倒置原则。
(2)缺点:类太多了,增加了代码复杂度;更加抽象,不易理解;依然只能生产一种产品,种类单一。
抽象工厂,可以生产不同类型的产品(实现于不同抽象接口),这些产品之间有一定的联系,可以归纳为一个族系的产品。
抽象工厂因为可以生产多种产品,所以不会像工厂方法那样有那么多的工厂类。不过也是因为产品的族系越来越复杂,才使得抽象工厂有了用武之地。
如有两条产品系列1和2,每个系列有一个抽象工厂,每个工厂可以生产A和B。
1、通用写法
public interface IProductA {
void doSomeThing();
}
public interface IProductB {
void doSomeThing();
}
public class ProductA1 implements IProductA {
public void doSomeThing() {
System.out.println(“ProductA1…doSomeThing”);
}
}
public class ProductA2 implements IProductA {
public void doSomeThing() {
System.out.println(“ProductA2…doSomeThing”);
}
}
public class ProductB1 implements IProductB {
public void doSomeThing() {
System.out.println(“ProductB1…doSomeThing”);
}
}
public class ProductB2 implements IProductB {
public void doSomeThing() {
System.out.println(“ProductB2…doSomeThing”);
}
}
public interface IFactory {
IProductA createProductA();
IProductB createProductB();
}
public class Product1Factory implements IFactory {
@Override
public IProductA createProductA() {
return new ProductA1();
}
最后
对于很多Java工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。
整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
再分享一波我的Java面试真题+视频学习详解+技能进阶书籍
tB1 implements IProductB {
public void doSomeThing() {
System.out.println(“ProductB1…doSomeThing”);
}
}
public class ProductB2 implements IProductB {
public void doSomeThing() {
System.out.println(“ProductB2…doSomeThing”);
}
}
public interface IFactory {
IProductA createProductA();
IProductB createProductB();
}
public class Product1Factory implements IFactory {
@Override
public IProductA createProductA() {
return new ProductA1();
}
最后
对于很多Java工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。
整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
再分享一波我的Java面试真题+视频学习详解+技能进阶书籍
[外链图片转存中…(img-i8QH5M8v-1720122952536)]