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

本文探讨了静态工厂方法在编程中的五个优点,如命名自由、对象复用、灵活性、返回子类型对象和控制类的扩展。同时也指出了无法创建子类和可能在API文档中隐藏的缺点。通过实例和JDBC服务提供者框架的应用来说明其适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

用静态工厂方法来代替构造方法。

public class Student {
    private String name;
    private int age;
    private String studentId;

    private Student(String name, int age, String studentId) {
        this.name = name;
        this.age = age;
        this.studentId = studentId;
    }

    public static Student createStudent(String name, int age, String studentId) {
        // 在静态工厂方法中执行一些额外的逻辑
        // 例如参数验证、生成默认的学生ID等

        // 调用私有构造器创建学生对象
        return new Student(name, age, studentId);
    }

    // Getter 和 Setter 方法...

}

静态工厂方法相较于构造方法的五个优点:

  1. 有自己的名称,而构造方法必须与类名一致。
  2. 不用每次被调用时都会创建一个新的对象,它可以返回预先构造好的对象。
  3. 可以返回所声明的返回类型的任何子类型的对象。
  4. 所返回对象的类可以输入的参数不同而改变。
  5. 在编写包含该方法的类时,所返回对象的类并不一定存在。

缺点:

  1. 如果没有共有的或者受保护的构造方法,就无法为这些类创建子类。(因祸得福)
  2. 在API文档中可能会很难发现这个方法。(无伤大雅)

下面来详细谈一谈每一个优缺点:

优点一:

静态工厂方法有自己的名称,而构造方法必须与类名一致:

import java.time.LocalDate;

public class DateUtils {
    private DateUtils() {
        // 私有构造方法,防止实例化
    }

    public static LocalDate createToday() {
        // 创建表示当前日期的对象
        return LocalDate.now();
    }

    public static LocalDate createDate(int year, int month, int day) {
        // 创建指定年月日的日期对象
        return LocalDate.of(year, month, day);
    }
}

优点二:

静态工厂方法不用每次被调用时都会创建一个新的对象,它可以返回预先构造好的对象:

单例模式(Singleton Pattern)是一种创建型设计模式,旨在确保类只有一个实例,并提供全局访问点以访问该实例。

单例模式的主要特点是:

  1. 保证类只有一个实例:通过限制类的实例化过程,确保在程序运行期间只存在一个实例对象。
  2. 全局访问点:提供一个全局的访问方法,使其他对象可以方便地获取该单例实例。

使用单例模式的主要优点包括:

  1. 对资源的集中管理:单例模式可以集中管理某些共享的资源,确保资源的一致性和可靠性。
  2. 节约系统资源:由于只有一个实例存在,可以减少系统开销,节约内存和处理器等资源。
  3. 简化对象之间的通信:通过单例对象,可以简化对象之间的通信,避免了传递多个对象引用的复杂性。

常见的单例模式实现方式有两种:它们的区别在于对象的创建时间点不同。

  1. 饿汉模式(Eager Initialization):

    • 在类加载时就创建并初始化单例对象。
    • 对象在整个生命周期中都存在,无论是否被使用。
    • 线程安全,不需要考虑多线程环境。
    • 简单直接,但可能导致资源浪费。
  2. 懒汉模式(Lazy Initialization):

    • 在需要时才创建并初始化单例对象。
    • 对象在首次使用时创建,延迟了对象的实例化时间。
    • 需要考虑多线程环境下的线程安全性。
    • 在多线程环境下可能需要额外的同步机制,可能会影响性能。
// 饿汉模式
public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {
        // 私有构造函数
    }

    public static Singleton getInstance() {
        return instance;
    }

    // 其他方法...
}
// 懒汉模式
public class Singleton {
    private static Singleton instance;

    private Singleton() {
        // 私有构造函数
    }

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    // 其他方法...
}

优点三:

静态工厂方法可以返回所声明的返回类型的任何子类型的对象:

举个例子:createShape静态工厂方法声明的返回值类型是Shape,但它却可以返回Shape的子类型Circle和Square。

public class Shape {
    private String name;

