工厂模式
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一,它提供了一种创建对象的方式,使得创建对象的过程与使用对象的过程分离。
工厂模式提供了一种创建对象的方式,而无需指定要创建的具体类。
通过使用工厂模式,可以将对象的创建逻辑封装在一个工厂类中,而不是在客户端代码中直接实例化对象,这样可以提高代码的可维护性和可扩展性。
工厂模式的类型
简单工厂模式(Simple Factory Pattern):
- 简单工厂模式不是一个正式的设计模式,但它是工厂模式的基础。它使用一个单独的工厂类来创建不同的对象,根据传入的参数决定创建哪种类型的对象。
工厂方法模式(Factory Method Pattern):
- 工厂方法模式定义了一个创建对象的接口,但由子类决定实例化哪个类。工厂方法将对象的创建延迟到子类。
抽象工厂模式(Abstract Factory Pattern):
- 抽象工厂模式提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。
概要
意图
定义一个创建对象的接口,让其子类决定实例化哪一个具体的类。工厂模式使对象的创建过程延迟到子类。
主要解决
接口选择的问题。
何时使用
当我们需要在不同条件下创建不同实例时。
如何解决
通过让子类实现工厂接口,返回一个抽象的产品。
关键代码
对象的创建过程在子类中实现。
应用实例
- 汽车制造:你需要一辆汽车,只需从工厂提货,而不需要关心汽车的制造过程及其内部实现。
- Hibernate:更换数据库时,只需更改方言(Dialect)和数据库驱动(Driver),即可实现对不同数据库的切换。
优点
- 调用者只需要知道对象的名称即可创建对象。
- 扩展性高,如果需要增加新产品,只需扩展一个工厂类即可。
- 屏蔽了产品的具体实现,调用者只关心产品的接口。
缺点
每次增加一个产品时,都需要增加一个具体类和对应的工厂,使系统中类的数量成倍增加,增加了系统的复杂度和具体类的依赖。
使用场景
- 日志记录:日志可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志的位置。
- 数据库访问:当用户不知道最终系统使用哪种数据库,或者数据库可能变化时。
- 连接服务器的框架设计:需要支持 "POP3"、"IMAP"、"HTTP" 三种协议,可以将这三种协议作为产品类,共同实现一个接口。
注意事项
工厂模式适用于生成复杂对象的场景。如果对象较为简单,通过 new 即可完成创建,则不必使用工厂模式。使用工厂模式会引入一个工厂类,增加系统复杂度。
结构
工厂模式包含以下几个主要角色:
- 抽象产品(Abstract Product):定义了产品的共同接口或抽象类。它可以是具体产品类的父类或接口,规定了产品对象的共同方法。
- 具体产品(Concrete Product):实现了抽象产品接口,定义了具体产品的特定行为和属性。
- 抽象工厂(Abstract Factory):声明了创建产品的抽象方法,可以是接口或抽象类。它可以有多个方法用于创建不同类型的产品。
- 具体工厂(Concrete Factory):实现了抽象工厂接口,负责实际创建具体产品的对象。
实现
我们将创建一个 Shape 接口和实现 Shape 接口的实体类。下一步是定义工厂类 ShapeFactory。
FactoryPatternDemo 类使用 ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(CIRCLE / RECTANGLE / SQUARE),以便获取它所需对象的类型。
步骤 1
创建一个接口:
Shape.java
步骤 2
创建实现接口的实体类。
Rectangle.java
Square.java
Circle.java
步骤 3
创建一个工厂,生成基于给定信息的实体类的对象。
ShapeFactory.java
步骤 4
使用该工厂,通过传递类型信息来获取实体类的对象。
FactoryPatternDemo.java
步骤 5
执行程序,输出结果:
Inside Circle::draw() method. Inside Rectangle::draw() method. Inside Square::draw() method.
冷锋SJ记忆
son***e_xuan@126.com
使用反射机制可以解决每次增加一个产品时,都需要增加一个对象实现工厂的缺点
使用的使用采用强制转换
这样就只需要一个对象实现工厂
冷锋SJ记忆
son***e_xuan@126.com
tongyu
tyy***28@sohu.com
省略类型强制转换,支持多态
tongyu
tyy***28@sohu.com
dugw
630***862@qq.com
在上面的基础上进一步扩展,针对多个接口实现一个公共的工厂类:
dugw
630***862@qq.com
佐手
476***586@qq.com
在jdk9中直接使用泛型的newInstance方法已经过时。重写的getClass()方法如下:
佐手
476***586@qq.com
1310337382@qq.com
131***7382@qq.com
创建的这个工厂,这个创建函数,没能看出有什么特别的,还特别繁琐,如果IShape类太多,那么if else语句也将增加太多,不好维护,与其这样写,还不如改成如下:
这样写在创建时也不用知道具体的类别,易于管理维护。
1310337382@qq.com
131***7382@qq.com
kusirp21
hoo***@hotmail.com
楼上几位真心会开玩笑!
其实使用反射是一种不错的办法,但反射也是从类名反射而不能从类反射!
先看一下工厂模式是用来干什么的——属于创建模式,解决子类创建问题的。换句话来说,调用者并不知道运行时真正的类名,只知道从“Circle"可以创建出一个shape接口的类,至于类的名称是否叫'Circle",调用者并不知情。所以真正的对工厂进行扩展的方式(防止程序员调用出错)可以考虑使用一个枚举类(防止传入参数时,把circle拼写错误)。
如果调用者参肯定类型是Circle的话,那么其工厂没有存在的意义了!
比如 IShape shape = new Circle();这样不是更好?也就是说调用者有了Circle这个知识是可以直接调用的,根据DP(迪米特法则)其实调用者并不知道有一个Circle类的存在,他只需要知道这个IShape接口可以计算圆面积,而不需要知道;圆这个类到底是什么类名——他只知道给定一个”circle"字符串的参数,IShape接口可以自动计算圆的面积就可以了!
其实在.net类库中存在这个模式的的一个典型的。但他引入的另一个概念“可插入编程协议”。
那个就是WebRequest req = WebRequest.Create("http://ccc......");可以自动创建一个HttpWebRequest的对象,当然,如果你给定的是一个ftp地址,他会自动创建一个FtpWebRequest对象。工厂模式中着重介绍的是这种通过某个特定的参数,让你一个接口去干对应不同的事而已!而不是调用者知道了类!
比如如果圆的那个类名叫"CircleShape“呢?不管是反射还是泛型都干扰了你们具体类的生成!其实这个要说明的问题就是这个,调用者(clinet)只知道IShape的存在,在创建时给IShape一个参数"Circle",它可以计算圆的面积之类的工作,但是为什么会执行这些工作,根据迪米特法则,client是不用知道的。
我想问一下那些写笔记的哥们,如果你们知道了泛型,那么为什么不直接使用呢?干吗还需要经过工厂这个类呢?不觉得多余了吗?
如果,我只是说如果,如果所有从IShape继承的类都是Internal类型的呢?而client肯定不会与IShape一个空间!这时,你会了现你根本无法拿到这个类名!
Create时使用注册机制是一种简单的办法,比如使用一个枚举类,把功能总结到一处。而反射也是一种最简单的办法,调用者输入的名称恰是类名称或某种规则时使用,比如调用者输入的是Circle,而类恰是CircleShape,那么可以通过输入+”Shape"字符串形成新的类名,然后从字符串将运行类反射出来!
工厂的创建行为,就这些作用,还被你们用反射或泛型转嫁给了调用者(clinet),那么,这种情况下,要工厂类何用?!
kusirp21
hoo***@hotmail.com
罗
107***8153@qq.com
参考地址
使用枚举优化
罗
107***8153@qq.com
参考地址
往生在世
493***942@qq.com
使用类名增加耦合性,那就需要使用常量或者枚举类解耦合。
往生在世
493***942@qq.com
牟春平
muc***ping@gmail.com
@kusirp21 讲得很好,使用枚举类优化。
增加枚举类
修改工厂类的工厂方法,个人建议工厂方法应该是静态方法或者采用单例模式:
上面用到的类和接口与课程中的一样。
最后是使用示例:
牟春平
muc***ping@gmail.com
msyms
934***531@qq.com
参考地址
一、一句话概括工厂模式
二、生活中的工厂模式
msyms
934***531@qq.com
参考地址
李金晨
231***8763@qq.com
工厂模式实例:
李金晨
231***8763@qq.com
Robbie
g00***00@126.com
根据上面 @kusirp21 枚举的思路实现的工厂模式:
Robbie
g00***00@126.com
阿燃
357***211@qq.com
接口定义实现类的规范,通过反射工厂类进行生产,完整的一个过程就是。
接口类:
实现类:
工厂类:
给定条件产出:
阿燃
357***211@qq.com
杨轻帆
294***4412@qq.com
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
此练习中我写的工厂是通过一个方法(买狗)来调用相应对象创建语句去创建不同的(狗)对象;用子对象中的重写方法验证父对象被赋值后调用的方法是属于谁的。
具体的解释在代码中已经标明:
杨轻帆
294***4412@qq.com
BertYY
zge***an@hotmail.com
python 代码实现示例:
BertYY
zge***an@hotmail.com
Siskin.xu
sis***@sohu.com
Python 方式:
Siskin.xu
sis***@sohu.com
清露凝尘
575***823@qq.com
看了大家的笔记,差点误入歧途,感谢 @kusirp21 的当头棒喝。取长补短,将工厂方法中的判断,提取到枚举中, 工厂方法使用反射创建对象。
以下是代码:
清露凝尘
575***823@qq.com
不在窝里
yzq***p@163.com
清露凝尘的做法非常好,我借鉴之后,减少了一下代码量,个人觉得效果达到,编码也少,这样挺好。
以下修改了异常抛出结果,简单做了下拼接。
测试类:
不在窝里
yzq***p@163.com
蘑菇力
168***7348@qq.com
反射很方便。
以上这种方法,便于维护,创建对象时,只需要该类实现Service接口,并在配置文件中添加该类的全限定名即可。类似于插件开发。
蘑菇力
168***7348@qq.com
fanjinzhao
165***6686@qq.com
在逻辑代码中应避免出现"魔法值"。
结合之前几位的逻辑,我将反射和枚举类结合一起并完善,写出下面代码:
fanjinzhao
165***6686@qq.com
mm
mfx***0@live.cn
C++ 的实现方法:
mm
mfx***0@live.cn
squid233
513***220@qq.com
改良版 C++ 方式:
squid233
513***220@qq.com
MJ123
MJ1***6019027@126.com
C# 入门版:
MJ123
MJ1***6019027@126.com
RUNOOB
429***967@qq.com
工厂模式可以帮助我们封装对象的创建逻辑,提供统一的接口来创建不同类型的对象,从而使得客户端代码更加灵活和可维护。同时,工厂模式也支持扩展,可以方便地引入新的产品类型,而无需修改现有的客户端代码。
工厂模式有多种变体,其中最常见的是简单工厂模式、工厂方法模式和抽象工厂模式。
下面是一个简单工厂模式的示例:
在上面的示例中,我们定义了抽象产品接口 Product 和具体产品类 ConcreteProductA 和 ConcreteProductB。通过简单工厂类 SimpleFactory 的 createProduct 方法,根据传入的参数类型来创建不同的具体产品实例。
RUNOOB
429***967@qq.com