chapter2

Chapter2

Tip1 静态工厂方法代替构造器

公有的静态方法,只是一个返回类实例的静态方法。

静态工厂方法的优势:

优势一:

有名称,如果构造器本身没有正确的描述被返回的对象,具有适当名称的静态工厂方法会更加适用。由于一个类仅能提供一个构造器,程序员可能会调用错误的构造器。用静态方法代替构造器,可以避免构造器的错误使用。

优势二:

不必每次都返回新的对象,直接返回已经创建好的对象就行,适合单例模式、不可实例化、不可变的类,这样可以确保不生成两个相等的实例。

优势三:

(有点没理解好)可以返回原返回类型的任何子类型的对象。
服务提供者框架(Service Provider Framework)
多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现(通过重写服务类中的那个关键函数)。感觉上好像就是一个多态的使用而已,还是没理解到位?
下面是一个实现的例子:
这个例子大概就是,有好多service provider,它们可以提供不同的service。最一般的想法就是,一个provider类里面弄一个doService的方法,但这么做显然不太好,会有许多重复的代码。于是需要一个Service接口,里面至少要有一个doService方法,其他Provider再具体生成的时候都需要去继承这个接口来实现它们自己的doService方法。此外还需要有个Services的类来提供一些工具函数(API)让provider对Service的调用过程变得更方便。Services里面基本全是一些静态方法。比如生成新的Provider实例(这里就用到了上面提到的用静态方法代替构造器),根据已有的Provider实例名来返回实例,把一个新生成的实例注册到Service的管理器中(用一个map 保存实例名字字符串和实例对象的映射关系)

源码如下:
package chapter2_serviceprovider;
public interface Service {
//some methos to do
public void doService();
}

package chapter2_serviceprovider;

public interface Provider {
	
	Service newService();
}


package chapter2_serviceprovider;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

// 1 provide a map to store the relationship of name and provider instance
// 2 provide a registration api to excute the puts method of map
// 3 provide Service access api to return the instance of provider from map 
public class Services {
		private Services(){}
		
		//providers is a map to store the 
		//relation of the service name and the provider
		private static final Map<String,Provider>providers=
				new ConcurrentHashMap<String,Provider>();
		
		public static final String DEFAULT_PROVIDER_NAME="<def>";
		
		//provider registraion api
		public static void registerDefaultProvider(Provider p){
			registerProvider(DEFAULT_PROVIDER_NAME,p);
		}
		
		public static void registerProvider(String name,Provider p){
			providers.put(name, p);
		}
		
		//Service access api
		public static Service newInstance(){
			return newInstance(DEFAULT_PROVIDER_NAME);
			
		}
		
		public static Service newInstance(String name){
			Provider p=providers.get(name);
			if(p==null){
				throw new IllegalArgumentException(
						"No provider registered with name:"+name);
			}
			return p.newService();
		}
		
}

package chapter2_serviceprovider;
public class testServiceProvider {
	
	//create different providers
	//every provider should realize the newService in their own way
	 private static Provider DEFAULT_PROVIDER = new Provider() {  
	public Service newService() {  
	return new Service() {  
	
	public void doService() {  
		System.out.println("do the service in the default way") ; 
	}  
	};  
	}  
	}; 
	
		 private static Provider SERVICE_WAYA = new Provider() {  
		public Service newService() {  
		return new Service() {  
		public void doService() {  
		System.out.println("do the service in waya") ;  
		}  
		};  
		}  
		}; 
		
			 private static Provider SERVICE_WAYB = new Provider() {  
			public Service newService() {  
			return new Service() {  
			public void doService() {  
				System.out.println("do the service in wayb") ; 
			}  
			};  
			}  
			};
	
	public static void main(String[] args) {

		//regist the created Providers
		//namely create the maping betwwen the provider and its key name
		Services.registerDefaultProvider(DEFAULT_PROVIDER);
		Services.registerProvider("servicea",SERVICE_WAYA);
		Services.registerProvider("serviceb",SERVICE_WAYB);
		
// create the newinstance
		Service s1=Services.newInstance();
		Service s2=Services.newInstance("servicea");
		Service s3=Services.newInstance("serviceb");
		
//invoke the service
		s1.doService();
		s2.doService();
		s3.doService();
				
	}
}

优势四

创建参数化实例的时候,可以更加简洁,这里的例子用的是集合中的例子
如果想要通过泛型创建一个Map比如
Map<String,List> m=new HashMap<String,List>()
这样可能显得很繁琐,尤其是使用迭代器循环的时候,可能里里外外要好多层。
如果HashMap提供一个类似的静态工厂方法(貌似目前还没有类似的方法)
Public static<K,V>HashMap<K,V>newinstance(){
return new HashMap<K,V>()
}

