经验法则:考虑用静态工厂方法代替公有构造方法

经验法则:考虑用静态工厂方法代替公有构造方法

一、引出静态工厂方法

  对于java类而言,为了让使用者获取它自身的一个实例化对象,会有以下方法:

    1、该类提供一个公有的构造方法。在这种情况下,程序可以通过多个“new 构造方法”语句来创建类的任意多个实例。但是每执行一条new语句,都会导致java虚拟机的堆区中产生一个新的对象。

    2、该类提供一个公有的静态工厂方法(它只是一个简单的静态方法,返回的是类的一个实例,要区别于设计模式中的工厂方法模式)。对于某些java平台库类或自己的工具类、参数化类,需要进一步封装创建自身实例的细节,并且控制自身实例的数目,那么就可以采用方法2提供静态工厂方法来实现。例如:Class实例是Java虚拟机在加载一个类时自动创建的,程序无法用new语句创建java.lang.Class类的实例,因为Class类没有提供public类型的构造方法。为了使程序能获得代表某个类的Class实例,在Class类中提供了静态工厂方法forName(String name),如下:

Class clazz=Class.forName("FirstDemo"); //返回代表FirstDemo类的实例

  静态工厂方法和公有构造方法都有各自的长处,但通常静态工厂方法更为合适,所以在使用时,可以优先考虑静态工厂方法。

  注意:并不是所有情况下,静态工厂方法都优于公有构造方法!要充分理解静态工厂方法的适用情况,能够把握该方式收放自如才可考虑使用该方式,否则最好还是使用java规范的公有构造方法,避免程序混乱。

  下面来详细对比一下两者区别。

二、对比静态工厂方法和构造方法

  在某些场合,提供静态工厂方法而不用公有的构造方法,具有以下的优势

  1、每个静态工厂方法可以有自己任意定义的名字,因此在方法名中就能体现出与实例有关的信息,可以提高程序代码的可读性。而一个类中构造方法的名字必须与类名相同,即使需要提供2个构造方法,只能令构造方法的参数个数不同或者个数相同但参数类型的顺序上保持不同,因此不能单从名字上就区分出每个重载方法的用途,用户调用时容易引起混淆。例如用静态工厂方法获取男女实例,显然能够更易于阅读:

 1 public class Gender {
 2     //私有化实例和构造方法
 3     private String description;
 4     private static final Gender female = new Gender("女");
 5     private static final Gender male = new Gender("男");
 6     private Gender(String description) {
 7         this.description = description;
 8     }
 9     //公有的静态工厂方法
10     public static Gender getFemaleInstance() {//获取女性实例
11         return female;
12     }
13     public static Gender getMaleInstance() {//获取男性实例
14         return male;
15     }
16     public String getDescription() {
17         return description;
18     }
19 }

  注意:用静态工厂方法代替构造方法后,构造方法就成了private的了,但如果你希望同时也提供公有的构造方法也是可以的。

  2、如以上提到的使用new构造方法方式每次创建实例时都要执行一次new语句,而静态工厂方法不必在每次调用时都创建一个新的对象,是否会创建一个新的对象完全取决于方法的实现。

1 public class StaticFactoryDemo {
2     private static final StaticFactoryDemo demo = new StaticFactoryDemo();//私有,静态,final,构造方法实例化对象
3     public static StaticFactoryDemo getInstance(){//公有的静态工厂方法,获取实例化对象
4            return demo;
5     }
6     public void printMessage(){//普通方法
7           System.out.println("Test Static Factory Class!"); 
8     }
9 }

  例如:在以上代码的全局唯一性对象中通过自定义的getInstance()静态方法提供对该对象的返回。如果需要在其他类中调用StaticFactoryDemo类中的printMessage方法,那么只需要使用如下语句即可,而不必使用new关键字:

StaticFactoryDemo.getInstance().printMessage();//调用printMessage方法

  利用这一特点,静态工厂方法可用来创建以下类的实例。

单例类:只有惟一的实例的类(如以上的StaticFactoryDemo类)。

枚举类:实例的数量有限的类(如:enum weekday{ sun,mou,tue,wed,thu,fri,sat };)。

具有实例缓存的类(该类通常满足:1、类的实例的数量有限2、程序执行过程中,频繁访问该类的一些特定实例):能把已经创建的实例暂且存放在缓存中的类。

