除了 new 之外的创建对象的方法

通过 new 创建对象,会使得程序面向实现编程,先举个例子,某个果园里现在有两种水果,一种是苹果,一种是香蕉,有客户想采摘园子里的水果,要求用get()方法表示即可

一般情况下,最直接的写法为:

public class Apple {
public void get() {
System.out.println("得到苹果");
}
}
 
public class Banana {
public void get() {
System.out.println("得到香蕉");
}
}
 
// 客户端
public static void one() {
// 实例化一个apple
Apple apple = new Apple();
// 实例化一个banana
Banana banana = new Banana();
apple.get();
banana.get();
}

如上代码,一堆的水果类,必须等到运行时才能知道实例化哪一个。一旦水果类有变化或者扩展,还要重新修改客户端类,一旦代码量多了,或者系统复杂了,修改的成本是很大的。

那么可以用一种方法替代,就是工厂模式——把实例化的具体代码从应用中抽离,或者封装。工厂模式的变形比较多,本文只引申简单工厂模式。

简单工厂模式

教科书的定义:

简单工厂模式属于类的创建型模式,也叫静态工厂方法模式。它通过专门定义一个类来负责创建其他类的实例,目的是为了隐藏具体类的对象的创建过程,既不耽误对象的创建,也隐藏了创建过程,被创建的实例通常都具有共同父类

继续看水果的例子,后来果园有了新需求——用采摘到的水果做果汁,要求使用 doJuice(对应的水果)生产果汁。水果类的代码可以保持不变,客户端新加的其他代码如下:

// 客户端
private static void doJuice(Apple apple) {
apple.get();
System.out.println("做成果汁");
} private static void doJuice(Banana banana) {
banana.get();
System.out.println("做成果汁");
}
 
public static void one() {
// 实例化一个apple
Apple apple = new Apple();
// 实例化一个banana
Banana banana = new Banana();
doJuice(apple);
doJuice(banana);
}

随着业务发展,后来果园又引进了大量新水果,比如橘子,西瓜,柿子,荔枝,葡萄,哈密瓜,火龙果等。如果继续用之前的代码,那么除了必须新加水果类之外,在客户端里还要分别为每一类水果添加对应的doJuice(水果)方法,然而水果那么多……使得代码的维护性,稳定性变差

面向接口编程

为了增加程序的灵活性,可以做一些抽象,即把各个具体的水果都抽象化,可以选择抽象类或者接口去实现,现在要创建不带任何方法定义和成员变量的抽象化的类,首选的应该是接口

如图1所示,接口有扩展能力,也就是旧的接口能 extends 新接口,从而使得代码的扩展行为是可行的

使用接口的另一个原因和抽象类一样,都是为了避免某个类被实例化,可以告诉编译器,以及一起协作开发的程序员,这个类不需要实例化,它只是为了对某些行为做出规范,谁想用,谁就去遵守这个规则即可。

public interface Fruit {
void get();
}
 
public class AppleA implements Fruit {
@Override
public void get() {
System.out.println("苹果");
}
} public class BananaA implements Fruit {
@Override
public void get() {
System.out.println("香蕉");
}
} // 客户端
private static void doJuiceB(Fruit fruit) { // Fruit 是接口,只需要一个方法 doJuiceB
fruit.get();
System.out.println("做成果汁");
}
 
private static void two() {
// 使用接口的引用指向子类的对象,向上转型过程,用到了多态
Fruit apple = new AppleA();
Fruit banana = new BananaA();
Fruit orange = new OrangeA(); doJuiceB(apple);
doJuiceB(banana);
doJuiceB(orange);
}

综上,接口的作用可以概括为两个:

1、避免客户端去实例化某个类

2、向上转型的使用(多态)

分离变的部分

继续看这个例子,客户只是想把果园采摘的水果做出果汁,客户作为调用者,只需要水果去做出果汁,而水果具体怎么得到的,其实客户不需要也没必要关心,调用者没必要为了喝果汁还花代价去亲自采摘水果……

