美文网首页
01静态工厂方法代替构造器

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

作者: GeekGray | 来源:发表于2018-10-02 22:23 被阅读10次

    阅读原文

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

    对于类而言,为了让客户端获得它自身的一个实例,最常用的方法就是提供一个公有的构造器。还有一种方法,类可以提供一个公有的静态工厂方法(static factory method),它只是一个返回类的实例的静态方法。

    例如:这个方法将Boolean基本类型值转换成了一个Boolean对象引用

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

    01-静态工厂方法的优势

    优势1.静态工厂方法有名称
      如果构造器的参数本身没有确切描述正被返回的对象,那么具有适当名称的静态工厂方法更容易使用,产生的客户端代码也更容易阅读。例如,构造器BigInteger(int, int, Random)返回的Bigteger可能为素数,如果用名为BigInteger.probablePrime的静态工厂方法来表示,显然更为清楚。

    一个类智能有一个带有指定签名的构造器。通过提供两个构造器,它们的参数列表只在参数类型的顺序上有所不同。实际上这并不是一个好主意。面对这样的API,用户永远记不住该用哪个构造器,结果常常会调用错误的构造器。并且,读到使用了这些构造器的代码时,如果没有参考类的文档,往往不知所云。

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

    优势2.减少对象的创建

    这使得不可变类可以使用预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用,从而避免不必要的重复对象。Boolean.valueOf(boolean)方法说明了这项技术:它从来不创建对象。这种方法类似于Flyweight模式。如果程序经常请求创建相同的对象,并且创建对象的代价很高,则这项技术可以极大第提升性能。

    静态工厂方法能为重复的调用返回相同对象,这样有助于总能严格控制在某个时刻哪些实例应该存在。这种类被称作实例受控类(instance-controlled)。编写实例受控的类有几个原因。实例受控使得类可以确保一个Singleton或者是不可实例化的。它还使得不可变变的类可以确保不会存在两个相等的实例,即当且仅当a==b的时候才有a.equals(b)为true。如果类保证了这一点,它的客户端就可以使用==操作符来代替equals(Object),这样可以提升性能。枚举enum类型保证了这一点。

    优势3.返回原返回类型的任何子类的对象

    这样我们在选择返回对象的类时就有了更大的灵活性。这种灵活性的一种应用是,API可以返回对象,同时又不会使对象的类变成公有的。以这种方式隐藏实现类会使API变得非常简洁。这项技术适用于基于接口的框架(interface-based framework)因为在这种框架中,接口为静态工厂方法提供了自然返回类型。接口不能有静态方法(Java8中接口中可以有默认方法和静态方法,并遵循类优先原则)。因此按照惯例,接口Type的静态工厂方法被放在一个名为Types的不可实例化的类中。

    例如,Java Collections Framework API的集合接口有32个便利实现,分别提供了不可修改的集合,同步集合等待。几乎所有这些实现都通过静态工厂方法在一个不可实例化的类(java.util.Collections)中导出。所有返回对象的类都是非公有的。

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

    静态工厂方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不必存在。这种灵活的静态工厂方法构成了服务提供者框架(Service Provider Framework)的基础,例如JDBC(Java数据库连接,Java Database Connectivity)API。服务提供者框架是指这样一个系统:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把他们从多个实现中解耦出来。

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

    服务提供者框架的第四个组件是可选的,服务提供者接口,这些提供者负责创建其服务实现的实例。如果没有服务提供者接口,实现就按照类名称注册,并通过反射反射进行实例化。对于JDBC来说,Connction就是他的服务接口,DriverManager.registerDriver是提供者注册API,DriverManager.get Connection是服务访问API,Driver就是服务提供者接口。

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

    // Service provider framework sketch
    
    // Sercive interface
    public interface Sercvice
    {
        ...//Service-specific methods go here
    }
    
    //Service provider interface
    public interface Provider
    {
        Service newService();
    }
    
    //Noninstantiable class for service registeration and access
    public class Services
    {
        private Services(){}//Prevents instantiation (Item 4)
    
        //Maps service names to services
        private static final Map<String,Provider>providers=new ConcurrentHashMap<String,Provider>();
        public static final String DEFAULT_PROVIDER_NAME="<def>";
        //Provider registration API
        public static void registerDefaultProvider(Provider p)
        {
            registerProvider(DEFAULT_PROVIDER_NAME,p);
        }
        public static void registerProvider(String name,Provider p)
        {
            providers.put(name,p);
        }
    
    
        //Sercivce access API
        public static Service newInstance(DEFAULT_PROVIDER_NAME);
        {
            return newInstance()
        }
        public static Service newInstance()
        {
            Provider p = providers.get(name);
            if(p==null)
            {
                throw new IllegalArgumentException("No provider registered with name:"+name);
                return p.newService();
            }
        }
    }
    

    优势4.创建参数化类型实例的时候,代码更简洁

    遗憾的是,在调用参数化类的构造器时,即使类型参数很明显,也必须指明。这通常要连续两次提供类型参数:

    Map<String,List<String>> m = new HashMap<String, List<String>>();
      随着类型参数变得越来越长,越来越复杂,但是有了静态工厂方法,编译器就可以替你找到类型参数。这被称作类型退到(type inference)Java10已经实现了。例如,假设HashMap提供了这个静态工厂:

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

    就可以用下面这句简洁的代码替代上面这段繁琐的声明:

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

    01-静态工厂方法的缺点

    缺点1.类如果不包含公有的或者受保护的构造器,就不能被子类化
    对于公有的静态方法返回的非公有类,也同样如此。例如,要想将Collections Framework中的任何方便的实现类子类化,这是不可能的。但是鼓励使用复合(composition)而不是继承

    缺点2.实际上与其他的静态方法没有任何区别

    在API文档中,它们没有像构造器那样在API文档中明确标识出来,因此,对于提供了静态工厂方法而不是构造器的类来说,要想查明如何实例化一个类,这是非常困难的。可以通过在类或者接口注释中关注静态工厂,并遵守标准的命名习惯,也可以弥补这一劣势。下面是静态工厂方法的一些惯用名称:
    valueOf-----不太严格地讲,该方法返回的实例与它的参数具有相同的值。这样的静态工厂方法实际上是类型转换方法。
    of-----valueOf的一种更为简洁的替代,
    getInstance-----返回的实例是通过方法的参数来描述的,但是不能够说与参数具有同样的值。对于Singleton来说,该方法没有参数,并返回唯一的实例。
    newInstance-----像getInstance一样,但newInstance能够确保返回的每个实例都有与所有其他实例不同。
    getType-----像getInstance一样,但是在工厂方法处于不同的类中的时候用。Type表示工厂方法返回的对象类型。
    newType-----像newInstance一样,但是在工厂方法处于不同的类中的时候用。Type表示工厂方法返回的对象的类型。

    相关文章

      网友评论

          本文标题:01静态工厂方法代替构造器

          本文链接:https://www.haomeiwen.com/subject/onwaoftx.html