引入问题:在面向对象编程中,创建一个对象的最常用的方法是new一个对象实例,new对象操作符就是用来构造对象实例的,但是在一些情况下,new操作符直接生成对象会带来一些问题,举例说,许多类型对象的创建都需要一系列的步骤,可能需要计算或取得对象的初始设置,选择生成哪个子对象实例,或者在生成需要的对象之前必须先生成一些辅助功能的对象,这些情况下,对象的建立就是一个过程,不仅是一个操作。
定义:
将原来分布在各个地方的复杂对象创建过程单独抽离出来,交给相应的工厂类创建。其它地方想要使用对象直接找工厂创建。这样当我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来
指向新创建的对象。由于创建过程都由工厂统一管理,所以发生创建业务逻辑变化时,只需要修改工厂。
分类:根据产品是具体产品还是具体工厂可分为简单工厂模式和工厂方法模式,根据工厂的抽象程度可分为工厂方法模式和抽象工厂模式。该模式用于封装和管理对象的创建,是一种创建型模式。
一.简单工厂模式
(1)定义:就像它的名字一样,实际上就是使用一个工厂类对产品的创建提供一个简单的封装。具体表现就是使用一个工厂方法,依据传入的参数,生成对应的产品对象;
(2)UML
(3)示例:在游戏中,我们创建不同的角色,比如战士,魔法师,刺客等。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Client2 : MonoBehaviour {
void Start () {
GamePlayer warrior = PlayerFactory.CreatCharacter("战士",1,123,"屠龙刀");
GamePlayer magic = PlayerFactory.CreatCharacter("魔法师", 2, 34, "寒冰法杖");
}
}
/// <summary>
/// 玩家抽象类
/// </summary>
public abstract class GamePlayer
{
}
/// <summary>
/// 战士玩家
/// </summary>
public class WarriorPlayer: GamePlayer
{
public WarriorPlayer(string name, int lv, int atk)
{
Debug.Log("战士属性赋值:" + "name:" + name + "等级" + lv + "攻击力" + atk + "...");
}
}
/// <summary>
/// 魔法师玩家
/// </summary>
public class MagicPlayer : GamePlayer
{
public MagicPlayer(string name, int lv, int atk)
{
Debug.Log("魔法师属性赋值:" + "name:" + name + "等级" + lv + "攻击力" + atk + "...");
}
}
/// <summary>
/// 玩家简单工厂类
/// </summary>
public class PlayerFactory
{
static public GamePlayer CreatCharacter(string name,int lv,int atk,string weapon)
{
GamePlayer gamePlayer = null;
if (name=="战士")
{
Debug.Log("实例化战士对象");
gamePlayer = new WarriorPlayer(name,lv,atk);
Debug.Log("实例化装备");
Debug.Log("赋予战士装备:"+"武器"+weapon+"首饰"+"... " + "戒指" + "...");
}
else if(name == "魔法师")
{
Debug.Log("实例化魔法师对象");
gamePlayer = new MagicPlayer(name, lv, atk);
Debug.Log("实例化装备");
Debug.Log("赋予魔法师装备:" + "武器" + weapon + "首饰" + "... " + "戒指" + "...");
}
return gamePlayer;
}
}
总结:
(1)在一定程度上实现了解耦,把对象的创建过程与使用分离了。
(2)不足之处:通过分析我们发现当游戏每新增一个角色,我们都要修改PlayerFactory里面的创建方法,这显然不符合我们的开闭原则,而且通过传递参数的方式来确定产生的对象相对来讲比较容易出错。
二.工厂方法模式
1.定义:在简单工厂的基础上,把工厂提取成一个接口或抽象类,具体生产什么产品由子工厂类决定;
2.UML
(3)示例:还是上面的那个例子,我们可以提取出角色工厂抽象基类,然后派生出两个子厂,分别生产战士和魔法师。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Client2 : MonoBehaviour {
void Start () {
PlayerFactory warriorFactory = new MagicFactory();
warriorFactory.CreatCharacter(1, 123, "屠龙刀");
warriorFactory.CreatCharacter(1, 123, "倚天剑");
PlayerFactory magicFactory = new MagicFactory();
magicFactory.CreatCharacter(2, 34, "寒冰法杖");
magicFactory.CreatCharacter(3, 34, "雷霆法杖");
}
}
/// <summary>
/// 玩家抽象类
/// </summary>
public abstract class GamePlayer
{
}
/// <summary>
/// 战士玩家
/// </summary>
public class WarriorPlayer : GamePlayer
{
public WarriorPlayer(int lv, int atk)
{
Debug.Log("战士属性赋值:" + "name:" + "战士" + "等级" + lv + "攻击力" + atk + "...");
}
}
/// <summary>
/// 魔法师玩家
/// </summary>
public class MagicPlayer : GamePlayer
{
public MagicPlayer(int lv, int atk)
{
Debug.Log("魔法师属性赋值:" + "name:" + "魔法师" + "等级" + lv + "攻击力" + atk + "...");
}
}
/// <summary>
/// 玩家抽象工厂类
/// </summary>
public interface PlayerFactory
{
GamePlayer CreatCharacter(int lv, int atk, string weapon);
}
public class MagicFactory : PlayerFactory
{
public GamePlayer CreatCharacter(int lv, int atk, string weapon)
{
Debug.Log("实例化魔法师对象");
MagicPlayer gamePlayer = new MagicPlayer(lv, atk);
Debug.Log("实例化装备");
Debug.Log("赋予魔法师装备:" + "武器" + weapon + "首饰" + "... " + "戒指" + "...");
return gamePlayer;
}
}
public class WarriorFactory : PlayerFactory
{
public GamePlayer CreatCharacter(int lv, int atk, string weapon)
{
Debug.Log("实例化战士对象");
GamePlayer gamePlayer = new WarriorPlayer( lv, atk);
Debug.Log("实例化装备");
Debug.Log("赋予战士装备:" + "武器" + weapon + "首饰" + "... " + "戒指" + "...");
return gamePlayer;
}
}
总结:与简单工厂相比,符合了我们的开闭原则,提高了可扩展性,现在游戏每新增一个角色,我们只需要新增相应的角色类和工厂类即可,不需要修改原有的代码。当然这样也在一定程度上增加的系统的复杂性。
三.抽象工厂模式
思考:以上两种工厂,不管拆分,它们本质上都是创建主角的,假如我们要创建怪物呢,难道我们把上述的工厂方法模式完全复制一份,然后修改相应的代码,显然不是一个好办法,虽然符合我们的开闭原则,但却使我们的类的数量大大增加,使我们的系统的更加的冗余 此时我们的抽象工厂就诞生了。
1.定义:抽象工厂模式实质上对AbstarctFactory进行再抽象,然后通过在AbstarctFactory中增加创建产品的接口,然后在具体子工厂中实现新加产品的创建。
2.UML
3.示例:在CharacterFactory中增加创建怪物的接口
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Client2 : MonoBehaviour {
void Start () {
CharacterFactory worriorFactory = new WarriorFactory();
CharacterFactory magicFactory = new MagicFactory();
magicFactory.CreatGamePlayer(2, 34, "寒冰法杖");
worriorFactory.CreatGamePlayer(1, 123, "屠龙刀");
magicFactory.CreatMonster(1, 12, "普通法杖");
worriorFactory.CreatMonster(1, 123, "菜刀");
}
}
/// <summary>
/// 玩家抽象类
/// </summary>
public abstract class GamePlayer
{
}
/// <summary>
/// 战士玩家
/// </summary>
public class WarriorPlayer : GamePlayer
{
public WarriorPlayer(int lv, int atk)
{
Debug.Log("战士玩家属性赋值:" + "name:" + "战士" + "等级" + lv + "攻击力" + atk + "...");
}
}
/// <summary>
/// 魔法师玩家
/// </summary>
///
public class MagicPlayer : GamePlayer
{
public MagicPlayer(int lv, int atk)
{
Debug.Log("魔法师玩家属性赋值:" + "name:" + "战士" + "等级" + lv + "攻击力" + atk + "...");
}
}
/// <summary>
/// 怪物抽象类
/// </summary>
public abstract class GameMonster
{
}
/// <summary>
/// 战士怪物
/// </summary>
public class WarriorMonster : GameMonster
{
public WarriorMonster(int lv, int atk)
{
Debug.Log("战士怪物属性赋值:" + "name:" + "战士" + "等级" + lv + "攻击力" + atk + "...");
}
}
/// <summary>
/// 魔法师怪物
/// </summary>
public class MagicMonster : GameMonster
{
public MagicMonster(int lv, int atk)
{
Debug.Log("魔法师怪物属性赋值:" + "name:" + "魔法师" + "等级" + lv + "攻击力" + atk + "...");
}
}
public interface CharacterFactory
{
GamePlayer CreatGamePlayer(int lv, int atk, string weapon);
GameMonster CreatMonster(int lv, int atk, string weapon);
}
public class WarriorFactory : CharacterFactory
{
public GamePlayer CreatGamePlayer(int lv, int atk, string weapon)
{
Debug.Log("实例化战士玩家对象");
GamePlayer gamePlayer = new WarriorPlayer(lv, atk);
Debug.Log("实例化玩家装备");
Debug.Log("赋予战士玩家装备:" + "武器" + weapon + "首饰" + "... " + "戒指" + "...");
return gamePlayer;
}
public GameMonster CreatMonster(int lv, int atk, string weapon)
{
Debug.Log("实例化战士怪物对象");
GameMonster gameMonster = new WarriorMonster(lv, atk);
Debug.Log("实例化怪物装备");
Debug.Log("赋予战士怪物装备:" + "武器" + weapon + "首饰" + "... " + "戒指" + "...");
return gameMonster;
}
}
public class MagicFactory : CharacterFactory
{
public GamePlayer CreatGamePlayer(int lv, int atk, string weapon)
{
Debug.Log("实例化魔法师玩家对象");
MagicPlayer gamePlayer = new MagicPlayer(lv, atk);
Debug.Log("实例化玩家装备");
Debug.Log("赋予魔法师玩家装备:" + "武器" + weapon + "首饰" + "... " + "戒指" + "...");
return gamePlayer;
}
public GameMonster CreatMonster(int lv, int atk, string weapon)
{
Debug.Log("实例化魔法师怪物对象");
GameMonster gamePlayer = new MagicMonster(lv, atk);
Debug.Log("实例化怪物装备");
Debug.Log("赋予魔法师怪物装备:" + "武器" + weapon + "首饰" + "... " + "戒指" + "...");
return gamePlayer;
}
}
总结:抽象工厂模式是工厂方法模式的升级版,后者面向单个产品,而前者面向的的是一个产品族。
并且产品族各产品之间应该相关或互相依赖。就像官方定义一样: 为创建一组相关/互相依赖的对象提供一个接口而无需指定它们的具体类。
也就是说不是任何情况下我们都有必要升级,就拿我们上面这个例子来讲,我们发现玩家和怪物应该更强调的是一个并列关系并没有太大的关联。所以我们实际上可以再使用一层我们的方法工厂以此来符合我们的逻辑。
然后抽象工厂模式更多的是使用在 一系列相互关联的基础产品,而这些产品共同组成一个成品。比如一个汽车工厂要生成汽车,而每种汽车都有车门、车轮胎等一系列产品,这意味着每增加一款汽车就需要增加一个新的工厂来提供新产品的实现。这时候就可以使用抽象工厂模式来进行设计。所以说设计模式本身就有其适用的场景,并不是滥用的,
使用总结
首先,工厂模式是为了解耦:把对象的创建和使用的过程分开。就是客户端 想调用玩家或怪物,那么客户端只是调用玩家或怪物的方法,而至于客户端的实例化,就交给工厂类。
其次,工厂模式可以降低代码重复。比如,创建对象玩家或怪物的过程很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。我们可以把这些创建玩家或怪物的代码放到工厂里统一管理。既减少了重复代码,也方便以后对B的创建过程的修改维护。当然,你也可以把这些创建过程的代码放到类的构造函数里,同样可以降低重复率不过,这样也会导致构造函数过于复杂,承担了过多的职责。
还有就是由于创建过程都由工厂统一管理,可以降低维护成本,比如我们把创建魔法师怪物的地方改为创建它的子类火魔法师怪物,我们不需要找到所有需要创建魔法师怪物的地方去逐个修正,只需要修改工厂即可,
说工厂模式的使用至少要符合以下两点:
-
对象的创建过程或者实例化准备工作很复杂,需要初始化很多参数,外部赋值等。
2.类本身有好多子类,这些类的创建过程在业务中容易发生改变,或者对类的调用容易发生改变。