之前的设计,客户端有一个传入接口类型参数的 doJuiceB(Fruit fruit); 方法。客户端调用该方法可以动态的做出不同水果的果汁,现在把采集水果的代码单独放到一个类里,隐藏起来,分离变化的部分,我们叫它工厂类,下面是代码实现。

// 工厂生产水果
// 对于客户端来说,不再直接简单粗暴的 new 一个水果的实例,而是把生成水果的实例的过程放到一个单独的类,把这个实例化的过程隐藏了起来……我们叫它工厂类
public class FruitFactory {
public static FruitC getApple() {
return new AppleC();
} public static FruitC getBanana() {
return new BananaC();
}
}
 
// 客户端
private static void doJuice(FruitC fruit) {
fruit.get();
System.out.println("做成果汁");
}
 
private static void three() {
FruitC apple = FruitFactory.getApple();
FruitC banana = FruitFactory.getBanana(); doJuice(apple);
doJuice(banana);
}

简单工厂模式解决的问题是如何去实例化一个合适的对象,简单工厂模式的核心思想就是有一个专门的类来负责创建实例。具体来说,把产品看为是一系列的类的集合,这些类由某个抽象类或者接口派生出一个对象树,工厂类产生一个合适的对象来满足客户的要求,从而把对象的创建过程进行封装

如果简单工厂模式所涉及到的具体产品之间没有共同的逻辑,那么就可以使用接口来扮演抽象产品的角色,如果具体产品之间有逻辑上的联系,就把这些共同的东西提取出来,放在一个抽象类中,然后让具体产品继承抽象类,以实现代码复用,如图2所示。借用高斯林(Java之父)所说:

共同的东西总是应该抽象出来。在实际的的使用中,抽象产品和具体产品之间往往是多层次的产品结构

引入简单工厂模式

上面的设计,对于客户端来说,不再直接简单粗暴的 new 一个水果的实例,而是把生成水果的实例的过程放到一个单独的类,把这个实例化的过程隐藏了起来……我们叫它工厂类,这个设计也叫简单工厂模式——它解决的问题是如何去实例化一个合适的对象。

简单工厂模式的核心思想就是:有一个专门的类来负责创建实例。具体来说,把产品看着是一系列的类的集合,这些类是由某个抽象类或者接口派生出来的一个对象树,而工厂类用来产生一个合适的对象来满足客户的要求,从而把对象的创建过程进行封装,如果简单工厂模式所涉及到的具体产品之间没有共同的逻辑,那么我们就可以使用接口来扮演抽象产品的角色,如果具体产品之间有逻辑上的联系,我们就把这些共同的东西提取出来,放在一个抽象类中,然后让具体产品继承抽象类,为实现更好复用的目的,共同的东西总是应该抽象出来的。在实际的的使用中,抽象产品和具体产品之间往往是多层次的产品结构,如图:

下面看看教科书的定义:简单工厂模式属于类的创建型模式,也叫静态工厂方法模式,通过专门定义一个类来负责创建其他类的实例,目的是为了隐藏具体类的对象的创建过程,既不耽误对象的创建,也隐藏了创建过程。被创建的实例通常都具有共同父类

本例子里,苹果和香蕉都有一个共同的父类——水果,此时我们专门定义一个类,负责创建其他类的实例,这个类叫简单工厂类,它有三个角色:

1、工厂(Creator)角色:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。

2、抽象产品(Product)角色:简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口,或者抽象类。

3.具体产品(Concrete Product)角色:简单工厂模式所创建的具体实例对象,这些对象去继承或者实现抽象角色

不过,细细体味下,在工厂类里针对每一个水果都有一个对应的获取水果的操作,这是一种很粗糙的设计,还可以更好,就是把每个get方法抽象为一个公用的get方法,代码如下:

public interface FruitD {
void get();
}
//////////////////////////////
public class AppleD implements FruitD {
@Override
public void get() {
System.out.println("苹果");
}
}
///////////////////////////////
public class BananaD implements FruitD {
@Override
public void get() {
System.out.println("香蕉");
}
}
//////////////////////////////
public class FruitFactoryFour {
public static FruitD getFruit(String type) {
if ("apple".equalsIgnoreCase(type)) {
return new AppleD();
} else if ("banana".equalsIgnoreCase(type)) {
return new BananaD();
} else {
System.out.print("error!");
} return null;
}
}

