从王者荣耀看设计模式(十四.工厂方法模式)

从王者荣耀看设计模式(工厂方法模式)

从王者荣耀看设计模式(十四.工厂方法模式)

二.简介

王者荣耀游戏设计师根据英雄技能、属性、天赋等因素,将英雄划分为射手、辅助、打野、法师、坦克、战士等职业。一局比赛存在多类英雄可供选择。玩家在挑选游戏英雄时,合理的英雄职业搭配是赢得游戏胜利的基本保证。

三.工厂方法模式

工厂方法模式(Factory Method Pattern):工厂方法模式又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,它属于类创建型模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

  • 工厂方法模式使用场景
  1. 一个类不知道他所需要的对象的类:在工厂方法模式中,客户端不需要知道
    具体产品类的类名,只需知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
  2. 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需提供一个创建产品的接口,而由其子类来确定具体要求创建的对象,利用面向对象的多态性和里氏替换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展
  3. 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中
  • 工厂方法模式涉及的设计原则有:
    ★依赖于抽象,不依赖于具体类
    ★对于扩展是开放的,对于修改是封闭的
    ★封装变化
    ★针对接口编程,不针对实现编程

  • 工厂方法模式的通用类图:
    从王者荣耀看设计模式(十四.工厂方法模式)

  • 工厂方法模式涉及的角色有:
    Product(抽象产品)
    抽象产品是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的共同父类或接口
    ConcreteProduct(具体产品)
    具体产品实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,它们之间一一对应。
    Factory(抽象工厂)
    在抽象工厂类中,声明了工厂方法(Factory Method),用于返回一个产品。抽象工厂是工厂方法模式的核心,它与应用程序无关。任何在模式中创建对象的工厂类都必须实现该接口
    ConcreteFactory(具体工厂)
    具体工厂是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户调用,返回一个具体产品类的实例。在具体工厂类中包含了与应用程序紧密相关的逻辑,并且接受应用程序调用以创建产品对象。

  • 工厂方法模式的优点:
  1. 在工厂方法模式中,工厂方法模式用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将其实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无需知道具体产品类的类名。
  2. 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。他能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类
  3. 使用工厂方法模式的另一优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他具体工厂和具体类,而只要添加一个具体工厂和具体产品就可以。这样,系统的可扩展性就变得非常好,完全符合"开闭原则".
  • 工厂方法模式的缺点
  1. 在一定的程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
  2. 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

四.简单工厂方法模式实现

4.1结构图

从王者荣耀看设计模式(十四.工厂方法模式)

4.2设计类图

从王者荣耀看设计模式(十四.工厂方法模式)

4.3代码实现

创建工厂类HeroFactory(核心)

package com.practice.SimpleFactory;
public class HeroFactory {
    static Hero hero = null;
    public static Hero getHeroInstance(String heroTP) throws Exception {
        if(heroTP.equalsIgnoreCase("Tank")) {
            hero = new TankHero();
        }else if(heroTP.equalsIgnoreCase("Wild")) {
            hero = new WildHero();
        }else if(heroTP.equalsIgnoreCase("Magus")) {
            hero = new MagusHero();
        }else if(heroTP.equalsIgnoreCase("Shoot")) {
            hero = new ShootHero();
        }else if(heroTP.equalsIgnoreCase("Auxiliary")) {
            hero = new AuxiliaryHero();
        }else {
            throw new Exception();
        }
        return hero;
    }
}

创建抽象接口类Hero

package com.practice.SimpleFactory;

public interface Hero {
    void display();
}

创建具体产品类AuxiliaryHero(辅助英雄类)

package com.practice.SimpleFactory;

public class AuxiliaryHero implements Hero{
    public void display() {
        System.out.println("您的选择为:辅助(战地医生,为队友输血、解控、打鸡血)");
    }
}

创建具体产品类MagusHero(法师英雄类)

package com.practice.SimpleFactory;

public class MagusHero implements Hero{
    public void display() {
        System.out.println("您的选择为:法师(法师,大杀器,基本就是战场上的核弹,一炸一大片,输出还十分高,最高效的人头收割器");
    }
}

创建具体产品类ShootHero(射手英雄类)

package com.practice.SimpleFactory;

public class ShootHero implements Hero{
    public void display() {
        System.out.println("您的选择为:射手(射手,火力压制,利用大打击面形成火力网保护网中的近战英雄)");
    }
}

创建具体产品类TankHero(坦克英雄类)

package com.practice.SimpleFactory;

public class TankHero implements Hero{
    public void display() {
        System.out.println("您的选择为:坦克(坦克主要是在前排吸收伤害,并且尽量控制敌人)");
    }
}

创建具体产品类WildHero(打野英雄类)

package com.practice.SimpleFactory;

public class WildHero implements Hero{
    public void display() {
        System.out.println("您的选择为:打野(后排杀手,目标是在开战时以最快速度摧毁对方的输出主力)");
    }
}

XML配置文件(Config.xml)
通过配置文件可以极大提高系统的扩展性,让软件实体更符合开闭原则。为了让系统更符合开闭原则和依赖倒置原则,需要做到"将抽象写在代码中,将具体写在配置里"通过修改无须编译的配置文件来提高系统的可扩展性和灵活性

<?xml version="1.0" encoding="UTF-8"?>
<config>
    <className>Tank</className>
</config>

工具类读取XML文件信息(XMLUtilHero类)
在该工具类中,通过Java语言提供的DOM(Document Object Model,文档对象模型)API来实现对XML文档的操作,在DOM API中,XML文档以树形结构存储在内存中,可以通过相关的类对XML进行读取、修改等操作

package com.practice.Client;
import java.io.File;
import javax.xml.parsers.*;
import org.w3c.dom.*;

