第一条:考虑用静态工厂方法代替构造器

这里的静态工厂方法与设计模式里面的 工厂模式 不一样,二者并不完全等价 ;


目录


新手的日常:

我们平时一般都是通过类的构造器,来向外提供类的实例 ;这样做,在某些情况下,并不是很优美,或者说直接使用构造器很操蛋;

但是我们作为新手,因为学java的时候,就是这么学的,可能并不能感觉我们这样的做,有什么不妥;希望通过下面的场景学习,你能知道我们平时写的代码到底是多么的孬;

没见过,大海的广阔;久而久之,你眼中只有溪流的宽度;


场景 一:

我们的类对外提供 多个构造器,来实例化 具有不同功能的实例

假如我们直接使用构造器创建对象,可能会让阅读代码的人,不知所云,阅读代码的人,往往需要借助API才能知道,我们这个构造器是实例出的对象具有什么功能;

蹩脚的代码

//    构造一个随机生成的正 BigInteger, 这可能是素数, 具有指定的 bitLength。
    BigInteger bigInteger = new BigInteger(1,2,new Random()) ;

假如没有我写的注释,谁能知道 new BigInteger(1,2,new Random()) 到底返回一个什么!?因此,我们应该使用 静态工厂方法 来替换掉这样的代码;

优美的代码

 BigInteger bigInteger = BigInteger.probablePrime(3,new Random()) ;

显然上面的代码,很明了,我们完全可以根据 静态工厂方法名字,知道它具体返回什么对象;

从上面的例子中,我们可以发现静态工厂方法优势之一就是静态工厂方法有自己的名字,我们可以根据它提供的功能来命名它,让使用者一目了然 相比之下,构造器就有很大的局限性,它的名字只能跟随类来,类的名字并不能涵盖类提供的对象的所有功能;

静态工厂方法 的名字,在一个地方也是具有优势的,或者说是首选项

不同的构造器实例化的对象,具有不同的功能,但是多个构造器之间,仅仅是参数列表的参数顺序不同这样,通过参数列表,我们是分不清每一个构造器是实例化哪一个功能的对象的;


----------直接构造器代码:----------

/**
 * e1_1 假定功能:
 *  根据构造器的参数,将参数相加的和、相减的商,赋值给answer
 */
public class e1_1 {

   public float answer ;

   public e1_1(int a,float b ){
          answer =  a + b ;
    }

    public e1_1(float a,int b){
        answer =  a - b ;
    }

}


--------------------- 调用类 ---------------------

import org.junit.Test;

/**
* 调用类
*/
public class test1_1 {

    @Test
    public void test(){
    //  对应这样的代码,不去翻看API 谁能分清谁是加、谁是减法呢
        float answer1 = new e1_1(3, 5f).answer;
        float answer2 = new e1_1(3f, 5).answer;
    }
}

我们使用 静态工厂方法 来替换上述的写法 ;



--------------------- 使用静态工厂方法替换掉构造器 ---------------------

public class e1_1 {

    public float answer;
//  构造器私有化
    private e1_1(int a, float b) {
        answer = a + b;
    }

    private e1_1(float a, int b) {
        answer = a - b;
    }
 // 对外只提供静态工厂方法
    public static float getAdd(int a, float b){
        return new e1_1(a,b).answer ;
    }

    public static float getDec(int a, float b){
        return new e1_1(b,a).answer ;
    }
}


--------------------- 调用类 ---------------------

public class test1_1 {

    @Test
    public void test(){
//  现在的调用,一眼就能看出,谁是加、谁是减法
        float answer1 = e1_1.getAdd(3, 5);
        float answer2 = e1_1.getDec(3, 5);
    }
}

小结:通过上述的代码,我们知道了 静态工厂方法 的一个优势 ,就是它具有名字,我们可以直接提供的功能来作为名字,对使用者很友好 ;


场景二:

当我们每次需要一个类的对象的时候,假如,我们需要的对象,并没有什么不同 ;我们可以考虑,提供一个 静态工厂方法 每次都返回我们已经创建好的对象,这样,就避免了调用构造器创建创建重复对象的缺点

示例代码:


--------------------代码来自Boolean类--------------------

// TRUE、FALSE 是我们事先准备的对象
  public static final Boolean TRUE = new Boolean(true);
  public static final Boolean FALSE = new Boolean(false);

// 每次需要相同对象的时候,直接返回已经创建好的对象
   public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }

