单例模式 Singleton
1、什么是单例模式
在软件设计中,单例模式是一种创建型设计模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点。
这意味着无论何时需要该类的实例,都可以获得相同的实例,而不会创建新的对象。
单例模式通常用于控制对资源的访问,例如配置文件、数据库连接,或者共享的实例。
2、为什么使用单例模式
- 资源共享:单例模式可以确保在整个应用程序中只有一个实例,从而节省系统资源,避免多次创建相同对象。
- 全局访问:通过单例模式,可以在任何需要时轻松访问该类的实例,而无需传递它作为参数。
- 懒加载:单例模式可以实现懒加载,即只有在需要时才创建实例,而不是在应用程序启动时就创建。
3、如何实现单例模式
示例场景:设计实现一个 Logger 类,用于记录应用程序中的日志信息。
非单例模式的实现
public class Logger {private List<String> logs = new ArrayList<>();public void log(String message) {logs.add(message);}
}// 在应用程序的不同部分创建Logger实例
Logger logger1 = new Logger();
Logger logger2 = new Logger();logger1.log("Message from logger1");
logger2.log("Message from logger2");System.out.println(logger1.getLogs()); // ['Message from logger1']
System.out.println(logger2.getLogs()); // ['Message from logger2']
单例模式的实现
public class Logger {private static Logger instance; // 单例实例private List<String> logs = new ArrayList<>();private Logger() {}public static Logger getInstance() {if (instance == null) {instance = new Logger();}return instance;}public void log(String message) {logs.add(message);}public List<String> getLogs() {return logs;}
}// 在应用程序的不同部分获取Logger实例
Logger logger1 = Logger.getInstance();
Logger logger2 = Logger.getInstance();logger1.log("Message from logger1");
logger2.log("Message from logger2");System.out.println(logger1.getLogs()); // ['Message from logger1', 'Message from logger2']
System.out.println(logger2.getLogs()); // ['Message from logger1', 'Message from logger2']
代码对比说明
- 资源共享:使用单例模式后,Logger 实例在整个应用程序中是唯一的,确保了日志的全局共享,避免了每次创建实例都生成新对象的问题。
- 全局访问:单例模式允许在应用程序的任何地方访问相同的 Logger 实例,而不必传递它。这提高了代码的简洁性和可维护性。
- 懒加载:单例模式的实现中,通过 getInstance 方法进行了懒加载,只有在实例不存在时才创建。这确保了在应用程序启动时不会预先创建实例,而是在需要时才进行创建,提高了效率。
4、是否存在缺陷和不足
- 全局状态:单例模式引入了全局状态,可能会导致代码的耦合性增加。由于单例在整个应用程序中都是可见的,其他部分可能难以预测它的状态,从而使代码难以理解和维护。
- 并发控制:在多线程环境中,需要考虑并发控制的问题。当多个线程同时尝试第一次获取单例实例时,可能会导致创建多个实例。为了解决这个问题,需要引入同步机制,但这会带来性能的开销。
- 隐藏依赖关系:使用单例模式可能导致隐藏了类之间的依赖关系,因为它可以在任何地方访问单例实例。这使得代码的结构变得不够清晰,降低了模块化的优势。
- 单一职责原则破坏:单例模式通常负责两个职责控制实例化过程和提供全局访问点。这可能违反了单一职责原则,使得代码的职责不够清晰。
- 测试困难:由于单例模式创建实例的逻辑通常包含在类内部,很难进行单元测试。测试过程可能会依赖于全局状态,导致测试的不确定性。
5、如何缓解缺陷和不足
- 使用依赖注入:考虑使用依赖注入来解决单例模式引入的全局状态问题。通过将依赖项传递给需要它们的类,可以更好地控制类之间的关系。
- 懒加载与双重检查锁定:在多线程环境中,可以使用懒加载和双重检查锁定等技术来确保实例的唯一性,并提高性能。
- 考虑其他创建型模式:根据具体需求,考虑其他创建型设计模式,如工厂方法模式或建造者模式,以避免单例模式的一些限制。
- 避免过度使用:单例模式并不适用于所有情况,避免过度使用。在确保有明确需求时使用,而不是为了方便而滥用。
虽然单例模式存在一些缺点,但在许多情况下,它仍然是一种有效的设计选择。在使用单例模式时,需要仔细权衡其优点和缺点,并根据具体情况选择合适的设计方式。
原型模式 Prototype
1、什么是原型模式
原型模式是一种创建型设计模式,核心思想是通过复制现有对象来创建新对象,而不是通过实例化类,这种创建新对象的方式更加高效,尤其当新对象的创建成本较高时,使用原型模式可以提高性能。
2、为什么使用原型模式
- 性能提升:当新对象的创建成本较高时,通过复制现有对象可以避免重复执行初始化和配置的开销,提高对象创建的效率。
- 灵活性:原型模式可以在运行时动态配置对象,通过克隆可以得到一个与原始对象相似的新对象,而无需修改结构。
- 简化对象创建:原型模式隐藏了对象的创建细节,使得对象的创建更加简单和统一。
3、如何实现原型模式
非原型模式的实现
public class Book {private String title;private String author;public Book(String title, String author) {this.title = title;this.author = author;}// Getters and setters...public Book clone() {return new Book(this.title, this.author);}
}
原型模式的实现
public class Book implements Cloneable {private String title;private String author;public Book(String title, String author) {this.title = title;this.author = author;}// Getters and setters...@Overridepublic Book clone() {try {return (Book) super.clone();} catch (CloneNotSupportedException e) {return null;}}
}
4、是否存在缺陷和不足
- 深克隆问题:默认的 clone 方法是浅克隆,即只克隆对象本身,而不克隆其引用类型的成员变量。如果对象包含引用类型的成员变量,可能需要进行深克隆,以避免共享引用对象。
- 构造函数不执行:在使用原型模式时,对象的构造函数不会执行,这可能导致某些初始化逻辑未被执行。
5、如何缓解缺陷和不足
- 深克隆的实现:如果需要深克隆,可以通过重写 clone 方法,手动克隆引用类型的成员变量,确保新对象独立于原始对象。
- 初始化方法:在原型对象中提供一个初始化方法,可以在克隆对象后手动调用该方法,以确保必要的初始化逻辑得以执行。
通过以上缓解措施,可以更好地应对深克隆和构造函数不执行等问题,使得原型模式更加灵活和实用,在实际应用中,根据具体需求和场景选择是否使用原型模式。