这样直接通过HashMap.newinstance()来生成新的实例,会方便许多。
在自己编写类似的工具类的时候,可以借鉴相关的思路,来简化程序
缺点一
光看有点是不对的,如果该类不含有共有的或者受保护的构造器,就不能被实例化。比如说Collections类,它里面全都是static方法和私有的构造器,因此不能被实例化。鼓励使用复合的继承的方式对其进行操作

缺点二
与其他静态方法实际上没有什么本质的区别。
总结
静态方法和共有构造器各有用处,静态方法通常情况下更合适。因此我们的第一反应应该是使用静态工厂,而非是使用共有的构造器。

Tip2 遇到多个构造器参数时,要考虑使用构造器

这部分主要介绍的是Builder模式

具体内容:

下面是一个实际的例子

package chapter2_buildermodel;
//using builder to create the instance of NutritionFacts
public 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;
	
	//initialize the parameters by a builder
	private NutritionFacts(Builder builder){
		servingSize=builder.servingSize;
		servings=builder.servings;
		calories=builder.calories;
		fat=builder.fat;
		sodium=builder.sodium;
		carbohydrate=builder.carbohydrate;
	}
	
	public void info(){
		System.out.println("the servingSize is "+servingSize);
		System.out.println("the servings is "+servings);
		System.out.println("the calories is "+calories);
		System.out.println("the fat is "+fat);
		System.out.println("the sodium is "+sodium);
		System.out.println("the carbohydrate is "+carbohydrate);


	}
	
	public static class Builder{
		//required parameters
		private final int servingSize;
		private final int servings;
		
		//optionl parameters (shuld provide a default value)
		private int calories=0;
		private int fat=0;
		private int carbohydrate=0;
		private int sodium=0;
		
		//every Builder method return this builder instance
		public Builder(int servingSize,int servings){
			this.servingSize=servingSize;
			this.servings=servings;
		}
		
		public Builder calories(int val){
			calories=val; return this;
		}
		
		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;
		}
		
		//return this initialized instance by the build method at last
		public NutritionFacts build(){
			//invoke the private constructor of NutritionFacts
			//this constructor use a builder instance to initaialze the parameter
			return new NutritionFacts(this);
		}
		
	}
	
	public static void main(String[] args) {
		//create the NutritionFacts instance by the Builder model
		NutritionFacts cocaCola=new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
		cocaCola.info();
	}
}
这样处理起来,比那种使用多个构造方法的形式确实灵活好多,但是还是比较麻烦。在一些类似的脚本语言中,可能对于类似问题的处理会好一些,比如早python中,直接在函数声明的时候就能指定默认的参数。传参的时候也能用显式的方式指定到底是给哪个变量传递的。

总结
如果类的构造器或者静态工厂方法有多个参数,或者以后可能会拓展出多个参数出来,Builder模式是很推荐的选择,对参数的检验也变得更加灵活,更易于编写维护。
当然缺点就是代码可能会显得冗长,只有在多参数的时候才适合使用。

书上提供的另外两种方式是设置多个构造器,或者是使用JavaBean的方式,具体参考书上内容。

Tip3 用私有构造器或者枚举类型强化Singleton属性

