构造器优化——静态工厂和构建器

来源:互联网 发布:unity3d开发的大型游戏 编辑:程序博客网 时间:2024/05/07 16:44

一、简介

构造器是我们日常开发使用最多的,这种创建对象的方式尤其独特的优点,但是也有他相应的而不足之处。

当一个类的公用构造器的数量比较少的时候,应该考虑使用静态工厂方法来替代构造器。

当一个类的公用构造器的数量比较多且要想更易于拓展时,应该考虑使用构建器。

二、静态工厂

静态工厂(static factory method):是一个返回类的实例的静态方法,而不是提供公有的构造器。

注意:此处的工厂与设计模式中的工厂模式不是一个概念,不要混淆。

1. 静态工厂的缺点

首先写缺点感觉怪怪的,但是我认为这更加便于对静态工厂的理解。

  1. 静态工厂与其他的静态方法实际上没有任何区别。(这句话也是促使我先写缺点的原因)
  2. 类不包含共有的或者受保护的构造器,就不能被子类化。

在API文档中静态工厂与静态类是完全一样的,不像构造器那样被明确表明出来。所以查询通过静态工厂来实例化一个类,是很困难的。因此我们要遵循一定的命名原则。

对于共有工厂返回非公有类也是不能被子类化,例如:要想将Collections Framework(util工具包中的框架Collections)中任何方便的实现类子类化是不可能的。但是因祸得福,以为它鼓励我们使用复合(composition),而不是继承。

2.静态工厂惯用名称

  • valueOf——>这种静态方法实际上是类型转换方法。该方法返回的实例于它的参数具有相同的值。
  • of——>valueOf的一种更为简洁的替代。
  • getInstance——>返回的实例是通过方法的参数来描述的,但是不能够说与参数具有相同的值。(在单例模式中就是返回当前工具类的唯一实例)
  • newInstance——>与上面的getInstance类同,但是newInstance能够确保返回的每个实例都与所有其他实例不同
  • getType——>与上面的getInstance类同,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法所返回的是对象类型
  • newType——>与上面的newInstance类同,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法所返回的是对象类型

3.静态工厂的优点

<1>静态工厂具有名称

通过名称就能大体了解到了他所实现的功能。

例如:构造器BigInteger(int,int,Random)返回的实例可能是素数。然而静态工厂BigInteger.probalePrime就一目了然。

一个类只能有一个带有指定签名的构造器(我当前的理解是:在具有相同数量的参数时,具有的参数类型不一样)。通常避免这种限制的方法是提供两个构造器,但是他们的参数列表只在参数类型的顺序上有所不同。实际上面对这样的设计,使用者很容易混淆,容易调用错误的构造器。

由于静态工厂方法有名称,所以不受上述限制。当一个类需要多个带有相同签名(当前理解:参数)的构造器时,就用静态工厂方法代替构造器,并且慎重地选择名称以便突出它们之间的区别。

<2>不必再每次调用的时候都创建一个新对象

因为是静态的,所以只在只创建一次。(优缺点很明显)程序经常请求创建相同的对象,并且创建对象的代价恩高,这是这项技术的优点。

静态工厂方法能够为重复的调用返回相同的对象,这样有助于类总能严格控制在某个时刻哪些实例应该存在,这种类被称作实例受控的类(instance-controlled)。

<3>可以返回原返回类型的任何子类型的对象

<------------------------------理解中--------------------------------------------->

<4>创建参数化的实例的时候更加简洁

通常在调用参数化的构造器时,即使类型参数很明显,也必须指明(声明的时候已经书写,但是new的时候还得写一遍)。

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

如果类型参数变得更长,更复杂,这显得很痛苦,但有了静态工厂方法,编译器就可以替你找到类型参数。这被称作类型推到(Type inference)。

下面就是优化过程:

//假设HashMap提供了这个静态工厂public static <K, V> HashMap<K, V> newInstance(){return new HashMap<K, V>();}//我们就可以用更加简洁的代码代替繁琐的声明Map<String,List<String>> m = HashMap.newInstance();

三、构建器

静态工厂和构造器共同拥有的局限性:它们都不能很好的扩展到大量的可选参数。

通常我们针对多个可选参数采用的方法是重叠构造器(telescoping constructor)模式,提供一个只有必要参数的构造器,第二个构造器有一个可选参数,第三个有两个可选构造参数,以此类推,最后一个构造器包含所有可选参数。

//Telescoping constructor pattern(重叠构造器模式)- does not scale well!public class NutritionFacts {private final int servingSize; // (mL) requiredprivate final int servings; // (per container) requiredprivate final int calories; // optionalprivate final int fat; // (g) optionalprivate final int sodium; // (mg) optionalprivate final int carbohydrate; // (g) optionalpublic NutritionFacts(int servingSize, int servings) {this(servingSize, servings, 0);}public NutritionFacts(int servingSize, int servings, int calories) {this(servingSize, servings, calories, 0);}public NutritionFacts(int servingSize, int servings, int calories, int fat) {this(servingSize, servings, calories, fat, 0);}public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {this(servingSize, servings, calories, fat, sodium, 0);}public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {this.servingSize = servingSize;this.servings = servings;this.calories = calories;this.fat = fat;this.sodium = sodium;this.carbohydrate = carbohydrate;}public void display() {// 显示构造信息System.out.println(servingSize);System.out.println(servings);System.out.println(calories);System.out.println(fat);System.out.println(sodium);System.out.println(carbohydrate);}public static void main(String[] args) {NutritionFacts cocaCola = new NutritionFacts(1, 2);cocaCola.display();}}
重叠构造器模式肯定是可行的,但是当有许多参数的时候,客户端的代码就会很难编写,并且仍然难以阅读。