public class XMLUtilHero {
    //该方法用于从XML配置文件中提取英雄名称,并返回该英雄名称
    public static String getHeroName() {
        try {
            //创建文档对象
            DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = dFactory.newDocumentBuilder();
            Document doc;
            doc = builder.parse(new File("src\\com\\practice\\Client\\config.xml"));
            
            //获取包含英雄名称的文本节点
            NodeList nl = doc.getElementsByTagName("className");
            Node classNode = nl.item(0).getFirstChild();
            String dName = classNode.getNodeValue().trim();
            return dName;
        }catch(Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

创建测试类Client类

package com.practice.Client;

import com.practice.Hero.Hero;
import com.practice.HeroFactory.HeroFactory;

public class Client {
    public static void main(String [] args) {
        Hero hero;
        HeroFactory factory;
        factory = (HeroFactory) XMLUtilHero.getHeroFactory();
        hero = factory.produceHero();
        hero.display();
    }
}

代码运行结果:
从王者荣耀看设计模式(十四.工厂方法模式)

从代码结构不难看出,当我们需要添加新英雄职业比如战士类英雄时,我们必须修改工厂类,加入要处理的逻辑,这违背了"开闭原则"。在简单工厂模式中,所有的产品都是由同一个工厂创建,工厂职责较重,业务逻辑比较复杂,具体产品与工厂类之间的耦合度较高,严重影响了系统的灵活性和扩展性,而工厂方法模式则可以很好地解决这一问题。

五.用工厂方法模式实现

5.1 结构图

从王者荣耀看设计模式(十四.工厂方法模式)

5.2 设计类图

从王者荣耀看设计模式(十四.工厂方法模式)

5.3 代码实现

创建抽象产品类(Hero类)

package com.practice.Hero;

public interface Hero {
    public void display();
}

创建具体产品类(AuxiliaryHero类(辅助英雄类))

package com.practice.Hero;

public class AuxiliaryHero implements Hero {
    public void display() {
        System.out.println("辅助:战地医生,为队友输血、解控、打鸡血");
    }

}

创建具体产品类(MagusHero类(法师英雄类))

package com.practice.Hero;

public class MagusHero implements Hero {
    public void display() {
        System.out.println("法师:大杀器,基本就是战场上的核弹,一炸一大片,输出还十分高,最高效的人头收割器");
    }

}

创建具体产品类(ShootHero类(射手英雄类))

package com.practice.Hero;

public class ShootHero implements Hero{
    public void display() {
        System.out.println("射手:火力压制,利用大打击面形成火力网保护网中的近战英雄");
    }
}

创建抽象工厂类(HeroFactory)

package com.practice.HeroFactory;

import com.practice.Hero.Hero;

public interface HeroFactory {
    Hero produceHero();
}

创建具体工厂类(AuxiliaryHeroFactory(辅助工厂类))

package com.practice.HeroFactory;

import com.practice.Hero.AuxiliaryHero;
import com.practice.Hero.Hero;

public class AuxiliaryHeroFactory implements HeroFactory {
    public Hero produceHero() {
        System.out.println("您的选择为:辅助");
        return new AuxiliaryHero();
    }
}

创建具体工厂类(MagusHeroFactory(法师工厂类))

package com.practice.HeroFactory;

import com.practice.Hero.Hero;
import com.practice.Hero.MagusHero;

public class MagusHeroFactory implements HeroFactory {
    public Hero produceHero() {
        System.out.println("您的选择为:法师");
        return new MagusHero();
    }
}

创建具体工厂类(ShootHeroFactory(射手工厂类))

package com.practice.HeroFactory;

import com.practice.Hero.Hero;
import com.practice.Hero.ShootHero;

public class ShootHeroFactory implements HeroFactory {
    public Hero produceHero() {
        System.out.println("您的选择为:射手");
        return new ShootHero();
    }
}

配置文件XML(config.xml)

<?xml version="1.0" encoding="UTF-8"?>
<config>
    <HeroFactory>ShootHeroFactory</HeroFactory>
</config>

辅助类(XMLUtilHero读取XML文件)

package com.practice.Client;

import java.io.File;

import javax.xml.parsers.*;
import org.w3c.dom.*;


public class XMLUtilHero {
    //该方法用于从XML配置文件中提取具体类类名。并返回一个实例对象
    public static Object getHeroFactory() {
        try {
            //创建文档对象
            DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = dFactory.newDocumentBuilder();
            Document doc;
            doc = builder.parse(new File("src\\com\\practice\\Client\\config.xml"));
            
            //获取包含类名的文本节点
            NodeList nl = doc.getElementsByTagName("HeroFactory");
            Node classNode = nl.item(0).getFirstChild();
            String HeroFactory = classNode.getNodeValue();
            
            //通过类名生成实例对象并将其返回
            String ClassName = "com.practice.HeroFactory."+ HeroFactory;
            Class<?> c = Class.forName(ClassName);
            @SuppressWarnings("deprecation")
            Object obj = c.newInstance();           
            return obj;
        }catch(Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

创建测试类(Client类)

package com.practice.Client;

import com.practice.Hero.Hero;
import com.practice.HeroFactory.HeroFactory;

public class Client {
    public static void main(String [] args) {
        Hero hero;
        HeroFactory factory;
        factory = (HeroFactory) XMLUtilHero.getHeroFactory();
        hero = factory.produceHero();
        hero.display();
    }
}

运行结果
从王者荣耀看设计模式(十四.工厂方法模式)

六.源代码下载

从王者荣耀看设计模式(工厂方法模式)

上一篇:从王者荣耀看设计模式(九.命令模式)


下一篇:PAT (Advanced Level) Practice 1001 A+B Format