对于这样设计的类的对象,我们可以通过 == 运算符来判断对象是否相等,而不用复写 equal 方法,这样做,可以提升一些性能 ;

小结:相比于构造器静态工厂方法 可以做到,每次调用都返回相同的对象,避免 重复创建新的对象


场景三

我们可以使用 静态工厂方法,来 灵活的选择返回的对象的类型,可以是原返回类型的任意子类型,也就是返回的类型可以是构造器类的子类。

EnumSet类就是这样实现的,它根据枚举元素的个数,来选择换回哪一个实现类 ;


----------------代码来自EnumSet类----------------------

 public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
        Enum[] universe = getUniverse(elementType);
        if (universe == null)
            throw new ClassCastException(elementType + " not an enum");
//     判断枚举的元素的个数,决定使用哪一个实现类
        if (universe.length <= 64)
            return new RegularEnumSet<>(elementType, universe);
        else
            return new JumboEnumSet<>(elementType, universe);
    }

这样做,是 很灵活的假如后期,我们发现,当元素大于128的时候,使用另一个实现会更有效率,那么JDK就可以将新的实现类添加进去;

但是对于客户端来说,完全是隔离的,因为我们无法知道,也不必知道JDK到底使用了哪一个实现类,只要知道它返回一个 EnumSet类型 即可 ;

—————————–案例 一 ———————:

JDK源代码中,基于接口的框架Collections 就是这样做的;

该框架有一个 publicCollection 接口,里面定义了集合应该有的操作,也就是一个集合应该是什么样的;

集合有好多种,不可改变集合、同步集合、xxx集合;它们都是是 Collection 接口的具体实现;JDK 将它们实现为 非public 类(在源代码里面,实现类都是Collections 类的内部类,当然不是内部类,也行,没有什么影响)。

这样做的意义在于,将这些具体实现类隐藏起来,可以减少 API 的数量,由于是 非public 类,因此 API 中不需要对其进行任何讲解,(这样做也导致了缺点一点的产生

框架中还有一个对外 暴露不可实例化Collections 类,里面提供大量的静态工厂方法,来对外提供具体的集合实现 ,但是这些方法的返回值类型,都是 Collection 接口 ,这就 达到了灵活选择返回对象类型的目的 ,同时静态工厂方法具有名字,也便于使用者知道返回的是哪一种集合 ;

—————————–案例 二 ———————:

服务提供者框架;其中子类型的实现类,甚至在定义静态工厂方法的时候,都不必存在 ;


场景四:

在使用泛型的时候,静态工厂方法,可以为我们创造一些便利;

先看下我们平时写的泛型,这样的代码,多孬哦,左右两边都需要写上泛型参数,泛型很长的时候,写的很烦人,虽然IDE会自动提示,但是这么长看着也恶心啊 ;

  Map<String,List<ArrayList<Integer>>> map = new HashMap<String, List<ArrayList<Integer>>>() ;

上述的泛型是JDK定义的,我们无法去更改了;但是我们可以为自己的类,写上静态工厂方法,便于泛型的使用 ;

假如我们自己写 HashMap类,那么我们就应该提供一个静态工厂方法:


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


--------------------------------

//   这样我们再使用泛型,就会很简洁

Map<String,List<ArrayList<Integer>>> map = HashMap.newInstance();

优点

经过上述的场景分析,其实可以看出来,一个场景就是一个优点的展示;

  1. 当一个类有多个 重载构造器的时候,具有名字 静态工厂方法,可以很直接明了的让调用者知道返回的是一个什么玩意;
  2. 可以控制返回对象的个数;
  3. 可以灵活的返回原对象类型的子类型;
  4. 可以简化泛型的书写,在自己的工具类中应该有这个小工具;

缺点

  1. 不利于扩展

    类的构造器被私有化了,或者类是非public ,那么我们就不能复用它们,就像Collection框架,我们想对同步集合进行复用,直接GG,是非public类的,跨包访问不到;继承了,是不好使,只能使用返回的子类型对象,进行 组合 因祸得福 ?!

  2. 静态工厂方法不容易被识别出来

    静态工厂方法,归根到底也就是普通的静态方法,不能向构造器那样,一下子被 javaDoc 识别出来,使用者往往需要寻找半天才能找到需要的那个静态工厂方法,而不能向构造器那样,一下定位到;

    我们可以为它们取一些,大家认可的名字,来帮助定位;比如 valueOfnewInstancegetInstance 等等 ;

猜你喜欢

转载自blog.csdn.net/youngyouth/article/details/80081295
今日推荐