遇到许多构造器参数的时候,还有第二种代替办法,即JavaBean模式,在这种模式下调用一个无参构造器来创建对象,然后调用setter方法来设置每个必要的参数,以及每个相关的可选参数:

// JavaBeans Pattern - allows inconsistency, mandates mutabilitypublic class NutritionFacts {    // Parameters initialized to default values (if any)    private int servingSize  = -1;  // Required; no default value    private int servings     = -1;  // Required; no default value    private int calories     = 0;    private int fat          = 0;    private int sodium       = 0;    private int carbohydrate = 0;    public NutritionFacts() { }    // Setters    public void setServingSize(int val)  { servingSize = val; }    public void setServings(int val)     { servings = val; }    public void setCalories(int val)     { calories = val; }    public void setFat(int val)          { fat = val; }    public void setSodium(int val)       { sodium = val; }    public void setCarbohydrate(int val) { carbohydrate = val; }    public static void main(String[] args) {        NutritionFacts cocaCola = new NutritionFacts();        cocaCola.setServingSize(240);        cocaCola.setServings(8);        cocaCola.setCalories(100);        cocaCola.setSodium(35);        cocaCola.setCarbohydrate(27);    }}
JavaBean模式相对于重叠构造器模式来说创建实例更容易了,但是其也有着严重的缺点:

  • 因为构造过程被分到了几个调用中,在构造过程中JavaBean可能处于不一致的状态。
  • 类无法仅仅通过检验构造器参数的有效性来保证一致性(调用可选参数,可选参数被初始化),试图使用处于不一致状态的对象,将会导致失败,这种错误与包含错误的代码大相径庭,因此很难调试。
  • JavaBean模式阻止了把类做成不可变得可能,这就需要程序员付出额外的女里来确保它的线程安全。

然而构建器既能保证向重叠构造器模式那样的安全,也能保证想JavaBean模式那么好的可续行。这就是Builder模式的一种形式。

其实现方式是:不直接生成想要的对象。然后客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成不可变得对象。这个builder是它构建的类的静态成员类。下面就是它的示例:

// Builder Patternpublic class NutritionFacts {    private final int servingSize;    private final int servings;    private final int calories;    private final int fat;    private final int sodium;    private final int carbohydrate;    public static class Builder {        // Required parameters        private final int servingSize;        private final int servings;        // Optional parameters - initialized to default values        private int calories      = 0;        private int fat           = 0;        private int carbohydrate  = 0;        private int sodium        = 0;        public Builder(int servingSize, int servings) {            this.servingSize = servingSize;            this.servings    = servings;        }        public Builder calories(int val)            { calories = val;      return this; }//返回Builder对象本身,以便于可以把调用连接起来。(简而言之:通过点一直链接就行,就像main方法中一样)        public Builder fat(int val)            { fat = val;           return this; }        public Builder carbohydrate(int val)            { carbohydrate = val;  return this; }        public Builder sodium(int val)            { sodium = val;        return this; }        public NutritionFacts build() {            return new NutritionFacts(this);        }    }    private NutritionFacts(Builder builder) {        servingSize  = builder.servingSize;        servings     = builder.servings;        calories     = builder.calories;        fat          = builder.fat;        sodium       = builder.sodium;        carbohydrate = builder.carbohydrate;    }    public static void main(String[] args) {        NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).            calories(100).sodium(35).carbohydrate(27).build();    }}


********************************************************************************结束语********************************************************************************************
  我在写这篇博客的时候也是一名初学者,有任何疑问或问题请留言,或发邮件也可以,邮箱为:577328725@qq.com,我会尽早的进行更正及更改。
在我写过的博客中有两篇博客是对资源的整理,可能对大家都有帮助,大家有兴趣的话可以看看!!
下载资料整理——目录:http://blog.csdn.net/fanxiaobin577328725/article/details/51894331
  这篇博客里面是我关于我见到的感觉不错的好资源的整理,里面包含了书籍及源代码以及个人搜索的一些资源,如果有兴趣的可以看看,我会一直对其进行更新和添加。
优秀的文章&优秀的学习网站之收集手册:http://blog.csdn.net/fanxiaobin577328725/article/details/52753638
  这篇博客里面是我对于我读过的,并且感觉有意义的文章的收集整理,纯粹的个人爱好,大家感觉有兴趣的可以阅读一下,我也会时常的对其进行更新。
********************************************************************************感谢********************************************************************************************

0 0