Effective Java---------用静态工厂方法替代构造器

对于类而言,要获取它自身的一个实例,最传统的方法是提供一个公有的构造器。还有一种方法类可以提供一个公有的静态工厂方法他只是一个返回该类的实例的静态方法。例如:Boolean类的静态工厂方法,这个方法将boolean基本类型值转换成了一个Boolean对象引用:

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

注意,这里所说的静态工厂方法与设计模式中的工厂方法模式不同。

静态工厂方法与构造器不同的优势:

1、它们有名称

如果构造器的参数没有确切的描述被返回的对象,那么有适当名称的静态工厂方法会更容易使用。

如BigInteger(int, int, Random)方法返回的BigInteger可能为质数,Java1.4方法中新增的BigInteger.probablePrime(int, Random)静态工厂方法更为清楚(这个方法返回随机质数,int类型参数指定返回质数的bit位数)。

一个类只能带有一个指定签名的构造器,我们可以这样避开这种限制:提供多个构造器,他们的参数列表在参数类型、顺序上不同,但是对于这样的API,记住不同的参数类型与顺序是很难的,因此往往无法选择正确的构造器。

由于静态工厂方法有名称,因此不受上述限制。在一个类需要多个相同签名的构造器时,就使用静态工厂方法代替构造器,并仔细地选择名称来突出各静态工厂方法之间的区别。

2、不必在每次调用它们时都创建一个新的对象

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

如下面的代码,一共只创建了4个对象。

import java.util.HashMap;
import java.util.Map;

public class MobilePhone {

    private static Map map = new HashMap();

    String brand;
    String chip;
    double price;

    private MobilePhone(String brand, String chip, double price) {
        this.brand = brand;
        this.chip = chip;
        this.price = price;
    }

    private MobilePhone(String brand) {
        this.brand = brand;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getChip() {
        return chip;
    }

    public void setChip(String chip) {
        this.chip = chip;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "The mobile phone information:\tBrand:" + brand + "\tChip:" + chip + "\tPrice:" + price;
    }

    public static MobilePhone getMobilePhone(String brand) {
        MobilePhone mobilePhone = (MobilePhone) map.get(brand);
        if (mobilePhone == null) {
            mobilePhone = new MobilePhone(brand);
            map.put(brand, mobilePhone);
            System.out.println("---------------手机品牌:" + brand + "---------------");
        }
        return mobilePhone;
    }

    public static void main(String[] args) {
        String[] brand = {"HuaWei", "XiaoMi", "Vivo", "Oppo"};
        String[] chip = {"Qualcomm855", "Kirin980"};

        for (int i = 0; i < 20; i++) {
            MobilePhone mobilePhone = getMobilePhone(brand[(int) (Math.random() * brand.length)]);
            mobilePhone.setChip(chip[(int) (Math.random() * chip.length)]);
            mobilePhone.setPrice(Math.floor(Math.random() * 5000));
            System.out.println(mobilePhone);
        }
    }
}

输出结果
---------------手机品牌:XiaoMi---------------
The mobile phone information:    Brand:XiaoMi    Chip:Kirin980    Price:4845.0
The mobile phone information:    Brand:XiaoMi    Chip:Qualcomm855    Price:704.0
---------------手机品牌:Oppo---------------
The mobile phone information:    Brand:Oppo    Chip:Kirin980    Price:4798.0
The mobile phone information:    Brand:XiaoMi    Chip:Kirin980    Price:1905.0
---------------手机品牌:HuaWei---------------
The mobile phone information:    Brand:HuaWei    Chip:Kirin980    Price:2083.0
The mobile phone information:    Brand:Oppo    Chip:Kirin980    Price:565.0
The mobile phone information:    Brand:HuaWei    Chip:Qualcomm855    Price:4904.0
---------------手机品牌:Vivo---------------
The mobile phone information:    Brand:Vivo    Chip:Qualcomm855    Price:1744.0
The mobile phone information:    Brand:Oppo    Chip:Kirin980    Price:4336.0
The mobile phone information:    Brand:Vivo    Chip:Qualcomm855    Price:2108.0
The mobile phone information:    Brand:HuaWei    Chip:Qualcomm855    Price:634.0
The mobile phone information:    Brand:HuaWei    Chip:Qualcomm855    Price:2153.0
The mobile phone information:    Brand:Oppo    Chip:Kirin980    Price:114.0
The mobile phone information:    Brand:HuaWei    Chip:Kirin980    Price:1496.0
The mobile phone information:    Brand:Vivo    Chip:Kirin980    Price:1223.0
The mobile phone information:    Brand:XiaoMi    Chip:Kirin980    Price:2606.0
The mobile phone information:    Brand:Oppo    Chip:Qualcomm855    Price:2540.0
The mobile phone information:    Brand:Vivo    Chip:Qualcomm855    Price:2204.0
The mobile phone information:    Brand:Vivo    Chip:Kirin980    Price:4336.0
The mobile phone information:    Brand:Oppo    Chip:Kirin980    Price:4256.0

3、它们可以返回原返回类型的任何子类型对象

这样我们在选择返回对象的类时就有了更大的灵活性。这种灵活性的一种应用是,API可以返回对象,同时又不会使对象的类变成公有的,以这种方式隐藏实现类会使API变得非常简洁。

如不可修改集合、同步集合,等等,几乎所有这些实现类都通过静态工厂方法在一个不可实例化的类(java.util.Collections)中导出。另外在Java8中,接口可以有静态方法,因此静态工厂方法可以出现在接口当中。

4、所返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值

只要是已生命的返回类型的子类型,都是允许的。返回对象的类也可能随着发行版本的不同而改变。

如EnumSet类没有公有的构造器,只有静态工厂方法。EnumSet.noneOf()方法根据参数枚举类型的元素来返回不同的子类实例,当参数枚举元素个数不大于64时返回RegularEnumSet,参数枚举元素个数大于64时返回JumboEnumSet。这两个累心那个都是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);
}

