Effective Java学习笔记--考虑使用静态工厂方法替代构造方法总结

什么是静态工厂方法

顾名思义,静态工厂方法的特性主要来自于两个关键字:静态和工厂,静态方法即归属于类或接口(Java8之后)的方法,而工厂方法就是用来创造对象的方法。整体理解静态工厂方法就是归属于类或接口的用来创造对象的方法。

但是Java的类在设计之初就有了公共构造方法作为创建对象的方法,为什么还要有静态工厂方法来创建对象呢?这就引出了本节的内容,作者认为静态工厂方法相比于公共构造方法在某些场景下有着很明显的优势。

静态工厂方法相对于公共构造方法的优势

与构造方法不同,它们是有名字的

不仅仅是有名字,而且这个名字可以自行定义。这样可以更好的辨别出到底构造的是类本身的对象、子类的对象或者是特殊的类对象,比如下面的随机数生成器:

import java.util.ArrayList;
import java.util.List;

public class withname{
    public static void main(String[] args) {
        int count = 10;
        List<Integer> randomInt= new ArrayList<>();
        List<Integer> randomOdd= new ArrayList<>();
        List<Integer> randomEven = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            randomInt.add(RandomNumber.createRandomNumber());
            randomOdd.add(RandomNumber.createRandomOddNumber());
            randomEven.add(RandomNumber.createRandomEvenNumber());
        }
        System.out.println("############Random Number##########");
        for (Integer integer : randomInt) {
            System.out.println(integer);
        }
        System.out.println("############Random Odd Number##########");
        for (Integer integer : randomOdd) {
            System.out.println(integer);
        }
        System.out.println("############Random Even Number##########");
        for (Integer integer : randomEven) {
            System.out.println(integer);
        }
    }
}


class RandomNumber{
    public static int createRandomNumber(){
        /*
        * 随机生成一个0-100的整数
        * */
        return  (int) (Math.random()*100);
        
    }

    public static int createRandomOddNumber(){
        int num = (int) (Math.random()*100);
        while (num%2==0) {
            System.out.println(num);
            num = (int) (Math.random()*100);
        }

        return num;
    }

    public static int createRandomEvenNumber(){
        int num = (int) (Math.random()*100);
        while (num%2!=0) {
            num = (int) (Math.random()*100);
        }

        return num;
    }

}

这个随机数生成器RandomNumber有三个静态工厂方法,分别产生一般随机数、奇数随机数和偶数随机数,通过名字可以很容易了解到。但如果使用公共构造方法的话,首先,相同签名的构造方法只能有一个,因此要想构建如上三个构造方法的话只能通过通过不同的签名来实现(比如设置一些不同的参数?),如果是原本就有参数的方法就只能通过改变参数顺序来实现,这对于用户来说是非常不友好的。

与构造方法不同,它们不需要每次调用时都创建一个新对象

这对于一些对资源使用要求比较高的场景(比如企业级应用的数据库链接池设计)以及单例构建等场景有很大的应用价值。构造方法必定会返回一个新建一个对象的引用,因此必定会有资源的占用,而静态构造方法的返回内容可以自行定义。

这里通过单例模式来说明,可以看到静态工厂方法getInstance会检测Singleton_second对象是否已经创建,如果已经创建则直接返回创建的对象。

public class Singleton_second {
    private volatile static Singleton_second instance;

    private Singleton_second(){};

    public static Singleton_second getInstance(){
        if (instance==null) {
            synchronized (Singleton_second.class){
                if (instance==null) {
                    instance=new Singleton_second();
                }
            }
        }
        return instance;
    }
}

与构造方法不同,它们可以返回其返回类型的任何子类型的对象

这为用户在选择返回对象的类时提供了很大的灵活性,比如Collections这一个类的API可以用来实例化所有的集合,而且不会把这个集合的类暴露出来。这里用一个简单的例子。比如对于Car这个接口,可以去实现所有不同的车类型,同时不返回具体的子类:

public interface Car {
    void run();

    void stop();

    public static Car getCar(){
        return new Car(){
            @Override
            public void run() {
                System.out.println("car is running");
            }

            @Override
            public void stop() {
                System.out.println("car is stop");
            }
        };
    }

    public static Car getSUV(){
        return new SUV();
    }
    public static Car getHatchback(){
        return new Hatchback();
    }
    public static Car getSedan(){
        return new Sedan();
    }
}

这里Car接口也通过getCar()实现了一个自然返回类型。

返回对象的类可以根据输入参数的不同而不同

这个特性作者直接举例了EnumSet 这个类,大多数枚举类型具有 64 个或更少的元素,静态工厂将返回一个 RegularEnumSet 实例, 底层是long 类型;如果枚举类型具有65个或更多元素,则工厂将返回一个 JumboEnumSet 实例,底层是long 类型的数组。这极大节约了资源的使用。

在编写包含该方法的类时,返回的对象的类不需要存在

这个特性乍一看没有什么特别之处,但它其实是服务提供者框架的核心一环。我们知道服务提供框架是服务提供方通过各自实现服务接口形成不同的服务,在通过注册后由统一的服务访问API        去调用相关服务,这里的服务访问API要如何做到把所用的服务都聚合进来,就需要灵活的静态工厂方法,而且这个方法可以预先设置相关服务的返回方式,哪怕这个服务还没有实现。

比如我们要设计一个邮件发送服务的框架,我们希望用户可以通过接口轻松的选择不同的邮件发送策略,而不必关注具体的实现细节,同时就算有的策略没有实现,我们也可以先在接口方法里把这些策略的调用接口实现了(这其实就做到了抽象和实现的解耦设计)。

接口定义

public interface MailService{
    void sendMail(String recipient, String subject, String content);
}

实现类

// SMTP邮件服务实现
class SmtpMailServiceImpl implements MailService {
    @Override
    public void sendMail(String recipient, String subject, String content) {
        // 实现SMTP发送邮件的逻辑
    }
}

// API邮件服务实现
class ApiMailServiceImpl implements MailService {
    @Override
    public void sendMail(String recipient, String subject, String content) {
        // 实现通过API发送邮件的逻辑
    }
}

静态工厂方法

public class MailServiceFactory {
    private MailServiceFactory() {} // 私有构造器,避免直接实例化
    
    // 静态工厂方法,根据参数返回不同实现的MailService实例
    public static MailService createMailService(String type) {
        switch (type.toLowerCase()) {
            case "smtp":
                return new SmtpMailServiceImpl();
            case "api":
                return new ApiMailServiceImpl();
            case "LMTP" //注:LMTP服务还没有通过接口实现,但可以先在静态工厂方法里设置
                throw new UnsupportedOperationException("LMTP service implementation is not supported yet.");
            default:
                throw new IllegalArgumentException("Unsupported mail service type.");
        }
    }
}

这里的静态工厂方法集成在辅助类当中,Java8以后接口也支持静态工厂方法,可以直接在接口内进行定义。

客户端调用

public class MailClient {
    public static void main(String[] args) {
        MailService mailService = MailServiceFactory.createMailService("smtp");
        mailService.sendMail("user@example.com", "Test Email", "This is a test email.");
        
        // 用户可以轻松切换到另一种邮件发送方式
        mailService = MailServiceFactory.createMailService("api");
        mailService.sendMail("another@example.com", "Another Test", "Another test using API.");
    }
}

总之,静态工厂方法的灵活程度确实要远远大于公共构造方法,尤其是在服务提供者框架以及面向接口的编程设计中会有大量的应用。

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值