具有实例缓存的不可变类:不可变类的实例一旦创建,其属性值就不会被改变。具有实例缓存的不可变类扩展代码:

 1 import java.lang.ref.SoftReference;
 2 import java.util.HashSet;
 3 import java.util.Iterator;
 4 import java.util.Set;
 5 
 6 public class Name {
 7     private String firstname;
 8     private String lastname;
 9     private Name(String firstname, String lastname) {
10         this.firstname = firstname;
11         this.lastname = lastname;
12     }
13     // 实例缓存,存放Name对象的软引用
14     private static final Set<SoftReference<Name>> names = new HashSet<SoftReference<Name>>();
15     // valueOf静态工厂方法
16     public static Name valueOf(String firstname, String lastname) {
17         Iterator<SoftReference<Name>> it = names.iterator();
18         while (it.hasNext()) {
19             SoftReference<Name> ref = it.next(); // 获得软引用
20             Name name = ref.get(); // 获得软引用所引用的Name对象
21             if (name != null && name.firstname.equals(firstname) && name.lastname.equals(lastname))
22                 return name;
23         }
24         // 如果在缓存中不存在Name对象,就创建该对象,并同时把它的软引用加入到实例缓存
25         Name name = new Name(firstname, lastname);
26         names.add(new SoftReference<Name>(name));
27         return name;
28     }
29     // 主方法
30     public static void main(String[] args) {
31         Name n1 = Name.valueOf("大大", "张");
32         Name n2 = Name.valueOf("大大", "张");
33         Name n3 = Name.valueOf("小小", "李");
34         System.out.println(n1);
35         System.out.println(n2);
36         System.out.println(n3);
37         System.out.println(n1 == n2); // 打印true
38     }
39 }

   扩展<不可变类>:例如JDK基本类库中所有基本类型的包装类,如Integer和Long类,都是不可变类,java.lang.String也是不可变类。

    创建一个不可变类:
      1. 所有成员都是private
      2. 不提供对成员的改变方法,例如:setXX
      3. 确保所有的方法不会被重载。手段有两种:使用final Class(强不可变类),或者将所有类方法加上final(弱不可变类)。
      4. 如果某一个类成员不是原始变量(boolean,byte, char, double ,float, integer, long, short)或者不可变类,必须通过在成员初始化或者get方法时通过深度clone方法,来确保类的不可变。例如:

 1 public final class FinalClass {//不可变类
 2     private final int[] iArray;//private成员变量
 3     public FinalClass(int[] aArray) {//整型数组不是原始变量,需要用深度clone方法,保证类的不可变
 4         this.iArray = aArray.clone();
 5     }
 6     public String toString() {
 7         StringBuffer sb = new StringBuffer("Numbers are: ");
 8         for (int i = 0; i < iArray.length; i++) {
 9             sb.append(iArray[i] + " ");
10         }
11         return sb.toString();
12     }
13 }

  3、new构造方法只能创建当前类的实例,而静态工厂方法可以返回当前类的任何子类的实例化对象。

    3.1使用静态工厂方法时,要求调用者通过接口来引用被返回的对象,而不是通过实现类来引用被返回的对象。例如Vector和ArrayList都是List接口的实现:

List<String> sStrings1 = new Vector<String>();
List<String> sStrings2 = new ArrayList<String>();

    3.2 静态工厂方法返回的对象所属的类,可以是非公有的;也可以随着每次调用而发生改变;在编写包含该静态工厂方法的类时可以不必存在。

    3.3 这一特性可以在创建松耦合的系统接口时发挥作用。

  扩展:静态工厂方法构成了“服务提供者框架”的基础。服务提供者框架指:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把它们从多个实现中解耦出来。包括4个组件:服务接口;提供者注册API;服务访问API(核心基础);服务提供者接口(可选)。例如代码:

 1 /*服务提供者框架描述*/
 2 // 服务接口
 3 public interface Service {
 4     // ...
 5 }
 6 
 7 // 服务提供者接口
 8 public interface Provider {
 9     Service newService();
10 }
11 
12 public class Services {
13     private Services() {
14     }
15     private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();//存储服务提供者信息
16     public static final String DEFAULT_PROVIDER_NAME = "<def>";
17     // 提供者注册API
18     public static void registerDefaultProvider(Provider p) {//默认注册服务提供者
19         registerProvider(DEFAULT_PROVIDER_NAME, p);
20     }
21     public static void registerProvider(String name, Provider p) {//注册服务提供者
22         providers.put(name, p);
23     }
24     // 服务访问API(使用静态工厂方法,返回值为接口类型)
25     public static Service newInstance() {//默认实例化服务
26         return newInstance(DEFAULT_PROVIDER_NAME);
27     }
28     public static Service newInstance(String name) {//实例化服务
29         Provider p = providers.get(name);
30         if (p == null)//若不存在注册的服务提供者,则无法提供服务
31             throw new IllegalArgumentException("No provider registered with name:" + name);
32         return p.newService();//若存在注册的服务提供者,则可newService()提供服务
33     }
34 }

  4、静态工厂方法,在创建参数化类型实例时,可简洁化代码。

  假设如果调用参数化的构造方法时,通常需要两次注明参数的类型,如:

Map<String,List<String>> m = new HashMap<String,List<String>>();

  假设HashMap提供了以下的静态工厂方法:

public static <K,V> HashMap<K,V> newInstance(){
  return new HashMap<K,V>();
}

  此时,可以使用以上的静态工厂方法实例化对象:

Map<String,List<String>> m = HashMap.newInstance();

  但是使用静态工厂方法也存在缺点

  1、类如果不含有public的或者protected的构造方法,则不能被子类化(被继承)。而且对于静态工厂方法返回的非公有类,也不能被继承。但是程序员通常被鼓励使用复合,而不是使用继承。

  2、它与其他静态方法没有任何区别。如果系统中存在很多静态工厂方法,则不容易知道如何去实例化一个类。因此要求程序员在使用静态工厂方法时需要进行详细的注释说明,并遵守标准的命名习惯。其中惯用的名称如下:

  valueOf(通常为类型转换方法);getInstance(获取实例);newInstance(创建实例);getType;newType...

原文地址:https://www.cnblogs.com/timssd/p/5483849.html