JAVA 工厂模式

来源:互联网 发布:淘宝新规则2016年9月 编辑:程序博客网 时间:2024/05/16 15:31

上一篇博文中,我介绍了类的静态工厂方法,其主要作用主要是产生一个自身或者子类的实例,从而完善自身的功能和描述。
而工厂模式则是为了解耦,客户端在需要使用产品类的实例的时候,不需要通过 new 或者产品类的静态工厂方法来产生其实例,而是首先创建工厂类,然后通过工厂类提供的方法来得到产品类的实例。

根据工厂模式的实现方式,又分简单工厂模式,工厂方法模式和抽象工厂模式三种。这三种模式并非单纯是递进改善的,而是各有优缺点,各自适应不同的场景。

首先需要明确的是,我们的工厂生产的产品是具有共性的,例如在游戏中,一个根据用户所选语言生产游戏角色的工厂。它生产的各种角色都能在特定场景下说特定台词,释放技能进行攻击等。这样,我们可为角色产品定义一个反映了角色的共性的接口。所有该工厂生产的产品都要实现该接口。

/* * 角色标准接口 */public interface Role {    //场景台词    void lines(String scene);    //释放技能攻击    void attack(String skill);}/* * 中文角色类 */public class ChnRole implements Role {    @Override    public void lines(String scene) {        System.out.println("中文角色在场景" + scene + "中说中文台词!" );    }    @Override    public void attack(String skill) {        System.out.println("中文角色使用" + skill + "进行攻击!" );    }}/* * 英文角色类 */public class EngRole implements Role {    @Override    public void lines(String scene) {        System.out.println("English Role speak English lines in scene " + scene);    }    @Override    public void attack(String skill) {        System.out.println("English Role attack enemy with " + skill);    }}

1) 简单工厂模式
使用简单工厂模式生产上述形状产品的方式如下。

public class RoleFactory {    public static Role createRole(String lang){        if("chinese".equalsIgnoreCase(lang)){            return new ChnRole();        }else if("english".equalsIgnoreCase(lang)){            return new EngRole();        }        return null;    }}

简单工厂模式通过一个工厂类的静态方法来生产产品,它接受一个关于待生产产品语言信息的参数,利用 JAVA 的多态特性,返回相应的产品实例。
对比上一篇博文中描述的类静态工厂方法,可以发现类的静态工厂方法实际上是简单工厂模式的一种特殊情况。静态工厂方法返回的实例是定义静态工厂方法的类的实例或其子类的实例,而简单工厂模式的工厂类则专门生产其它类的实例。

简单工厂模式将创建产品的过程和使用产品的过程进行了分割。将来的程序代码中,如果需使用 Role 的某个产品实例,用户只需要调用工厂类的创建方法,并给出想要使用的类的创建参数就可获得相应的实例,而无需关心该实例具体是怎么样被创建的。

简单工厂模式的缺点也很明显。

  • 一旦产品类的范围发生变化,例如添加了 KorRole 类,就要更新工厂类,其才能正常生产新的类型实例,因此简单工厂类是不符合面向对象编程的开闭原则。
  • 由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
  • 系统扩展困难,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。

总的来说,简单工厂模式适用情况包括:工厂类负责创建的对象比较少;客户端只知道传入工厂类的参数,对于如何创建对象不关心。

2) 工厂方法模式
工厂方法模式将简单工厂模式中的工厂类进行抽象化,使其成为抽象化的工厂接口。然后为每一个产品类创建相应的工厂类,使其实现抽象化的工厂接口。将来想要使用某个产品实例时,直接借助于其对应的产品工厂类来获得。
工厂类接口

public interface IFactoryMethod {    Role createRole();}

具体产品的工厂

/* * 中文角色工厂类 */public class ChnRoleFactory implements IFactoryMethod {    @Override    public Role createRole() {        return new ChnRole();    }}/* * 英文角色工厂类 */public class EngRoleFactory implements IFactoryMethod {    @Override    public Role createRole() {        return new EngRole();    }}

下面是使用工厂方法创建产品实例的一般做法。

public class RoleFactoryMethodTest {    public void roleLines(IFactoryMethod factory, String scene){        factory.createRole().lines(scene);    }    public static void main(String[] args) {        RoleFactoryMethodTest factoryTest = new RoleFactoryMethodTest();        factoryTest.roleLines(new ChnRoleFactory(), "和 NPC 交谈");        factoryTest.roleLines(new EngRoleFactory(), "Having dinner with a lady");    }}# 运行后的输出为# 中文角色在场景和 NPC 交谈中说中文台词!# English Role speak English lines in scene Having dinner with a lady

这样,用户根据需要产生什么样的实例,在使用的时候通过创建相应的实例工厂即可获得实例。面对和简单工厂模式同样的问题,即增加了一种新产品时,只需增加新产品对应的工厂类(例如 KorRoleFactory) 即可,而不需要修改已有的代码,满足了开闭原则。

