除了 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
来源: https://www.cnblogs.com/kubixuesheng/p/10344427.html