    private Shape(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public static Shape createShape(String name) {
        if (name.equalsIgnoreCase("circle")) {
            return new Circle();
        } else if (name.equalsIgnoreCase("square")) {
            return new Square();
        } else {
            return new Shape(name);
        }
    }
}

public class Circle extends Shape {
    public Circle() {
        super("Circle");
    }
}

public class Square extends Shape {
    public Square() {
        super("Square");
    }
}

java.util.Collections 是 Java 标准库中提供的一个实用类,它包含了一系列静态方法,用于操作和处理集合类(如 List、Set、Map 等)的工具方法。这些方法包括创建不可修改的集合、集合的排序、查找元素、线程安全包装等,其中的很多方法也是返回了所声明的类型的子类型。

以下是一些 java.util.Collections 类中常用的静态工厂方法:

  1. 创建不可修改的集合(Immutable Collections):

    • emptyList():返回一个空的不可修改的 List
    • emptySet():返回一个空的不可修改的 Set
    • emptyMap():返回一个空的不可修改的 Map
    • unmodifiableList(List<? extends T> list):返回一个不可修改的 List,基于指定的列表。
    • unmodifiableSet(Set<? extends T> set):返回一个不可修改的 Set,基于指定的集合。
    • unmodifiableMap(Map<? extends K, ? extends V> map):返回一个不可修改的 Map,基于指定的映射。
  2. 集合的排序和查找:

    • sort(List<T> list):对指定的列表进行原地排序(升序)。
    • reverse(List<T> list):反转指定列表中元素的顺序。
    • shuffle(List<T> list):随机打乱指定列表中元素的顺序。
    • binarySearch(List<? extends Comparable<? super T>> list, T key):二分查找指定列表中的元素。
    • binarySearch(List<? extends T> list, T key, Comparator<? super T> c):使用自定义比较器进行二分查找。
  3. 线程安全的集合包装:

    • synchronizedList(List<T> list):返回一个线程安全的 List,基于指定的列表。
    • synchronizedSet(Set<T> set):返回一个线程安全的 Set,基于指定的集合。
    • synchronizedMap(Map<K,V> map):返回一个线程安全的 Map,基于指定的映射。

这些静态工厂方法提供了方便的方式来创建和操作集合对象,简化了集合操作的代码实现。它们充分利用了 Java 泛型和静态方法的特性,提供了类型安全和易用性。

优点四:

静态工厂方法所返回对象的类可以输入的参数不同而改变:

在Java中,RegularEnumSet和JumboEnumSet是EnumSet的两个内部实现类。它们都是用于存储枚举类型元素的集合。

RegularEnumSet:

  • RegularEnumSet是EnumSet的默认实现类。
  • 它使用一个位向量(bit vector)来表示集合中的元素。
  • 当枚举类型的元素数量较少时(通常在64个或更少),RegularEnumSet会被使用。
  • RegularEnumSet的内部使用了long类型的数组,每个位表示一个枚举元素的存在与否。这种紧凑的表示形式使得RegularEnumSet在性能和内存消耗方面表现优异。

JumboEnumSet:

  • 当枚举类型的元素数量超过64个时,EnumSet会使用JumboEnumSet作为其内部实现。
  • JumboEnumSet使用一个BitSet来表示集合中的元素。
  • BitSet是一个更大的位向量,它可以支持更多的枚举元素。
  • JumboEnumSet以较高的内存消耗为代价,提供了对大型枚举集合的支持。

在大多数情况下,你不需要直接使用RegularEnumSet或JumboEnumSet,而是通过EnumSet的静态工厂方法来创建EnumSet对象。EnumSet会根据枚举类型的元素数量自动选择使用适当的内部实现类。

优点五:

静态工厂方法在编写包含该方法的类时,所返回对象的类并不一定存在:

在JDBC(Java数据库连接)中的服务提供者框架是一个典型的例子,其中静态工厂方法可以返回对象的类在编写包含该方法的类时并不一定存在。

在JDBC中,服务提供者框架用于加载和管理数据库驱动程序。驱动程序供应商可以通过实现特定接口并提供驱动程序的实现来注册其驱动程序。服务提供者框架允许应用程序通过静态工厂方法获取适当的驱动程序实例,而无需显式引用特定的驱动程序类。

下面是一个简化的示例,展示了JDBC服务提供者框架的代码:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class JDBCProviderFramework {
    // 私有构造函数,防止实例化
    private JDBCProviderFramework() {
    }

    // 静态工厂方法,返回数据库连接对象
    public static Connection getConnection(String url, String username, String password) throws SQLException {
        // 加载并注册合适的数据库驱动程序
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new SQLException("Failed to load database driver");
        }

        // 获取数据库连接
        return DriverManager.getConnection(url, username, password);
    }
}

