设计模式
设计模式简介
看懂UML类图和时序图
UML统一建模语言
UML类图及类图之间的关系
类关系记忆技巧
如何正确使用设计模式
优秀设计的特征
面向对象设计原则
创建型设计模式
工厂模式
抽象工厂模式
简单工厂模式
静态工厂模式(Static Factory)
单例模式
建造者模式
原型模式
结构型设计模式
适配器模式
桥接模式
组合模式
装饰器模式
外观模式
享元模式
代理模式
过滤器模式
注册模式(Registry)
行为型设计模式
责任链模式
命令模式
解释器模式
中介者模式
备忘录模式
迭代器模式
观察者模式
状态模式
策略模式
模板模式
访问者模式
规格模式(Specification)
J2EE 设计模式
MVC 模式
业务代表模式
组合实体模式
数据访问对象模式(DAO模式)
前端控制器模式
拦截过滤器模式
空对象模式
服务定位器模式
传输对象模式
数据映射模式(Data Mapper)
依赖注入模式(Dependency Injection)
流接口模式(Fluent Interface)
其他模式
对象池模式(Pool)
委托模式
资源库模式(Repository)
实体属性值模式(EAV 模式)
反面模式
归纳设计模式
本文档使用 MrDoc 发布
-
+
首页
工厂模式
> 工厂方法模式是一种创建型设计模式, 其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。 ![](/media/202203/2022-03-05_190116_203082.png) 工厂模式(Factory Pattern,虚拟构造函数、Virtual Constructor、Factory Method)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 在工厂模式中,我们在创建对象时**不会对客户端暴露创建逻辑**,并且是通过使用一个共同的接口来指向新创建的对象。 ## 目的 对比简单工厂模式的优点是,您可以将其子类用不同的方法来创建一个对象。 举一个简单的例子,这个抽象类可能只是一个接口。 这种模式是「真正」的设计模式, 因为他实现了 S.O.L.I.D 原则中「D」的 「依赖倒置」。 这意味着工厂方法模式取决于抽象类,而不是具体的类。 这是与简单工厂模式和静态工厂模式相比的优势。 ## 介绍 - 意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。 - 主要解决:主要解决接口选择的问题。 - 何时使用:我们明确地计划不同条件下创建不同实例时。 - 如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。 - 关键代码:创建过程在其子类执行。 - 应用实例: 1. 您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。 2. Hibernate 换数据库只需换方言和驱动就可以。 - 优点: 1. 一个调用者想创建一个对象,只要知道其名称就可以了。 2. 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3. 屏蔽产品的具体实现,调用者只关心产品的接口。 - 缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。 - 使用场景: 1. 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 2. 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 3. 设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。 - 注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。 ## 结构 ![工厂方法模式结构](/media/202202/2022-02-27_125828_379306.png) - 产品 (Product) 将会对接口进行声明。 对于所有由创建者及其子类构建的对象, 这些接口都是通用的。 - 具体创建者 (Concrete Creators) 将会重写基础工厂方法, 使其返回不同类型的产品。 注意, **并不一定每次调用工厂方法都会创建新的实例。 工厂方法也可以返回缓存、 对象池或其他来源的已有对象。** - 创建者 (Creator) 类声明返回产品对象的工厂方法。 该方法的返回对象类型必须与产品接口相匹配。 你可以将工厂方法声明为抽象方法, 强制要求每个子类以不同方式实现该方法。 或者, 你也可以在基础工厂方法中返回默认产品类型。 注意, 尽管它的名字是创建者, 但它最主要的职责并不是创建产品。 一般来说, 创建者类包含一些与产品相关的核心业务逻辑。 工厂方法将这些逻辑处理从具体产品类中分离出来。 打个比方, 大型软件开发公司拥有程序员培训部门。 但是, 这些公司的主要工作还是编写代码, 而非生产程序员。 ## UML ![工厂方法](/media/202203/2022-03-20_212452_596756.png) ## 伪代码 以下示例演示了如何使用工厂方法开发跨平台 UI (用户界面) 组件, 并同时避免客户代码与具体 UI 类之间的耦合。 ![跨平台对话框示例](/media/202202/2022-02-27_130729_791386.png) 基础对话框类使用不同的 UI 组件渲染窗口。 在不同的操作系统下, 这些组件外观或许略有不同, 但其功能保持一致。 Windows 系统中的按钮在 Linux 系统中仍然是按钮。 如果使用工厂方法, 就不需要为每种操作系统重写对话框逻辑。 如果我们声明了一个在基本对话框类中生成按钮的工厂方法, 那么我们就可以创建一个对话框子类, 并使其通过工厂方法返回 Windows 样式按钮。 子类将继承对话框基础类的大部分代码, 同时在屏幕上根据 Windows 样式渲染按钮。 如需该模式正常工作, 基础对话框类必须使用抽象按钮 (例如基类或接口), 以便将其扩展为具体按钮。 这样一来, 无论对话框中使用何种类型的按钮, 其代码都可以正常工作。 你可以使用此方法开发其他 UI 组件。 不过, 每向对话框中添加一个新的工厂方法, 你就离抽象工厂模式更近一步。 我们将在稍后谈到这个模式。 ```java // 创建者类声明的工厂方法必须返回一个产品类的对象。创建者的子类通常会提供 // 该方法的实现。 class Dialog is // 创建者还可提供一些工厂方法的默认实现。 abstract method createButton():Button // 请注意,创建者的主要职责并非是创建产品。其中通常会包含一些核心业务 // 逻辑,这些逻辑依赖于由工厂方法返回的产品对象。子类可通过重写工厂方 // 法并使其返回不同类型的产品来间接修改业务逻辑。 method render() is // 调用工厂方法创建一个产品对象。 Button okButton = createButton() // 现在使用产品。 okButton.onClick(closeDialog) okButton.render() // 具体创建者将重写工厂方法以改变其所返回的产品类型。 class WindowsDialog extends Dialog is method createButton():Button is return new WindowsButton() class WebDialog extends Dialog is method createButton():Button is return new HTMLButton() // 产品接口中将声明所有具体产品都必须实现的操作。 interface Button is method render() method onClick(f) // 具体产品需提供产品接口的各种实现。 class WindowsButton implements Button is method render(a, b) is // 根据 Windows 样式渲染按钮。 method onClick(f) is // 绑定本地操作系统点击事件。 class HTMLButton implements Button is method render(a, b) is // 返回一个按钮的 HTML 表述。 method onClick(f) is // 绑定网络浏览器的点击事件。 class Application is field dialog: Dialog // 程序根据当前配置或环境设定选择创建者的类型。 method initialize() is config = readApplicationConfigFile() if (config.OS == "Windows") then dialog = new WindowsDialog() else if (config.OS == "Web") then dialog = new WebDialog() else throw new Exception("错误!未知的操作系统。") // 当前客户端代码会与具体创建者的实例进行交互,但是必须通过其基本接口 // 进行。只要客户端通过基本接口与创建者进行交互,你就可将任何创建者子 // 类传递给客户端。 method main() is this.initialize() dialog.render() ``` ## 应用场景 - 当你在编写代码的过程中, 如果**无法预知对象确切类别及其依赖关系**时, 可使用工厂方法。 工厂方法将创建产品的代码与实际使用产品的代码分离, 从而能在不影响其他代码的情况下扩展产品创建部分代码。 例如, 如果需要向应用中添加一种新产品, 你只需要开发新的创建者子类, 然后重写其工厂方法即可。 - 如果你希望用户能**扩展你软件库或框架的内部组件**, 可使用工厂方法。 继承可能是扩展软件库或框架默认行为的最简单方法。 但是当你使用子类替代标准组件时, 框架如何辨识出该子类? 解决方案是将各框架中构造组件的代码集中到单个工厂方法中, 并在继承该组件之外允许任何人对该方法进行重写。 让我们看看具体是如何实现的。 假设你使用开源 UI 框架编写自己的应用。 你希望在应用中使用圆形按钮, 但是原框架仅支持矩形按钮。 你可以使用 圆形按钮RoundButton子类来继承标准的 按钮Button类。 但是, 你需要告诉 UI框架UIFramework类使用新的子类按钮代替默认按钮。 为了实现这个功能, 你可以根据基础框架类开发子类 圆形按钮UI UIWithRoundButtons , 并且重写其 createButton创建按钮方法。 基类中的该方法返回按钮对象, 而你开发的子类返回圆形按钮对象。 现在, 你就可以使用 圆形按钮UI类代替 UI框架类。 就是这么简单! - 如果你希望复用现有对象来节省系统资源, 而不是每次都重新创建对象, 可使用工厂方法。 在处理大型资源密集型对象 (比如数据库连接、 文件系统和网络资源) 时, 你会经常碰到这种资源需求。 让我们思考复用现有对象的方法: 1. 首先, 你需要创建存储空间来存放所有已经创建的对象。 2. 当他人请求一个对象时, 程序将在对象池中搜索可用对象。 3. …然后将其返回给客户端代码。 4. 如果没有可用对象, 程序则创建一个新对象 (并将其添加到对象池中)。 这些代码可不少! 而且它们必须位于同一处, 这样才能确保重复代码不会污染程序。 可能最显而易见, 也是最方便的方式, 就是将这些代码放置在我们试图重用的对象类的构造函数中。 但是从定义上来讲, 构造函数始终返回的是新对象, 其无法返回现有实例。 因此, 你需要有一个既能够创建新对象, 又可以重用现有对象的普通方法。 这听上去和工厂方法非常相像。 ## 优点 - 你可以避免创建者和具体产品之间的紧密耦合。 - 单一职责原则。 你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。 - 开闭原则。 无需更改现有客户端代码, 你就可以在程序中引入新的产品类型。 ## 缺点 - 应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂。 最好的情况是将该模式引入创建者类的现有层次结构中。 ## 与其他模式的关系 - 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), **随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)**。 - 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。 - 你可以同时使用工厂方法和迭代器模式来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。 - 原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。 - 工厂方法是模板方法模式的一种特殊形式。 同时, 工厂方法可以作为一个大型模板方法中的一个步骤。 ## 示例代码 ### TS ```ts /** * The Creator class declares the factory method that is supposed to return an * object of a Product class. The Creator's subclasses usually provide the * implementation of this method. */ abstract class Creator { /** * Note that the Creator may also provide some default implementation of the * factory method. */ public abstract factoryMethod(): Product; /** * Also note that, despite its name, the Creator's primary responsibility is * not creating products. Usually, it contains some core business logic that * relies on Product objects, returned by the factory method. Subclasses can * indirectly change that business logic by overriding the factory method * and returning a different type of product from it. */ public someOperation(): string { // Call the factory method to create a Product object. const product = this.factoryMethod(); // Now, use the product. return `Creator: The same creator's code has just worked with ${product.operation()}`; } } /** * Concrete Creators override the factory method in order to change the * resulting product's type. */ class ConcreteCreator1 extends Creator { /** * Note that the signature of the method still uses the abstract product * type, even though the concrete product is actually returned from the * method. This way the Creator can stay independent of concrete product * classes. */ public factoryMethod(): Product { return new ConcreteProduct1(); } } class ConcreteCreator2 extends Creator { public factoryMethod(): Product { return new ConcreteProduct2(); } } /** * The Product interface declares the operations that all concrete products must * implement. */ interface Product { operation(): string; } /** * Concrete Products provide various implementations of the Product interface. */ class ConcreteProduct1 implements Product { public operation(): string { return '{Result of the ConcreteProduct1}'; } } class ConcreteProduct2 implements Product { public operation(): string { return '{Result of the ConcreteProduct2}'; } } /** * The client code works with an instance of a concrete creator, albeit through * its base interface. As long as the client keeps working with the creator via * the base interface, you can pass it any creator's subclass. */ function clientCode(creator: Creator) { // ... console.log('Client: I\'m not aware of the creator\'s class, but it still works.'); console.log(creator.someOperation()); // ... } /** * The Application picks a creator's type depending on the configuration or * environment. */ console.log('App: Launched with the ConcreteCreator1.'); clientCode(new ConcreteCreator1()); console.log(''); console.log('App: Launched with the ConcreteCreator2.'); clientCode(new ConcreteCreator2()); ``` 输出: ```txt App: Launched with the ConcreteCreator1. Client: I'm not aware of the creator's class, but it still works. Creator: The same creator's code has just worked with {Result of the ConcreteProduct1} App: Launched with the ConcreteCreator2. Client: I'm not aware of the creator's class, but it still works. Creator: The same creator's code has just worked with {Result of the ConcreteProduct2} ```
追风者
2022年3月20日 22:27
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码