论坛首页 Java企业应用论坛

探索设计模式之三——抽象工厂模式

浏览 4804 次
精华帖 (0) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (1)
作者 正文
   发表时间:2010-01-19   最后修改:2010-01-19

3.抽象工厂模式(Abstract Factory Pattern

       前面介绍的“简单工厂模式”和“工厂方法模式”,立足点都是避免显式的创建具体对象,封装创建对象时可能出现的变化点,这已经能比较好的解决单个对象创建的问题,但实际业务中,还经常出现需要一系列对象互相关联使用来完成任务的情况。对于存在关联、以来的产品来说,使用简单工厂或者工厂方法一个一个的创建其中的具体产品,经常无法达到理想的效果。譬如,“西装短裤都分别实现了“上衣”,“裤子”接口,分别套在身上一点问题都没有,但是实际上却不太可能穿着西装和短裤上街。

 

       通过简单工厂或者工厂方法一个一个地单独创建产品然后再合并使用除了有可能出现上面“西服+短裤”的笑话外,用户在使用组合产品的时候,通常是不关心产品具体成员的创建过程的,因此也不应当把各个产品的创建过程揉合到具体逻辑之中。为了解决上面的问题,就GoF经典设计模式中就出现了本章的主角——抽象工厂模式。

 

目的:

创建一系列互相关联或者互相以来的组合产品。

 

场景:

随着游戏的发展,前期小打小闹的试探进攻时期已经过去。我们要考虑的问题也已经不再是一个机枪兵能否战胜一只小狗、一个火兵能否打赢一只刺蛇(见简单工厂模式)了。游戏中期要占得先机,关键不在一兵一卒,而在战术正确与否。

 

星际争霸多年的游戏发展中,出现过无数天马行空的战术和激动人心的战例。其中,在Terran在对抗TerranZerg时,有两种主流战术如下:

 

TvT:主力兵种使用坦克,维护兵种是SCV,空军选择Wraith(隐飞)

TvZ:主力兵种使用枪兵,维护兵种是Medic(护士),空军选择Science Vessel(科学球)

 

分析:

 

       只要对手的种族一确定,我们的战术就定下来,也就是选择的兵种就定下了。出战的时候,都是框一圈兵A过去(-_-#),并不关心里面某个兵是什么时候、如何被生产出来的,关心的只是这组部队能不能优势互补,赢得战争。这符合抽象工厂的使用场景。现在,就通过抽象工厂模式,看看上述两种战术中的兵种组合是如何使用代码演绎的。

 

照例,我们先看一下这次需要涉及到的工厂和产品类图及代码:

 

3.1 战斗部队及工厂的UML

 

public interface IAttackUnit {
    // 主攻单位有attack技能
    public void attack();
}
 
public interface IAirUnit {
    // 空中单位有assist技能
    public void assist();
}
 
public interface IMedicalUnit {
    // 医疗单位有cure技能
    public void cure();
}
 
public class Marine implements IAttackUnit {
    public Marine() {
        System.out.println("制造出一个枪兵。");
    }
    public void attack() {
        System.out.println("机枪兵作为主攻兵种,适合应付小体积单位,作战灵活,能被护士MM治疗。");
    }
}
 
public class Medic implements IMedicalUnit {
    public Medic() {
        System.out.println("制造出一个护士。");
    }
    public void cure() {
        System.out.println("护士能给生物部队快速恢复生命值,对机械部队着无能为力。");
    }
}
 
public class ScienceVessel implements IAirUnit {
    public ScienceVessel() {
        System.out.println("制造出一个科学球。");
    }
    public void assist() {
        System.out.println("科学球作为辅助单位,能侦查隐性单位,能使用辐射技能对目标造成伤害。");
    }
}
 
public class SCV implements IMedicalUnit {
    public SCV() {
        System.out.println("制造出一个SCV。");
    }
    public void cure() {
        System.out.println("SCV能修理坦克,但是对机枪兵等生物部队无能为力。");
    }
}
 
public class Tank implements IAttackUnit {
    public Tank() {
        System.out.println("制造出一辆坦克。");
    }
    public void attack() {
        System.out.println("坦克作为主攻兵种,适合应付大体积单位,威力强大,能比被SCV修理。");
    }
}
 
public class Wraith implements IAirUnit {
    public Wraith() {
        System.out.println("制造出一架隐性飞机。");
    }
    public void assist() {
        System.out.println("隐飞作为辅助单位,有优秀的侦查性能和很高的机动性。");
    }
}
 
 

 

       在上述代码中,“IAttackUnitIAirUnitIMedicalUnit”是抽象产品,实现这三个接口的“TankSCVMarine……”等则是具体产品,关于抽象产品、具体产品等概念在前一章已经介绍过。现在我们继续看看如何通过工厂来为抽象产品选择具体的实现类的。

 

       抽象工厂和具体工厂的实现代码如下:

 

public interface IArmyFactory {
    // 创建空军单位
    public IAirUnit createAirUnit();
    // 创建医疗单位
    public IMedicalUnit createMedicalUnit();
    // 创建主攻单位       
    public IAttackUnit createAttackUnit();
}
 
public class TvTArmyFactory implements IArmyFactory {
    public TvTArmyFactory() {
        System.out.println("当前使用的战术可以应付TvT。");
    }
    // 空中单位是隐飞
    public IAirUnit createAirUnit() {
        return new Wraith();
    }
    // 主攻单位是坦克
    public IAttackUnit createAttackUnit() {
        return new Tank();
    }
    // 医疗单位是SCV
    public IMedicalUnit createMedicalUnit() {
        return new SCV();
    }
}
 
public class TvZArmyFactory implements IArmyFactory {
    public TvZArmyFactory() {
        System.out.println("当前使用的战术可以应付TvZ。");
    }
    // 空中单位是科学球
    public IAirUnit createAirUnit() {
        return new ScienceVessel();
    }
    // 主攻单位是枪兵
    public IAttackUnit createAttackUnit() {
        return new Marine();
    }
    // 医疗单位是护士
    public IMedicalUnit createMedicalUnit() {
        return new Medic();
    }
}
 

 

       客户端调用代码如下:

 

public class WarField {
    // 战斗场景
    public static void warScene(IArmyFactory factory) {
        factory.createAttackUnit().attack();
        factory.createMedicalUnit().cure();
        factory.createAirUnit().assist();
    }
 
    public static void main(String[] args) {
        warScene(new TvTArmyFactory());
        System.out.println();
        warScene(new TvZArmyFactory());
    }
}
 

 

 

从代码上可以看出,每一个具体工厂的目的都不是创造单个的具体产品,而是过把整个产品家族(IAttackUnitIAirUnitIMedicalUnit)的所有具体产品都创造出来,这样各个具体产品之间的联系就通过一个具体工厂来体现。当需要新增一个产品家族,或者已有产品家族的构成成员发生变化的时候,影响也就局限在了某一个具体工厂之中。业务逻辑所依赖的都是抽象工厂和抽象产品,具体产品变化的影响就这样从业务逻辑中隔离开来。

 

这种通过依赖抽象类(或者接口)来削弱耦合、隔离变化点的方法,在设计模式中被总结为一个重要原则——依赖倒转原则:业务逻辑应该依赖于抽象类,而不应该依赖于具体实现类。“依赖倒转”中的“倒转”,指得就是人们直观思维之中,高层逻辑应当依赖与他下面的功能点分解,功能点分解又依赖于底层功能这种自上而下层层依赖的开发思想。这种开发思想中,任何底层的变化都有可能导致产生对上层的影响,同时,上层需求有改动的时候,又同样需要下层改动提供支持,这样当软件层次累计得越来越高的时候,变化就越来越可能引起上下层间的反复传递,导致严重的后果——牵一发而动全身。

 

       面向对象中耦合关系有三种:零耦合、抽象耦合、具体耦合。依赖反转原则的本质意图就是将具体耦合削弱为抽象耦合。从下面两张图上可以看出,在互相依赖的模块中加入一层“抽象层”后,上层业务逻辑、下层具体实现的变化都受抽象类的隔离,变化就相对不容易在各层之间扩散。

 


3.2 自上而下的依赖关系

 


 3.3 依赖反转

 

       如果说开闭原则(见第二章)是设计模式追求的目标,那依赖反转原则就是设计模式的最基本、普遍的解决问题思路。从面向具体类编程到面向接口编程的转变,是一个程序员从代码实现者到设计者进化的标志。

 

       最后,展示一下程序的最终运行结果:



  3.2 两种兵种组合

 

总结:

经过三章关于“工厂模式”的介绍,我们对通过工厂来创建产品,隐藏具体实现和创建细节的做法已经有了一定的了解。对象的产生,除了直接使用new和使用工厂封装外,还有几种其他方式。在这边Terran部队建设如火如荼的时候,且看看那边Zerg的发展情况如何,看看其他几种创建模式是如何在Zerg中发挥作用的。

  • 大小: 7.7 KB
  • 大小: 3.1 KB
  • 大小: 2.5 KB
  • 大小: 18.9 KB
   发表时间:2010-01-19  
PDF下载见附件,《设计模式探索——星际争霸探险之旅》其他章节请见我博客:
http://icyfenix.iteye.com
0 请登录后投票
   发表时间:2010-01-22   最后修改:2010-01-22
谢谢分享。能否这样理解,普通工厂模式是抽象工厂模式的一种特例?

楼主能否用项目实例来阐述下在哪些情况下运用相对应的设计模式?
0 请登录后投票
   发表时间:2010-06-17  
很不错,多谢楼主!有全部23种设计模式的下载吗?如有,请告知,谢谢。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics