zoukankan      html  css  js  c++  java
  • 创建和销毁对象

    何时以及如何创建对象,何时以及如何避免创建对象,如何确保对象能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作。

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

    对于类而言,为了让客户端获取类的一个实例,最常用的办法就是提供一个公有的构造器。现在有更好的方式来满足客户端的要求:提供一个公有的静态工厂方法,通过这个方法可以返回这个类的一个实例。例如:

    public static Blloean valueOf(boolean b){
        return b ? Boolean.TRUE : Boolean.FALSE;
    }

    静态工厂方法的优势:

    1,静态工厂方法有名称。如果某个类的构造函数本身没有明确地指明被返回的对象,那么其可阅读及使用不如静态工厂方法。一个类只能有一个指定签名的构造器(PS:所谓签名,即方法名+参数列表,并没有返回值和访问修饰符),一般程序员会提供两个或者更多的相同方法名但参数顺序不同的构造函数来突破这一限制,实际上这不是一个好的方法,因为对用户而言,常常会有调用混乱的情况,如果构造函数没有添加注释或者doc文档,那就更悲催了。

    2,不必每次调用静态工厂的时候都创建一个新的对象。这样可以让不可变类使用预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用,从而避免了创建不必要的重复对象,提高系统性能。示例就是一个代表,该方法从来不创建对象。静态工厂方法能够为重复的调用返回相同的对象,这样有助于类总能严格控制在某个时刻哪些实例应该存在。这种类被称作实例受控类。编写实例受控的类有几个原因。实例受控的类可以保证其本身是单例(singleton)或者是不可实例化的。实例受控的类还可以保证不会存在两个相等的实例,即当且仅当a == b的时候才有a.equals(b)为true。如果保证了这一点,它的客户端就可以使用==操作符来代替equals(object)方法,这样可以提高性能。枚举类型保证了这一点。

    3,静态工厂方法可以返回原返回类型的任何子类型的对象,即返回对象可以向下转型。这样我们在选择返回对象的类时就有了更大的灵活性,API可以返回对象,同时又不会使对象的类变成公有的。以这种方式隐藏实现类会使API变的非常简洁。这项技术适用于基于接口的框架(interface-based framework),因为在这个框架中,接口为静态工厂方法提供了自然返回类型。接口不能有静态方法,因此按照惯例,接口Type的静态工厂方法被放在一个名为Types的不可实例化的类中。例如,Java Collections Framework的集合接口有32个便利实现,分别提供了不可修改的集合、同步集合等等。几乎所有这些实现都通过静态工厂方法在一个不可实例化的类(java.util.Collections)中导出。所有返回对象的类都是非公有的。

    现在的Collections Framework API比导出32个独立公有类的那种实现方式要小的多,每种便利实现都对应一个类。这样不仅仅是指api数量上的减少,也是概念意义上的减少。用户知道,被返回的对象是有相关的接口精确指定的,所以他们不需要阅读有关的类的文档。使用这种静态工厂方法时,甚至要求客户端通过接口来应用被返回的对象,而不是通过它的实现类来引用被返回的对象,这是一种良好的习惯。

    公有静态工厂方法所返回的对象的类不仅可以是非公有的,而且该类还可以随着每次调用而发生变化,这取决于静态工厂方法的参数值。只要是已声明的返回类型的子类型,都是允许的。静态工厂方法返回的对象所属的类,在编写该静态工厂方法时可以不必存在。这种灵活的静态工厂方法构成了服务提供者框架(Service Provider Framework)的基础,例如,JDBC API。服务提供者框架是指这样一个系统:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把它们从多个实现中解耦出来。

    服务提供者框架中有三个重要的组件:服务接口(Service Interface),这是提供者实现的;提供者注册API(Provider Registration API),这是系统用来注册实现,让客户端访问他们的;服务访问API(Service Access API),是客户端用来获取服务的实例的。服务访问API一般允许但是不要求客户端指定某种选择提供者的条件。如果没有这样的规定,API就会返回默认实现的一个实例。服务访问API是”灵活的静态工厂“,它构成了服务提供者框架的基础。

    服务提供者框架的第四个组件是可选的:服务提供者接口(Service Provider Interface),这些提供者负责创建其服务实现的实例。如果没有服务提供者接口,实现就按照类名注册,并通过反射方式进行实例化。对于JDBC来说,实现就按照类名称注册,并通过反射方式进行实例化。对于JDBC来说,Connection就是他的服务接口,DriverManager.registerDriver是提供者注册API,DriverManager.getConnection是服务访问API,Driver就是服务提供者接口。下面是一个默认的服务提供者框架的实现和一个反射方式实现的服务提供者框架。

    public class SqlConn {
        public Statement getSt(){
            Statement st=null;
            try {
                Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");
                Connection con=DriverManager.getConnection("jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=shop","sa","sa");
                st=con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);
                return st;
            } catch (Exception e) {
                // TODO 自动生成 catch 块
                e.printStackTrace();
            }
            return st;
        }
    }

    服务提供者框架模式存在无数种变体。例如,服务访问API可以利用适配器模式(Adapter),返回比提供者需要的更丰富的服务接口,下面是一个简单的实现,包含一个服务提供者接口和一个默认提供者。

    //服务提供者模式
    
    //服务接口
    public interface Service {
    	//Service-specific methods go here
    }
    
    //服务提供者接口
    public interface Provider {
    	Servive newService();
    }
    
    //不可实例化的服务注册及访问服务
    public class Services {
    	private Services(){} //防止实例化
    	
    	//用一个map存放所有服务
    	private static final Map<String,Provider> providers = new ConcurrentHashMap<String,provider>();
    	
    	public static final String DEFAULT_PROVIDER_NAME = "<def>";
    	
    	//提供注册API
    	public static void registerDefaultProvider(Provider p){
    		registerProvider(DEFAULT_PROVIDER_NAME,p);
    	}
    	
    	public static void registerProvider(String name,Provider p){
    		providers.put(name,p);
    	}
    	
    	//服务访问API
    	public static Service newInstance(){
    		return newInstatnce(DEFAULT_PROVIDER_NAME);
    	}
    	
    	public static Service newInstance(String name) {
    		Provider p = providers.get(name);
    		
    		if(null == p){
    			throw new IllegalArgumentException("No provider registered with name : " + name);
    		}
    		
    		return p.newService();
    	}
    }

    4,在创建参数化类型实例的时候,它们使代码更加简洁。在JDK1.6中,在调用参数化的构造器时,即使参数类型很明显,也必须指明,比如:

    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()
    静态工厂的缺点:

    1,如果类不含有公有的(public)或者受保护的(protected)的构造器,就不能被子类化。

    2,静态工厂方法和其他静态方法没有任何区别,在API中没有明确标识出来。不过有些静态工厂方法有惯用名称:

    • valueOf:不太严格地将,该方法返回的实例与它的参数具有相同的值。这样的静态工厂方法实际上是类型转换的方法。
    • of:valueOf的简洁写法。
    • getInstance:返回的实例是通过方法的参数来描述的。但是不能够说与参数有相同的值。对于Singleton来说,该方法没有参数,并且返回唯一的实例。
    • newInstance:像getInstance一样,但newInstance能够确保返回的每个实例都与所有其他实例不同。
    • getType:像getInstance一样,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法所返回的对象类型。
    • newType:像newInstance一样,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法所返回的对象类型。

    简而言之,静态工厂方法和公有的构造器各有用处。但是静态工厂方法一般更加合适,因此切记第一反应就是提供公有的构造器,而不是优先考虑静态工厂方法。
  • 相关阅读:
    Linux系统主流架构一
    CentOS7.2部署KVM虚拟机
    MySQL
    MQ消息队列
    LVM
    Docker管理工具-Swarm部署记录
    Linux下DNS简单部署(主从域名服务器)
    kvm虚拟机命令梳理
    批量创建10个系统帐号tianda01-tianda10并设置密码
    随笔分类
  • 原文地址:https://www.cnblogs.com/luckyliu/p/2382584.html
Copyright © 2011-2022 走看看