public class Main {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/mydb";
        String username = "root";
        String password = "password";

        try {
            Connection connection = JDBCProviderFramework.getConnection(url, username, password);
            // 使用数据库连接执行操作
            // ...
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

在上述示例中,JDBCProviderFramework 类是服务提供者框架的一部分。它包含一个私有构造函数和一个静态工厂方法 getConnection(),用于获取数据库连接对象。

在 getConnection() 方法中,首先使用 Class.forName() 方法加载并注册合适的数据库驱动程序,这里以MySQL驱动程序为例。然后,通过 DriverManager.getConnection() 方法获取数据库连接对象,并将其返回。

在 Main 类的 main() 方法中,我们使用 JDBCProviderFramework.getConnection() 方法获取数据库连接对象,并在实际应用程序中使用该连接对象执行数据库操作。

这里的关键是,在编写 JDBCProviderFramework 类时,并没有显式引用或依赖于特定的数据库驱动程序类。相反,通过使用服务提供者框架和静态工厂方法,可以在未来动态加载和使用不同的数据库驱动程序,而无需修改 JDBCProviderFramework 类的代码。

这种设计允许在未来定义或扩展返回的对象类(即不同的数据库驱动程序),以适应新的数据库技术或供应商。通过配置和动态加载,应用程序可以灵活地选择和切换不同的数据库驱动程序,而不需要修改主要的代码逻辑。

这个例子展示了静态工厂方法在JDBC服务提供者框架中的应用,其中返回的对象的类在编写包含该方法的类时并不一定存在,而是根据具体的实现在运行时动态加载和使用。

这个太难理解了,感兴趣的读者可以去了解一下服务提供者框架!!

缺点一:

静态工厂方法因为没有共有的或者受保护的构造方法,就无法为这些类创建子类。(因祸得福)

        静态工厂方法的一个特点是通过私有构造函数来限制实例化,因此没有公有或受保护的构造方法。这导致无法直接创建该类的子类,从而在一定程度上确保了类的封装性和不可继承性。

        这种限制有时也可以被视为一种优势,因为它可以防止不必要或不合适的继承。静态工厂方法可以在类的实现中隐藏构造函数,将对象的创建控制权交给工厂方法,从而提供更精确的控制和灵活性。

尽管无法直接创建子类,但可以通过其他方式实现类似的扩展和定制。以下是一些常见的方法:

        组合和委派:使用静态工厂方法返回的对象作为另一个类的成员,并将其方法暴露给外部使用。这样可以通过组合和委托的方式实现类似于继承的行为。

接口实现:通过实现接口,可以创建新的类并使用静态工厂方法返回的对象作为实现接口的一部分。这样可以扩展原始类的功能并定制行为。

        静态工厂方法的参数化:静态工厂方法可以接受参数,并根据参数的不同返回不同的对象实例。通过合理设计参数,可以实现类似于子类化的效果。

虽然静态工厂方法限制了直接子类的创建,但可以通过以上方法实现类似的效果,并同时提供更好的灵活性和控制性。这种设计可以避免一些继承带来的问题,使代码更加清晰、可维护和可扩展。

缺点二:

在API文档中可能会很难发现这个方法。(无伤大雅)

静态工厂方法的命名方法没有固定的规范,但通常遵循一些常见的命名约定和惯例。以下是一些常见的命名方法:

  1. of 或 valueOf:这是一种常见的命名模式,用于表示通过给定的参数创建对象实例。例如:of()valueOf()getInstance()create()等。

  2. from:这种命名模式用于表示从给定的参数或源对象中创建对象实例。例如:from()fromX()fromXyz()等。

  3. newInstance 或 newX:这种命名模式用于表示创建新的对象实例。例如:newInstance()newX()newXyz()等。

  4. getX:这种命名模式用于表示获取对象的实例,通常与工厂方法所创建的对象具有某种关联关系。例如:getX()getXyz()getXByY()等。

  5. createX 或 buildX:这种命名模式用于表示创建对象实例,通常与构建复杂对象或对象图相关。例如:createX()buildX()createXyz()buildXyz()等。

这些只是一些常见的命名方法示例,具体的命名方式可以根据项目的需求和团队的约定进行选择。重要的是选择一个能清晰表达方法意图和用途的命名,使代码易于阅读和理解。

此外,为了提高代码的可读性和一致性,建议在命名静态工厂方法时遵循一些通用的命名约定,例如使用驼峰命名法、使用具体和清晰的命名等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值