工厂方法模式改善了简单工厂模式,但工厂方法模式导致系统中类的个数随着产品种类成倍增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
使用匿名内部类可以很好地改善工厂方法模式导致的类增加问题。其主要思想是在每个具体产品内部增加生产该产品的工厂,同样的,该工厂需要实现共同的工厂接口。这样就不用为每一个具体产品增加一个工厂类了。
例如,我们在中文角色产品中增加生产它的工厂。

/* * 中文角色类 */public class ChnRole implements Role {    @Override    public void lines(String scene) {        //...    }    @Override    public void attack(String skill) {        //...    }    //匿名内部类工厂    public static IFactoryMethod factory = new IFactoryMethod() {        @Override        public Role createRole() {            return new ChnRole();        }    };}

这样,我们在需要生产某种产品时,仍通过工厂得到它,只不过不用先 new 一个实例工厂了。

IFactoryMethod factory = ChnRole.factory;ChnRole chnRole = factory.createRole();

3) 抽象工厂模式
抽象工厂模式是抽象程度最高的工厂模式。它对工厂方法模式最大的改进在于其具体工厂可以产生多种产品。例如,我们的游戏除了需要一个游戏角色外,还需要游戏场景,游戏场景也根据语言的不同会发生一些变化。
游戏场景产品系列定义如下。

/* * 游戏场景接口 * */public interface Scene {    void describe();} /* * 中文游戏场景 * */public class ChnScene implements Scene {    @Override    public void describe() {        System.out.println("中文场景说明");    }}/* * 英文游戏场景 * */public class EngScene implements Scene {    @Override    public void describe() {        System.out.println("Engish description of the scene");    }}

游戏中,需要呈现某一画面时,需要同时得到画面中的角色及场景。我们的抽象工厂类需要规定两个分别生产角色和场景的方法。

/* * 抽象工厂类 */public abstract class AbstractFactory {    // 工厂的公有方法,显示语言    public void language(String lang){        System.out.println("Language: " + lang);    }    //生产角色,由子类工厂根据需要具体实现    public abstract Role createRole();    //生产场景,由子类工厂根据需要具体实现    public abstract Scene createScene();}

然后是具体的工厂类

/* * 中文画面工厂类,生产中文角色及场景 */public class ChnFactory extends AbstractFactory {    @Override    public Role createRole() {        return new ChnRole();    }    @Override    public Scene createScene() {        return new ChnScene();    }}/* * 英文画面工厂类,生产英文角色及场景 */public class EngFactory extends AbstractFactory {    @Override    public Role createRole() {        return new EngRole();    }    @Override    public Scene createScene() {        return new EngScene();    }}

下面是产品工厂创建产品实例的一般做法。

public class AbstractFactoryTest {    public static void main(String[] args) {        AbstractFactory chnFactory = new ChnFactory();        chnFactory.language("中文");        chnFactory.createScene().describe();        chnFactory.createRole().attack("寒冰掌");        AbstractFactory engFactory = new EngFactory();        engFactory.language("English");        engFactory.createScene().describe();        engFactory.createRole().attack("magic");    }}# 运行后的输出为# Language: 中文# 中文场景说明# 中文角色使用寒冰掌进行攻击!# Language: English# Engish description of the scene# English Role attack enemy with magic

在分析上述示例前,我们先介绍产品族和产品等级的概念。产品族是指一些产品一起构成了一个整体概念,这些产品就构成了一个产品族,例如我们游戏中的角色(Role)和场景(Scene),又比如海尔生产的电视和空调。产品等级指的则是同类型的不同产品,例如游戏中的中文角色和英文角色,又比如海尔生产的电视和 TCL 生产的电视。

从上面的例子可以看出,抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建,只需要关心实例需要能够做哪些事情。由于这种隔离,更换一个具体工厂(产品族工厂)就变得相对容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。另外,应用抽象工厂模式可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到了广泛的应用。
例如,我们的游戏发展过程中,需要添加一个新的语种韩语支持,我们只需要新增韩语产品族(KorRole, KorScene 等)和相应的韩语产品族工厂(KorFactory),并在需要的地方使用韩语产品族工厂替代原来的产品族工厂即可,无需改动已经构建完成的原有代码。

然而抽象工厂也有明显的缺点。在添加新的产品等级时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。
例如,我们要为游戏画面添加背景音乐,新引入了 BgMusic (ChnBgMusic, EngBgMusic 产品等级),则需要在抽象工厂中添加生产背景音乐的方法。这将导致我们需要修改所有继承了抽象工厂的具体工厂类,不符合开闭原则。

参考材料:
[http://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/abstract_factory.html]

从 java 8 开始,接口中可以引入 default 方法(包含实现),作为接口的默认功能被其实现类继承,这将在某种程度改善工厂模式的实现方式。

0 0