这样稍微好了点儿,把每个水果对应的get方法抽象为一个公用的get方法,工厂类里根据传入的参数,去判断应该生成哪个水果的对象,并把这个对象返回(依然是向上转型的使用),客户端只需简单的进行调用即可。

非常方便,也隐藏了具体产品的实例化过程,完美的完成了客户和水果厂的需求。

可以认为简单工厂模式的核心是工厂类,这个类含有必要的逻辑判断(if-else),可以决定在什么时候创建哪一个类的实例,而调用者则可以免除直接创建对象的责任。简单工厂模式通过这种做法实现了对责任的分割,当系统引入新的产品的时候无需修改调用者。

解耦合的简单工厂模式

虽然简单工厂模式分离了产品的创建者和消费者,有利于软件系统结构的优化,但是由于一切产品创建的业务逻辑都集中在一个工厂类中,导致了没有很高的内聚性,同时也违背了开闭原则。另外,简单工厂模式的方法一般都是静态的,而静态工厂方法让子类继承是可能被隐藏的,因此,简单工厂模式无法形成基于基类的继承树结构。

到了这里,其实又要想,不要过度的优化,不要为了使用设计模式而使用设计模式,如果是业务比较简单的场景,这样的简单工厂模式还是非常好用的。但无论如何,繁琐的if-else判断还是不太好,一旦判断条件稍微多点儿,if-else写起来就非常繁琐。

观察一些开源框架实现类似场景的代码,发现它们使用了 Java 的反射机制省去了判断的步骤,比之前的繁琐的 if-else 判断要好一些,如下代码。

public interface FruitE {
void get();
} public class BananaE implements FruitE {
@Override
public void get() {
System.out.println("香蕉");
}
} public class AppleE implements FruitE {
@Override
public void get() {
System.out.println("苹果");
}
}
 
// 新的工厂类
public class FruitFactoryFive {
public static FruitE getFruit(String type) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class fruit = Class.forName(type);
return (FruitE) fruit.newInstance();
}
}
 
// 客户端
private static void five() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
FruitE apple = FruitFactoryFive.getFruit("simpleFactory.five.AppleE");
FruitE banana = FruitFactoryFive.getFruit("simpleFactory.five.BananaE"); apple.get();
banana.get();
}

如此一来,使得工厂的扩展性变强了。

补充:forName 方法和 newInstance 方法

从 JVM 的角度看,使用 new 的时候,这个要 new 的类可以没有被 JVM 加载,但是使用 newInstance,就必须保证这个类已经加载且这个类已经链接,而完成这两个步骤的正是 Class 的静态方法 forName(......),该方法调用了启动类加载器(bootstrap加载器)去加载类(不初始化)。

Class 类的对象方法 newInstance 与静态方法 forName 实际上是把 new 关键字做的事情分解为了两步:

1、加载某个类

2、初始化

这样分步调用构造器的好处是显而易见的,因为它的粒度更细,所以程序可以在实例化类的时候获得更好的灵活性,催生一些降耦手段。

事实上,Class 类的 newinstance 方法经常被各种框架使用,它是解耦合的利器之一,比如设计模式中最最常见的工厂模式。

当然,一些知名的开源框架使用了更高级的asm等字节码框架,能使反射操作的性能非常高效,并且还能修改已经编译的字节码,使得程序的灵活性变得很强。

依托配置文件(注解)完全解耦

但是依然不完美—客户端缺少调用的灵活性,客户端必须传入严格对应类名的字符串,甚至还要包含完整的包名,才能实例化对应的类,稍微差一点儿,都会失败。故还是前面说的,简单的业务一般使用if-else的方式传入字符串即可,而稍微复杂的,可以变为反射的方式实现,而反射实现工厂类,对于客户端又显得调用上不方便。一些开源框架使用了配置文件或者注解解决了该问题,现在Java世界的主流是约定优于配置,注解是主流。

