工厂模式解析

也被叫做虚拟构造器

目的

工厂模式是一个创造型设计模式,在父类中提供一个创建对象的接口,但是也允许子类改变创建对象的类型。

问题

 想象你在实现一个物流管理应用。第一个版本只能处理卡车运输,所以你的代码主题都在Truck类中。

过了一段时间,你的app火了。每天你都收到几十条海运公司希望向你app里集成海洋运输业务的请求。

         添加一个新类到程序中不是那么简单,如果剩余的代码已经集成到现存的类中。

好消息,是吧?但是代码呢?现在,你大多数的代码是和Truck类是耦合的。添加一个Ships类

到app会需要对整个代码做出改动。另外,你之后如果决定添加另一种运输类型到app中,你又需要把上述改动重新做一遍。

结果是,你会获得一个很糟糕的代码,充满了各种对运输对象类型的判断,以改变app对应的行为。

解决方法

工厂方法模式建议你将直接的对象构造调用(使用new)替换为一个特殊的工厂方法。不要担心:这些对象还是通过new操作符创建的,但是会在工厂方法中被调用。工厂方法返回的对象通常作为产品。

         子类可以改变通过工厂方法返回的对象的类

首先,这个改动看起来四五没有意义:我们只是把构造函数从一个地方移动到另一个地方。但是,考虑到你可以在一个子类中重写这个工厂方法并且对这个方法创建的产品的类做出改动。 

但是这里有一个小限制 :子类只有当产品有一个共同的基类或者接口的时候才会返回不同类型的产品。同样,这个基类中的工厂方法需要有它的由接口定义的返回类型。

所有产品都需要遵循同一个接口

例如,Truck和Ship类都需要实现Transport接口,这个接口声明了一个deliver方法。每个类都各自实现了这个方法:Trucks通过卡车陆运,Ships海运。RoadLogistics的工厂方法类返回truck对象,SeaLogistics返回ships对象。

只要所有的类都实现了一个共同的接口,你就可以将这些对象传递给客户端代码而无需破坏它

使用了工厂方法的代码(通常叫做客户端代码)看不见通过不同子类返回的实际的产品之间的差异。这个客户端将所有的产品看作Transport这个抽象。这个客户端知道所有对象都应该有deliver方法,但是具体怎么实现的客户端并不需要知道(它只负责调用,不需要知道细节)。 

结构

 

 

1.这个Product 声明了接口,是所有可以被这个构造器和它的子类创建的对象共有的。

2.具体的的Product对象是这个产品接口的不同实现。

3.这个Creator类声明了返回新产品对象的方法。这个方法的返回对象的类型需要和产品接口匹配。

你可以将工厂方法声明为abstract,来请示所有的子类去实现他们自己版本的工厂方法。同样的,基类工厂方法也可以返回一些默认的产品类型。

注意,尽管他叫工厂模式,产品创建并不是它最首要的职责。通常,创建类已经有一些和产品类相关的核心业务代码。这个工厂方法帮助将这些逻辑和具体的产品类解耦。这里有一个类比:一个大型软件开发公司可以有一个程序员的培训部门。但是,公司的首要职责还是写代码,而不是培训程序员。

4.具体的创造者类重写了基类的工厂方法,所以它返回了一个不同类型的产品。

注意到这个工厂方法不是必须要创建一个新实例。他也可以从缓存池中返回一个已经存在的对象。

#伪代码

这个例子描述了工厂方法如何使用在创建跨平台的UI元素,而不用将客户端代码耦合到具体的UI类中。

跨平台对话框的例子 

Dialog基类使用了不同的UI元素去渲染他的窗口。在不同的操作系统下,这些元素看起来稍微有一些差异,但是他们仍应该表现出一致性。Windows下的一个按钮,到Linux下仍是一个按钮。

当这个工厂方法发挥作用的时候,你不需要为每个操作系统重写Dialog类的逻辑。如果我们在Dialog的基类中声明了一个生产各种按钮的工厂方法,我们稍后可以通过工厂方法创建一个返回了Window风格的子类。这个子类从基类继承了大部分代码,但是,由于工厂方法,可以在屏幕上渲染看起来像Windows风格的按钮。

为了使这个模式运行,Dialog基类必须在虚拟按钮的基础上操作(Dialog类只看到虚拟按钮):一个所有具体按钮都遵循的一个基类或者接口。这样Dialog类中的代码面对任何类型的按钮都能够工作。

同样,你也可以将这个方法应用到其他的UI元素。但是,你每向Dialog中添加一个工厂方法,你就向虚拟工厂方法迈进了一步。不用担心,我们之后在讨论这个模式。

// The creator  class declares the factory method that must 
// return an object of a product class.The creator's subclasses 
// usually provide the implenmentation of this method.
// 这个创造者类声明了必须返回产品类型的对象的工厂方法。这个创造者的子类通常提供了这个方法的实现
class Dialog is
    // The creator may also provide some defualt
    // 这个构造需要实现一些工厂方法默认的实现
    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 WidnwosButton()
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 reder(a,b) is
        //渲染一个windows风格的按钮
    method onClick() is
        //绑定一个原生OS点击事件
class HTMLButton implements Button is
    method render(a,b) is
        //返回一个HTML风格的按钮
    method onClick(f) is
        //绑定一个浏览器点击事件
