从王者荣耀看设计模式(工厂方法模式)
二.简介
王者荣耀游戏设计师根据英雄技能、属性、天赋等因素,将英雄划分为射手、辅助、打野、法师、坦克、战士等职业。一局比赛存在多类英雄可供选择。玩家在挑选游戏英雄时,合理的英雄职业搭配是赢得游戏胜利的基本保证。
三.工厂方法模式
工厂方法模式(Factory Method Pattern):工厂方法模式又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,它属于类创建型模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
- 工厂方法模式使用场景
- 一个类不知道他所需要的对象的类:在工厂方法模式中,客户端不需要知道
具体产品类的类名,只需知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。 - 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需提供一个创建产品的接口,而由其子类来确定具体要求创建的对象,利用面向对象的多态性和里氏替换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中
工厂方法模式涉及的设计原则有:
★依赖于抽象,不依赖于具体类
★对于扩展是开放的,对于修改是封闭的
★封装变化
★针对接口编程,不针对实现编程工厂方法模式的通用类图:
工厂方法模式涉及的角色有:
● Product(抽象产品)
抽象产品是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的共同父类或接口
● ConcreteProduct(具体产品)
具体产品实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,它们之间一一对应。
● Factory(抽象工厂)
在抽象工厂类中,声明了工厂方法(Factory Method),用于返回一个产品。抽象工厂是工厂方法模式的核心,它与应用程序无关。任何在模式中创建对象的工厂类都必须实现该接口
● ConcreteFactory(具体工厂)
具体工厂是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户调用,返回一个具体产品类的实例。在具体工厂类中包含了与应用程序紧密相关的逻辑,并且接受应用程序调用以创建产品对象。- 工厂方法模式的优点:
- 在工厂方法模式中,工厂方法模式用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将其实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无需知道具体产品类的类名。
- 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。他能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类
- 使用工厂方法模式的另一优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他具体工厂和具体类,而只要添加一个具体工厂和具体产品就可以。这样,系统的可扩展性就变得非常好,完全符合"开闭原则".
- 工厂方法模式的缺点
- 在一定的程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到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();
}
}
运行结果