String className = readConfig(); // 从配置文件中获得类的句柄
Class c = Class.forName(className);
factory = (FruitE)c.newInstance();

利用配置文件消灭了写死的产品类名称,无论产品怎么变化,代码不会再修改,甚至可以更换类的子类,只要他们继承该类(实现接口)就可以。

当然进一步就是自定义注解处理器,实现自己系统的注解

简单工厂模式的经典案例——JDBC

JDBC是SUN公司提供的一套数据库编程接口。它能提供简单、一致的方式访问各种关系型数据库。Java通过JDBC可以执行SQL语句,并能对获取的数据进行处理,将变化了的数据存回数据库。用JDBC进行数据库访问时,要使用数据库厂商提供的驱动程序接口与DBMS进行交互。客户端要使用使用数据时,只需要和工厂交互即可,这就是典型的简单工厂模式的应用。使得程序员的代码量得到极大的简化。

操作步骤按照顺序依次为:

1、注册并加载数据库驱动,一般使用Class.forName();

2、创建与数据库的链接Connection对象

3、创建SQL语句对象preparedStatement(sql);

4、提交SQL语句,根据实际情况使用executeQuery()或者executeUpdate();

5、显示相应的结果

6、关闭数据库

简单工厂模式的优缺点

优点

工厂类是整个模式的关键所在,它包含必要的判断逻辑,能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。

用户在使用时可以直接根据工厂类去创建所需的实例,而无需了解这些对象是如何创建以及如何组织的,有利于整个软件体系结构的优化

缺点

由于工厂类集中了所有实例的创建逻辑,这就直接导致一旦这个工厂出了问题,所有的客户端都会受到牵连;

由于简单工厂模式的产品基于一个共同的抽象类或者接口,这样一来,产品的种类增加的时候,即有不同的产品接口或者抽象类的时候,工厂类就需要判断何时创建何种种类的产品,这就和创建何种种类产品的产品相互混淆在了一起,违背了单一职责,导致系统丧失灵活性和可维护性。

简单工厂模式违背了“开放封闭原则”,因为当新增加一个产品的时候必须修改工厂类,相应的工厂类就需要重新编译一遍。

一句话:虽然简单工厂模式分离产品的创建者和消费者,有利于软件系统结构的优化,但由于一切逻辑都集中在一个工厂类中,导致了没有很高的内聚性,同时也违背了“开放封闭原则”。另外,简单工厂模式的方法一般都是静态的,而静态工厂方法是无法让子类继承的,因此,简单工厂模式无法形成基于基类的继承树结构

引申: Java 生成对象的方法都有哪些?

Java中有5类创建对象的方式

1、new

2、反射,Class.newInstance()或Contructor.newInstance(),其本质是一样的,都采用了反射机制

3、clone方法

4、反序列化

5、JNI

欢迎关注

dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!