class Applicatin is
    filed dialog:Dialog
    
    //这个应用根据据当前的配置或者环境设置选用了一个创造者模式
    method initialize() is
        config = readApplicationConfigFile()

        if(config.OS == "Windows")then
            dialog=new WindwosDialog()
        else if(config.OS=="Web") then
            dialog=new WebDialog()
        else
            throw new Exception("Error! UnKnown operation system.")
    //这个客户端代码通过一个具体的构造者的实例工作,尽管是通过基类接口。
    //只要这个客户端保持通过基类接口工作,你就可以传给它任何创建这的子类。
    method main() is
        this.initialize()
        dialog.render()

适用性

当你不能提前知道具体的类型和你代码将要使用的对象的依赖时,使用工厂模式。

工厂模式将产品构建从使用这些产品的代码中分离。尽管通过将产品的代码对立于其他代码来扩展更加容易(比如在客户端类实现多个构造函数)

例如,为了添加一个新的产品类型到app中,你只需要创建一个新的构造者类,并重写工厂方法

 当你希望通过一种扩展内部组件的方式为你的用户提供你的类库或者框架时,使用工厂模式。

继承也许是最简单的扩展类库或者框架默认行为的方式。但是框架该如何识别应该使用你的框架而不是标准组件。

这个解决办法时减少跨框架创建组件的代码,放到一个单独的工厂方法中,并且让任何人想自己扩展组件的时候重写这个方法。

让我们看看这个该如何工作。想象你使用一个开源的UI框架来开发app。你的app应该有圆按钮,但是框架支支持方形的按钮。你使用一个漂亮的RoundButton类扩展了标准的Button类。但是现在你需要去告诉UIFrameWork类去使用新的Button子类,而不是默认的组件。

为了实现这个目标,你创建了一个继承了框架子类的UIWithRoundButtons子类,并重写了它的createButton方法。因为这个方法在基类中返回Button对象,你自己创建的子类对象返回了RoundButton对象。现在使用UIWithRoundButtons类而不是UIFrameWork,你就可以实现上述需求。

当你希望通过重新使用已存在的对象而不是每次重新创建一个的时候使用工厂模式。

你可能经常碰到这个需求,当你处理大型的,资源敏感的对象,例如数据库连接,文件系统,和网络资源。

让我们来看看去重利用一个已经存在的对象需要做哪些事情:

1.首先,你需要创建一些仓库来记录所有已经常见的对象。

2.当需要一个对象的时候,这个程序需要从对象池中寻找一个空闲对象。

3.将这个对象返回给客户端代码。

4.如果没有空闲对象,这个程序需要创建一个新的对象(并把它加到对象池中)

这其实时很多的代码量!并且这些代码需要放到一个地方,防止重复的代码污染你的项目。

带盖最明显和边界的地方这个代码可以被放置的地方就是你试图重使用的对象的类被创建的地方。但是,一个构造器根据定义必须返回一个新的实例,它不能返回已经存在的实例。

所以,你需要拥有一个通用,能够创建新对象,并且重利用已经存在的对象的方法。这个恰好很适合工厂方法。

如何实现

1.让所有产品都遵循同一个接口。这个接口需要声明对于每个产品都能说的通的方法。

2.添加一个空的方法在构造器类的内部。这个方法返回的类型必须匹配这个通用的产品接口。

3.在创造者代码中找到所有产品构造器的引用。。一个一个,将他们替换为工厂方法,并将产品构造代码放到工厂方法中。

你可能需要添加一个临时的参数到工厂方法中去控制返回产品的类型。

此时,工厂方法的代码看起来会很丑陋。它需要一个很庞大的Switch来挑选该创建哪个产品类去实例化。但是不用担心,我们会很快修复这个。

4.现在,为在工厂方法中列出的各种类型的产品创建一系列构造者子类。重写子类中的工厂方法,从基类方法中移除差不多大小的构造代码。

5。如果有很多中产品类型并且为所有类型创建子类并不现实,你可以在子类中重利用基类的控制参数。

例如,假设你有下列类层次:基础的Mail类,有一系列子类:AirMail和GroundMail;Transport的类是Plane,Truck和Train。当AirMail只使用Plane对象,GroundMail只使用Truck和Train对象。你可以创建一个新的子类(比如TrainMail)去处理这两种情况,但是还有另一个方法。客户端代码可以传递一个参数到GroundMail的工厂类中去控制它希望接受的产品类。

6.如果,在所有的剔除操作后,这个基础的工厂类变空了,你就可以让他变成abstract。如果剩了一些代码,你就可以让他变成方法的默认行为。

利与弊

利                                                                        弊 

你可以避免创造者和具体产品的紧耦合                        代码会变得更加复杂,因为你需要引入更多

单一职责原则。你可以移动产品创造代码到程序的      子类去实现这个模式。最好的情景时当你引

一个地方,让代码更好维护                                           入这个模式到一个现存的创造者结构层次中

开闭原则。你可以引入新的产品类型到程序中,而

无需改动现有的客户端代码

和其他模式的关系 

 

  • 很多模式通过工厂模式开始(通过子类获得更少的复杂性和更强的通用性)并且进化出了虚拟工厂模式,原型模式和建造者模式(更灵活,但是更加复杂)
  • 虚拟工厂模式类通常基于一系列工厂方法,但是你可以使用原型方法在这些类上整合这些工厂方法。
  • 你可以同时使用工厂方法和迭代器方法去让集合子类返回不同类型的和这些集合适配的迭代器。
  • 原型模式不依赖于继承,所以它没有继承的缺点。另一方面,原型模式需要克隆对象的复杂的初始化。工厂方法是基于继承的但是不需要初始化步骤。
  • 工厂方法是模板模式的特例。同时,一个工厂方法可以作为一个大型的模板方法的一个步骤。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值