单例模式(singleton)似乎是必须要掌握的一个设计模式了,好多时候只需要生成一个单独的实例,具体的可以参考之前整理的这个(http://www.cnblogs.com/Goden/p/3956803.html)

再补充一点,因为反射方式的存在,可以通过反射的方式调用私有的构造器生成新的实例,于是为了防止有人利用这一点恶意破坏程度,可以再构造函数中添加保护机制,如果实例被第二次创建的时候,就会抛出一个异常。

还有就是单例的序列化的问题

之前那个是比较常见的一个单例实现,通过枚举的方式实现单例,看起来似乎更加简洁,并且单元素的枚举类型已成为实现singleton的最佳方法。(枚举这里相关内容具体参考第6章)
枚举就是单例的泛型化,这个说的也是很好,应该加强理解。在一个枚举类中,定义的每个instance都是单例的。

Tip4 私有构造器强化类的不可实例化的属性

有些类是不希望被实例化的,比如某些工具类,如java.lang.Math,或者java.util.Arrays,再或者java.util.Collections等等,对于这种类型的类,通常会提供给一个私有的构造器,来防止这个类被实例化。通常在这种私有的构造器上加一个注释,就是明确的指出,这个类不希望被实例化,比如// supperss default constructor for noninstamtiability

Tip5 避免创建不必要的对象

这个比较容易理解,就是在方法中尽量减少 Object o=new Object()这种语句,这样的话,每次方法被调用都会创建一个新的对象出来,到底是效率低了好多。
根据前面的几条,能使用静态方法的,最好还是使用静态方法。
下面这个例子,是好方法和差方法的对比:
package chapter2_objectReuse;

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

//check if the birth date betwwen baby boomer
public class Personold {
	
	private final Date birthDate=null;
	
	//other fields, methods, and constructor omitted
	//Bad Way (the Calendar instance is created every time when the method is invoked):
	public boolean isBabyBoomer(){
		Calendar gmtCal=Calendar.getInstance(TimeZone.getTimeZone("GMT"));
		gmtCal.set(1946, Calendar.JANUARY,1,0,0,0);
		Date boomStart=gmtCal.getTime();
		gmtCal.set(1965, Calendar.JANUARY,1,0,0,0);
		Date boomEnd=gmtCal.getTime();	
		return birthDate.compareTo(boomStart)>=0&&birthDate.compareTo(boomEnd)<0;
	}

}

package chapter2_objectReuse;

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class Personnew {
	private final Date birthDate=null;
	//other fields, methods, and constructor omitted
	//appoint the start time and the end time 
	private static final Date BOOM_START=null;
	private static final Date BOOM_END=null;
	
	// a better way to initialize the parameter
	static{
		Calendar gmtCal=Calendar.getInstance(TimeZone.getTimeZone("GMT"));
		gmtCal.set(1946, Calendar.JANUARY,1,0,0,0);
		Date BOOM_START=gmtCal.getTime();
		gmtCal.set(1965, Calendar.JANUARY,1,0,0,0);
		Date BOOM_END=gmtCal.getTime();
	}
	
	public boolean isBabyBoomer(){
		return birthDate.compareTo(BOOM_START)>=0&&birthDate.compareTo(BOOM_END)<0;
	}

}
第一个例子中,每次都要新生成一个Calemdar类,影响效率,后面的一个实现中,通过static块的方式,将参数都初始化好存了下来,这样比较好。

后面还说了适配器的情形(有点没看懂)还有自动装箱的情况,优先使用基本类型而不是自动装箱类型,比如计算long的时候写成了Long,这样的话,实例就会被自动装箱,生成许多不必要的实例,影响开销。

现在有点晕了,后面又说,这种避免对象创建的策略也不是绝对的,小对象的创建和回收都是比较廉价的,如果通过创建附加的对象能够提升程序的清晰性,这也是推荐的,看来还是具体情况具体分析了。

Tip6 消除过期引用

这一个tip主要是讨论一个内存泄露的问题。 内存泄露可以由过期引用引起,所谓过期引用就是指永远不会再被接触的引用。 比如下面的这个例子。 package chapter2_overduereference;
import java.util.Arrays;
import java.util.EmptyStackException;

public class Stack {
	private Object[]elements;
	private int size=0;
	private static final int DEFAULT_INITIAL_CAPACITY=16;
	
	public Stack(){
		elements=new Object[DEFAULT_INITIAL_CAPACITY];
		
	}
	public void push(Object e){
		ensureCapacity();
		elements[size++]=e;
	}
	public Object pop(){
		if(size==0)
			throw new EmptyStackException();
		return elements[--size];
	}
	
	//ensure space for one more elements
	//roughly doubling the capacity each time the array need grow
	private void ensureCapacity(){
		if(elements.length==size)
			elements=Arrays.copyOf(elements, 2*size+1);
	}

}
看起来代码也没什么错误,关键是在pop函数的地方,如果先入栈好多,再出栈,那么先前的引用就没有被释放掉,因为对于垃圾回收器来说,前面已经不用的数组区域和后面正在使用的数组区域是没有区别的,因此要用手工的方式使得不用的引用指向null才能保证被垃圾回收期器回收(与垃圾回收器的机制有关?)。这主要是因为stack是自己管理内存的。

好的做法应该是这样:
public Object pop(){
if(size==0)
throw new EmptyStackException();
Object result= elements[--size];
elements[size]=null;
return result;
}

事实上在官方的Stack实现中,也是这样做的:
在pop方法中,调用了一个removeElementAt方法来去掉指定位置的元素,在这个方法的最后,也把去掉的那个元素的引用赋成了null。

还有两种内存泄露的来源,由于没有实例,理解的不是太好。
缓存
监听器和其他回调

内存泄露一般不容易被发现,应该提前就预测到内存泄露可能会发生的地方。

Tip 7 避免使用终结方法

这个tip还是蛮重要的,算是有一点亲身体会。特别是使用finally来关闭连接状态的时候,无论是socket或者是是数据库的连接,并且还是那种多个线程的环境下。 这个后面介绍的部分还是有点没看懂。

除非是作为安群网,或者是为了终止非关键的本地资源,否则请不熬使用终结方法。在一些很少见的情况下,既然使用了终结方法,就要牢记调用super.finalize方法。(这一部分不太了解)如果用终结方法作为安全网,要记得记录终结方法的非法用法。最后,如果需要把终结方法与共有的非final类关联起来,请考虑使用终结方法的守卫者,以确保即使使子类的终结方法未能调用super.finalize该终结方法也会被执行。

posted @ 2015-01-29 10:19  hessen  阅读(212)  评论(0编辑  收藏  举报