Java反射+简单工厂模式总结的更多相关文章

  1. java反射机制(工厂模式)

    http://www.phpddt.com/dhtml/338.html java里面没有typeof,js有. 我终于实现了用反射机制编写的工厂模式.java反射在工厂模式可以体现. 包含产品接口类 ...

  2. 设计模式(java)--简单工厂模式之女娲造人.水果农场

    女娲抟土造人 话说:“天地开辟,未有人民,女娲抟土为人.”女娲需要用土造出一个个的人,但在女娲造出人之前,人的概念只存在于女娲的思想里面. 女娲造人,这就是简单工厂模式的应用.  首先,在这个造人的思 ...

  3. java 之 简单工厂模式(大话设计模式)

    以前只是看设计模式,每次看完都去理解一次,并没有手动去写代码,所以理解的还不是很深刻,最近查看框架源码,发现很多地方用到的都是设计模式,因为对设计模式理解的不够深刻,所以源码查看进度很慢!现在决定来温 ...

  4. Java实现简单工厂模式

    昨天看了一下设计模式,复习了一下简单工厂模式,做个笔记,浅淡一下我对简单工厂模式的理解.书上使用的是C#,因为我所学的是Java,所以本人就用Java实现了一遍.如果有讲的不对的地方,希望能够指出来. ...

  5. Java设计模式 -- 简单工厂模式(SimpleFactory)

    一.什么是简单工厂模式 简单工厂模式属于类的创建型模式,又叫做静态工厂方法模式.通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类. 二.模式中包含的角色及其职责 1.工厂(C ...

  6. Java设计模式-简单工厂模式(Static Factory Method)

    简单工厂模式(Static Factory Method) 简单工厂模式是类的创建模式,又叫静态工厂方法(Static Factory Method)模式.简单工厂模式是由一个工厂对象决定创建出哪一种 ...

  7. java的简单工厂模式

    目录 代码讲解 UML图解简单工厂 优点 缺点: 改进: 代码讲解 产品功能接口: /** * 定义接口,抽象出产品都具有的功能 */ interface Produce { void method( ...

  8. java设计模式--简单工厂模式

     简单工厂设计模式 工厂模式就是专门负责将大量有共同接口的类实例化,而且不必事先知道每次是要实例化哪一个类的模式.它定义一个用于创建对象的接口,由子类决定实例化哪一个类. 核心知识点如下: (1) 大 ...

  9. java之简单工厂模式详解

    设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计模式于 ...

随机推荐

  1. 你真的了解META-INF吗?

    你真的了解META-INF吗? 做过JAVA EE开发的工程师应该都知道在JAVA build出来的JAR或者WAR的顶层目录下有个META-INF文件夹吧,可是有多少人能够清楚说出这个文件夹到底是做 ...

  2. BZOJ4738 : 汽水

    二分答案$mid$,若存在一条路径满足$|ave-k|<mid$,则答案至多为$mid-1$. 若$ave\leq k$,则$\sum(w-k)\leq 0$,且$\sum(k-w-mid)&l ...

  3. BZOJ2130 : 魔塔

    考虑从$0$到$n$枚举$A$的通关楼层. 设$f[i]$表示$B$通关$i$层时$C$最多能得到多少金币,因为金币数非负,所以也可以看作最多通关多少层. 当$A$的通关楼层往上多$1$的时候,这把钥 ...

  4. [jzoj]1417.数学题

    Link https://jzoj.net/senior/#main/show/1417 Problem 当Alice在浏览数学书时,看到一个等式A=S,奇怪的是A和S并不相等.Alice发现可以通过 ...

  5. HttpClient异步调用引发的程序挂起问题排查及解决

    在搭建搭建分布式系统时,基础组件与框架的重要性不言而喻.但是如果组件出现bug,真的很要命.虽然我们通过各种单元测试,拼命找bug,但是总有一些问题被盲目自信蒙蔽了双眼,很多时候我们认为这段代码100 ...

  6. ggplot2 作图

    ggplot2 作图 ggplot2是著名的R语言作图工具包,gg为Grammar of Graphics的缩写,体现了结构化作图的思想.ggplot2根据图层来作图是非常优秀的思想,官方文档在这里 ...

  7. Mssql数据库与Excel导数据

    *.xls   2003的excel有行数限制,65535行好像,所以数据库行数多的时候,选择导出为*.xlsx文件 要装一下Microsoft.ACE.OLEDB.12.0(以下简称 ACE 引擎) ...

  8. JS_高程6.面向对象的程序设计(1)理解对象

    js的数据属性:P139(1)[[Configurable]](2)[[Enumerable]](3)[[Writable]](4)[[Value]] 使用Object.definerPropert( ...

  9. CSS_盒子模型

    2016-10-22 <css入门经典>第6章 1.每个HTML元素对应于一个显示盒子,但不是所有的元素都显示在屏幕上. 2.HTML元素显示为CSS显示盒子的真正方法称为“可视格式化方式 ...

  10. MYSQL的联合查询最好是少用,效能差异巨大

    同样的功能,不同的写法,时间和内存占用差了几千倍,不废话,直接上代码 第一种写法: 代码如下: $Rs=DB::get($_ENV['DB'],3,"SELECT * FROM _xiazh ...