5、方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在

这种灵活的静态工厂方法构成了服务提供者框架(Service Provider Framework)的基础,例如JDBC。服务提供者框架是指这样一个系统:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把他们从多个实现中解耦出来。如DriverManager.getConnection方法,返回的对象是Connection(接口)的实现类,我们可以使用Connection对象来接收。而返回的Connection实现类在未添加相关驱动jar包时是不存在的。

Connection connection = DriverManager.getConnection("","","");


//DriverManager类中代码
public static Connection getConnection(String url,
        String user, String password) throws SQLException {
        java.util.Properties info = new java.util.Properties();

        if (user != null) {
            info.put("user", user);
        }
        if (password != null) {
            info.put("password", password);
        }

        return (getConnection(url, info, Reflection.getCallerClass()));
    }


private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");

        SQLException reason = null;

        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }

        // if we got here nobody could connect.
        if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
        }

        println("getConnection: no suitable driver found for "+ url);
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }

静态工厂方法与构造器不同的缺点:

1、类如果不含公有的或受保护的构造器,就不能被实例化

2、程序员很难发现它们

在API文档中,它们没有像构造器那样被标识出来,因此,对于提供了静态工厂方法而不是构造器的类来说,要想查明如何实例化一个类是非常困难的。

工厂方法的一些惯用名称

from——类型转换方法,单个参数,返回该类型的一个相对应的实例

of——聚合方法,多个参数,返回该类型的一个实例,把它们合并起来

valueOf——比from和of更繁琐的一种替代方法

instance或者getInstance——返回的实例是通过方法的(如有)参数来描述的,到那时不能说与参数具有相同的值

create或者newInstance——像instance或者getInstance一样,但是每次调用都返回一个新的实例

getType——像getInstance一样,但是在工厂方法处于不同的类中使用的时候使用,例如:

                     FileStore fs = Files.getFileStore(path);

newType——像newInstance一样,但是在工厂方法处于不同的类中使用的时候使用,例如:

                     BufferedReader br = Files.newBufferedReader(path);

type——getType和newType的简版,例如:

                     List<Complaint> litany = Collections.list(legacyLitany);

 

简而言之,静态工厂方法和公有构造器都各有用处,我们需要理解他们各自的长处。静态工厂经常更加合适,因此切记第一反应就是提供公有的构造器,而不先考虑静态工厂。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值