时间:2025-03-13 18:11
人气:
作者:admin
SImple Factory定义
模式结构
包含角色
Factory:工厂角色
Product:抽象产品角色
所创建的所有对象的父类,负责描述所有实例所共有的公共接口ConcreteProduct:具体产品角色
UML


我的示例
特点
优点
XML等格式的配置文件中,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。缺点
简单工厂模式最大的问题在于工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则是相违背的。
由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
使用简单工厂模式将会增加系统中类的个数,在一定程序上增加了系统的复杂度和理解难度。
简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。
⭐️ 适用环境
工厂类负责创建的对象比较少:由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
客户端只知道传入工厂类的参数,对于如何创建对象不关心:客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数。
概括
当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。
模式实际应用
java加密技术
获取不同加密算法的密钥生成器
KeyGenerator keyGen=KeyGenerator.getInstance("DESede");精华总结
Factory Method简单工厂和抽象工厂是工厂模式的基础上,分别简单化和复杂化的产物,所以先介绍处于中间位置的工厂模式
引入
抽象父工厂,每一个子工厂专门生产一种产品,这种结构可以在不修改具体工厂类的情况下引进新的产品,如果引进新的产品,则新建一个专门生产该产品的工厂定义
模式结构
包含角色
Product:抽象产品
ConcreteProduct:具体产品
Factory:抽象工厂
ConcreteFactory:具体工厂
UML


作者示例
UML


我的示例
特点
优点
在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。(工厂模式通用的优点)
使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
缺点
⭐️ 适用环境
客户端只知道传入工厂类的参数,对于如何创建对象不关心:客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数。
模式应用
精华总结
Abstract Factory引入
工厂方法模式中,一个具体的工厂只能生产一种具体产品,如DELL工厂只生产一种笔记本电脑,然而工作和现实中一个具体的工厂,可能生产集中具体的产品,如DELL公司既生产笔记本也生产台式机
产品等级结构
DELL的笔记本电脑和MAC的笔记本电脑产品族
同一个工厂生产的,位于不同产品等级结构中的一组产品图示

抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构
模式结构
AbstractFactory:抽象工厂
ConcreteFactory:具体工厂
AbstractProduct:抽象产品
Product:具体产品
作者示例
UML


我的示例
特点
优点
缺点
在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。
开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)。
精华总结
Builder引入
定义
模式结构
角色
Builder:抽象建造者
ConcreteBuilder:具体建造者
Director:指挥者
Product:产品角色
UML


示例
UML

模式分析
特点
优点
缺点
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制
如内部变化复杂,会有很多的建造类
适用环境
软件开发中有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这一些基本部件不会变,而其组合经常变化的时候
与抽象工厂比较
精华总结
PrototypeSingleton引入
定义
补充
特点
优点
缺点
getInstance()和eat()融合在一个单例对象中)gc系统回收实现
懒汉式,线程不安全
代码示例
public class SingleObject { private static SingleObject singleObject; private SingleObject(){
}
public static SingleObject getInstance(){
if (singleObject==null){
// 这里一定要对singleObject进行赋值,而不能直接return SingleObject()
singleObject=new SingleObject();
}
return singleObject;
}
}
是否 Lazy 初始化(程序一启动就初始化单例对象,而不是等到用到单例对象的时候才初始化):是
是否多线程安全:否
实现难度:易
描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
懒汉式,线程安全
代码示例
public class SingleObject { private static SingleObject singleObject; private SingleObject(){
}
public static synchronized SingleObject getInstance(){
if (singleObject==null){
// 这里一定要对singleObject进行赋值,而不能直接return SingleObject()
singleObject=new SingleObject();
}
return singleObject;
}
}
synchronized是否 Lazy 初始化:是
是否多线程安全:是
实现难度:易
描述:这种方式具备很好的 lazy loading(延迟加载),能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点
缺点
synchronized 才能保证单例,但加锁会影响效率。getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。⭐️饿汉式,线程安全
代码示例
public class SingleObject { // 成员变量为private,防止外界访问,外界只能通过getInstance()方法获取该对象,而且必须为static,因为需要在静态方法getInstance()返回 private static SingleObject singleObject=new SingleObject(); // 构造方法为private,防止外界创建对象 private SingleObject(){
}
// 给外界提供的获取单例对象的方法
public static SingleObject getInstance(){
return singleObject;
}
public void eat(){
System.out.println("singleton is eating");
}
}
是否 Lazy 初始化((程序一启动就初始化单例对象,而不是等到用到单例对象的时候才初始化)):否
是否多线程安全:是
实现难度:易
描述:这种方式比较常用,但容易产生垃圾对象。
优点
缺点
DCL双重检测加锁(double check locking),线程安全
代码进化之路
getInstance()加装synchronized关键字,但是会导致多线程性能下降,那么我们可以把锁的范围放小一点,放到创造新对象的时候在加锁,如代码所示public class SingleObject { private static SingleObject singleObject; private SingleObject(){
}
public static SingleObject getInstance(){
if (singleObject==null) {
// 锁的范围从方法上缩小到生成单例对象上,提高性能
synchronized (SingleObject.class) {
singleObject = new SingleObject();
}
}
return singleObject;
}
}
但是这种看似完美解决的方法,仍然存在问题:虽然加了锁,仍然会有可能创建两个单例对象
getInstance()方法,他们同时判断singleObject ==null,因为锁没有加在方法上,得出的结果都是为null,所以线程A和线程B都进入了if代码块了new,就意味着返回了不止一个对象,所以是线程不安全的new新对象的时候,需要先判断此时singleObject是否已经是null,如果为不空,则不new,否则newpublic class SingleObject { private static SingleObject singleObject; private SingleObject(){
}
public static SingleObject getInstance(){
if (singleObjectnull) {
// 锁的范围从方法上缩小到生成单例对象上,提高性能
synchronized (SingleObject.class) {
// 在同步锁上加上双重判断,如果空,才new一个对象
if (singleObjectnull) {
singleObject = new SingleObject();
}
}
}
return singleObject;
}
}
但是仍然有问题

volatile关键字#TODO :#这里的作用是防止指令重排,但是实现这个防重排功能是由于volitile关键字的内存屏障功能完成的public class SingleObject { private static volatile SingleObject singleObject; private SingleObject(){
}
public static SingleObject getInstance(){
if (singleObjectnull) {
// 锁的范围从方法上缩小到生成单例对象上,提高性能
synchronized (SingleObject.class) {
// 在同步锁上加上双重判断,如果空,才new一个对象
if (singleObjectnull) {
singleObject = new SingleObject();
}
}
}
return singleObject;
}
}
强调

JDK 版本:JDK1.5 起
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:较复杂
描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
登记式/静态内部类,线程安全
代码示例
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:一般
和DCL双重检测加锁比较
相同点
不同点
和饿汉式相比较
饿汉式
Singleton一旦被加载,那么 instance 就会被实例化(没有达到 lazy loading 效果)静态内部类方式
Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance枚举,线程安全
代码示例
public enum Singleton {
//每一个实例都是全局唯一的Sigleton实例
INSTANCE;
public void sayHello(){
System.out.println("hello world");
}
}
是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易
特点
几种单例模式的经验之谈
lazy loading 效果时,才会使用第 5 种静态内部类方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。下一篇:模板方法模式