15.1 运动协会
时间:12月31日 地点:大B房间 人物:大B,小A
小A:“师兄,什么是运动协会?”
大B:“比如有个国家的运动员协会,他们是负责登记与注册职业运动员的(就好像我们国家的体育总局,呵呵,无论足球篮球还是乒乓球的运动员都必须在这里注册才能拿到我们国家职业运动员牌照)。一家体育俱乐部(比如篮球的广东宏远,足球的深圳健力宝)想获得球员为自己俱乐部效力,就必须通过这个运动员协会。”
小A:“怎样去实现‘运动员’接口?有几个客户端?”
大B:“根据DIP我们可以设计一个‘运动员’接口,‘足球运动员’和‘篮球运动员’(还有其他运动员)都实现‘运动员’这个接口。而‘运动员协会’就是一个简单工厂类,它负责实例化‘运动员’。我们这里的‘俱乐部’就是一个客户端(Client),不同的‘俱乐部’就是不同的客户端。对于不同的俱乐部对象(无论是八一还是深圳健力宝),他们都是面向‘运动员’接口编程,而不用管是‘足球运动员’还是‘篮球运动员’,也就是说实现了‘运动员’接口的具体类‘足球运动员’无需暴露给客户端。这也满足了DIP。”
小A:“但具体的俱乐部(比如足球的深圳健力宝)如何确保自己获取的是自己想要的运动员(健力宝俱乐部需要的当然是足球运动员)呢?”
大B:“这就需要‘运动员协会’这一工厂类了。俱乐部通过调用‘运动员协会’的具体方法,返回不同的实例。这同时也满足了LoD,也就是‘深圳健力宝足球俱乐部’对象不直接与‘足球运动员:李毅’对象通信,而是通过他们共同的‘朋友’——‘国家体育总局’通信。”
15.2 工厂方法模式
大B:“都知道Java最大的优点是它的完全OO化和它在多年的发展过程中吸收和总结了许多先进的框架与模式,其中工厂模式就是最常用的模式之一。”
小A:“师兄,能不能讲一下涉及到的OO原则的定义?”
大B:“好的。OCP(开闭原则,Open-Closed Principle):一个软件的实体应当对扩展开放,对修改关闭。我的理解是,对于一个已有的软件,如果需要扩展,应当在不需修改已有代码的基础上进行。DIP(依赖倒转原则,Dependence Inversion Principle):要针对接口编程,不要针对实现编程。我的理解是,对于不同层次的编程,高层次暴露给低层次的应当只是接口,而不是它的具体类。LoD(迪米特法则,Law of Demeter):只与你直接的朋友通信,而避免和陌生人通信。众所周知类(或模块)之间的通信越少,耦合度就越低,从而更有利于我们对软件的宏观管理。老子论‘圣人之治’有相同的思想,《老子》云:‘是以圣人之治,虚其心,实其腹,弱其志,常使民无知无欲。’,又云:‘小国寡民,邻国相望,鸡犬之声相闻,民至老死,不相往来。’。佩服我们的老祖宗,N千年前就想到了西方N千年后才想到的东西,同时也佩服《Java与模式》的作者阎宏,可以用中国传统哲学思想这么生动的说明这一软件设计原则。”
小A:“这么说来,工厂方法模式有什么意义啊?”
大B:“定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。”
15.3 工厂方法模式角色
小A:“工厂方法模式涉及到哪些角色?”
大B:“工厂方法模式的角色有:抽象工厂(Creator)角色、具体工厂(Concrete Creator)角色、抽象产品(Product)角色、具体产品(Concrete Product)角色。
抽象工厂(Creator)角色:是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。
具体工厂(Concrete Creator)角色:这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建产品对象。
有两个这样的角色:BulbCreator与TubeCreator。
抽象产品(Product)角色:工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。
具体产品(Concrete Product)角色:这个角色实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往一一对应。”
如图15-1工厂方法模式所示
图15-1 工厂方法模式
一个简单的实例
15.4 为何使用?
小A:“为什么会使用工厂模式?”
大B:“工厂模式是我们最常用的模式了,著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在Java程序系统可以说是随处可见。”
15.5 为什么工厂模式是如此常用?(www.xing528.com)
小A:“为什么工厂模式是如此常用?”
大B:“因为工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑实用工厂模式,虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。我们以类Sample为例,如果我们要创建Sample的实例对象:Sample sample=new Sample();可是,实际情况是,通常我们都要在创建sample实例时做点初始化的工作,比如赋值查询数据库等。首先,我们想到的是,可以使用Sample的构造函数,这样生成实例就写成:Sample sample=new Sample(参数);但是,如果创建sample实例时所做的初始化工作不是象赋值这样简单的事,可能是很长一段代码,如果也写入构造函数中,那你的代码很难看了(就需要Refactor重整)。”
15.6 为什么说代码很难看
小A:“为什么说代码很难看。”
大B:“初学者可能没有这种感觉,我们分析如下,初始化工作如果是很长一段代码,说明要做的工作很多,将很多工作装入一个方法中,相当于将很多鸡蛋放在一个篮子里,是很危险的,这也是有背于Java面向对象的原则,面向对象的封装(Encapsulation)和分派(Delegation)告诉我们,尽量将长的代码分派‘切割’成每段,将每段再‘封装’起来(减少段和段之间偶合联系性),这样,就会将风险分散,以后如果需要修改,只要更改每段,不会再发生牵一动百的事情。”
15.7 简单工厂模式与工厂方法模式大PK
小A:“什么是简单工厂模式?”
大B:“简单工厂模式又叫静态工厂模式,顾名思义,它是用来实例化目标类的静态类。”
小A:“简单工厂模式有什么优点?”
大B:“现在我就主要通过一个简单的实例说明简单工厂及其优点。”
小A:“嗯。好。”
大B:“如有个国家的运动员协会,他们是负责登记与注册职业运动员的(就好像我们国家的体育总局,呵呵,无论足球篮球还是乒乓球的运动员都必须在这里注册才能拿到我们国家职业运动员牌照)。一家体育俱乐部(比如篮球的广东宏远,足球的深圳健力宝)想获得球员为自己俱乐部效力,就必须通过这个运动员协会。根据DIP我们可以设计一个‘运动员’接口,‘足球运动员’和“篮球运动员”(还有其他运动员)都实现‘运动员’这个接口。而‘运动员协会’就是一个简单工厂类,它负责实例化‘运动员’。我们这里的‘俱乐部’就是一个客户端(Client),不同的‘俱乐部’就是不同的客户端。对于不同的俱乐部对象(无论是八一还是深圳健力宝),他们都是面向‘运动员’接口编程,而不用管是‘足球运动员’还是‘篮球运动员’,也就是说实现了‘运动员’接口的具体类‘足球运动员’无需暴露给客户端。这也满足了DIP。”
小A:“但具体的俱乐部(比如足球的深圳健力宝)如何确保自己获取的是自己想要的运动员(健力宝俱乐部需要的当然是足球运动员)呢?”
大B:“这就需要‘运动员协会’这一工厂类了。俱乐部通过调用‘运动员协会’的具体方法,返回不同的实例。这同时也满足了LoD,也就是‘深圳健力宝足球俱乐部’对象不直接与‘足球运动员:李毅’对象通信,而是通过他们共同的‘朋友’——‘国家体育总局’通信。”
下面给出各个类的程序。
大B:“这就是简单工厂模式的一个简单实例,你应该想象不用接口不用工厂而把具体类暴露给客户端的那种混乱情形吧?就好像没了体育总局,各个俱乐部在市场上自己胡乱的寻找仔细需要的运动员。简单工厂就解决了这种混乱。我们用OCP看看简单工厂,会发现如果要对系统进行扩展的话治需要增加实现产品接口的产品类(上例表现为‘足球运动员’,‘篮球运动员’类,比如要增加个‘乒乓球运动员’类),而无需对原有的产品类进行修改。”
小A:“这咋一看好像满足OCP。”
大B:“但是实际上还是需要修改代码的——对,就是修改工厂类。上例中如果增加‘乒乓球运动员’产品类,就必须相应的修改‘体育协会’工厂类,增加个‘注册乒乓球运动员’方法。所以可以看出,简单工厂模式是不满足OCP的。”
小A:“那工厂方法模式哩?”
大B:“我们刚刚讲了简单工厂模式,下面继续谈谈工厂方法模式。刚才点明了简单工厂模式最大的缺点——不完全满足OCP。为了解决这一缺点,设计师们提出了工厂方法模式。工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一个项目或者一个独立模块而言)工厂类,而工厂方法模式有一组实现了相同接口的工厂类。下面我们通过修改刚才的实例来介绍工厂方法模式。我们在不改变产品类(‘足球运动员’类和‘篮球运动员’类)的情况下,修改下工厂类的结构。”
相关代码如下:
大B:“很明显可以看到,‘体育协会’工厂类变成了‘体育协会’接口,而实现此接口的分别是‘足球协会’‘篮球协会’等等具体的工厂类。”
小A:“这样做有什么好处呢?”
大B:“很明显,这样做就完全OCP了。如果需要再加入(或扩展)产品类(比如加多个‘乒乓球运动员’)的话就不再需要修改工厂类了,而只需相应的再添加一个实现了工厂接口(‘体育协会’接口)的具体工厂类。”
小A:“工厂方法模式是为了克服简单工厂模式的缺点(主要是为了满足OCP)而设计出来的。但是,工厂方法模式就一定比简单工厂模式好呢?”
大B:“不一定。1、结构复杂度。从这个角度比较,显然简单工厂模式要占优。简单工厂模式只需一个工厂类,而工厂方法模式的工厂类随着产品类个数增加而增加,这无疑会使类的个数越来越多,从而增加了结构的复杂程度。2、代码复杂度。代码复杂度和结构复杂度是一对矛盾,既然简单工厂模式在结构方面相对简洁,那么它在代码方面肯定是比工厂方法模式复杂的了。简单工厂模式的工厂类随着产品类的增加需要增加很多方法(或代码),而工厂方法模式每个具体工厂类只完成单一任务,代码简洁。3、客户端编程难度。工厂方法模式虽然在工厂类结构中引入了接口从而满足了OCP,但是在客户端编码中需要对工厂类进行实例化。而简单工厂模式的工厂类是个静态类,在客户端无需实例化,这无疑是个吸引人的优点。4、管理上的难度。这是个关键的问题。我们先谈扩展。众所周知,工厂方法模式完全满足OCP,即它有非常良好的扩展性。”
小A:“那是否就说明了简单工厂模式就没有扩展性呢?”
大B:“不是的。简单工厂模式同样具备良好的扩展性——扩展的时候仅需要修改少量的代码(修改工厂类的代码)就可以满足扩展性的要求了。尽管这没有完全满足OCP,但你认为不需要太拘泥于设计理论,要知道,sun提供的Java官方工具包中也有想到多没有满足OCP的例子啊(Java.util.Calendar这个抽象类就不满足OCP,具体原因大家可以分析下)。然后我们从维护性的角度分析下。假如某个具体产品类需要进行一定的修改,很可能需要修改对应的工厂类。当同时需要修改多个产品类的时候,对工厂类的修改会变得相当麻烦(对号入座已经是个问题了)。反而简单工厂没有这些麻烦,当多个产品类需要修改是,简单工厂模式仍然仅仅需要修改唯一的工厂类。”
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。