说个老生常谈的问题的了,就是简单工厂模式到底是不是违背了开闭原则?
在讨论之前,先重温下什么是开闭原则。
开闭原则(open/closed principle)的定义是,software entities (classes, modules, functions) should be open for extension, but closed for modification,一个类开放它的拓展,关闭它的修改,即一个类可以允许拓展它的功能,而不允许修改它的源码。
先举个简单的例子,关于开闭原则的,看下面一段代码
class DefenseBuff
{
private int m_buffType;
public int BuffType
{
get { return m_buffType; }
set { m_buffType = value; }
}
public int GetDiscountDamage(int TotalDamage)
{
if (m_buffType == 1)
{
return TotalDamage - 100;
}
else
{
return TotalDamage - 50;
}
}
}
上面的问题在于,如果策划要加一个buff种类,你必须又加一个if-else的条件判断,这样会造成无休止的修改此类,然后再次测试此类能否正常运行,包括引用到它的类又能否正常运行。如果到后期系统越变越庞大,则非常难维护。
这就是违背了开闭原则。
如果我们改成下面这样:用继承和多态的方式,如果加了新的类型,我们只要创建一个新的子类就行了,这样就不会违背开闭原则
class DefenseBuff
{
public virtual int GetDiscountDamage(int TotalDamage)
{
return TotalDamage;
}
}
class SilverDefenseBuff : DefenseBuff
{
public override int GetDiscountDamage(int TotalDamage)
{
return base.GetDiscountDamage(TotalDamage) - 50;
}
}
class GoldDefenseBuff : DefenseBuff
{
public override int GetDiscountDamage(int TotalDamage)
{
return base.GetDiscountDamage(TotalDamage) - 100;
}
}
或者我们可以重Excel数据表中读数据,然而在现实开发中,策划一般把数值写在Excel表中的,不会向上面那样写死的,然而我们也不需要再修改代码,而策划可以任意修改数值和增加类型。
class DefenseBuff
{
private int m_buffType;
public int BuffType
{
get { return m_buffType; }
set { m_buffType = value; }
}
public DefenseBuff(int buffType)
{
m_buffType = buffType;
}
public int GetDiscountDamage(int TotalDamage)
{
return BuffTable.GetType(m_buffType).DiscountDamage;//从excel配置表读取数据
}
}
var player = WorldMap.Instance.GetSelfPlayer();
Monster m = WorldMap.Instance.GetMonsterPool().GetAt(0);
DefenseBuff bf = new DefenseBuff(PlayerProperty.Instance.GetMetaData(player.ID).buffType);
player.Damage(bf.GetDiscountDamage(MonsterTable.GetType(m.ID)).AttackDamage));
说完了开闭原则,现在来说一下简单工厂模式,看下面代码:
using System;
enum MonsterType
{
SKELETON,
GHOUST,
EVIL,
CACODEMON
}
abstract class Monster
{
public abstract int AttackDamage { get; }
}
class Skeleton : Monster
{
public override int AttackDamage
{
get
{
return 10;
}
}
}
class Ghoust : Monster
{
public override int AttackDamage
{
get
{
return 20;
}
}
}
class Evil : Monster
{
public override int AttackDamage
{
get
{
return 50;
}
}
}
class Cacodemon : Monster
{
public override int AttackDamage
{
get
{
return 100;
}
}
}
static class MonsterFactory
{
public static Monster Create(MonsterType monster_type)
{
switch(monster_type)
{
case MonsterType.SKELETON:
return new Skeleton();
case MonsterType.GHOUST:
return new Ghoust();
case MonsterType.EVIL:
return new Evil();
case MonsterType.CACODEMON:
return new Cacodemon();
}
}
}
var player = WorldMap.Instance.GetSelfPlayer();
var monster = MonsterFactory.Create(MonsterType.SKELETON);
player.Damage(monster.AttackDamage);
上述代码的问题就在于,如果策划要加一个新的怪物类,那我们就必须增加一个Swith-Case条件,就必须修改MonsterFactory的内部代码,这个其实在一定程度上也已经违背了开闭原则
如果改成下面的方式,类似于抽象工厂的方式,如果新加了一个怪物类,我们只要新增一个MonsterInfo的子类即可,就不会违背开闭原则了。
public abstract class MonsterInfo
{
public abstract Monster CreateMonster();
}
public class SkeletonInfo : MonsterInfo
{
public override Skeleton CreateMonster()
{
return new Skeleton();
}
}
public class GhoustInfo : MonsterInfo
{
public override Ghoust CreateMonster()
{
return new Ghoust();
}
}
public static class MonsterFactory
{
public static Monster Create(MonsterInfo aInfo)
{
return aInfo.CreateMonster();
}
}
var player = WorldMap.Instance.GetSelfPlayer();
var monster = MonsterFactory.Create(new SkeletonInfo());
player.Damage(monster.AttackDamage);
或者我们使用泛型,会更简洁方便
static class MonsterFactory<T> where T : Monster,new()
{
public static Monster Create()
{
return new T();
}
}
var player = WorldMap.Instance.GetSelfPlayer();
var monster = MonsterFactory<Skeleton>.Create();
player.Damage(monster.AttackDamage);
或者使用反射的方式:
public static class MonsterFactory
{
public static Monster Create(Type monsterType)
{
return Activator.CreateInstance(monsterType) as Monster;
}
public static Monster Create(string monsterType)
{
Type type = Assembly.GetExecutingAssembly().GetType(monsterType);
return Activator.CreateInstance(type) as Monster;
}
}
var player = WorldMap.Instance.GetSelfPlayer();
var monster1 = MonsterFactory.Create(typeof(SkeletonInfo));
var monster2 = MonsterFactory.Create("Ghoust");
player.Damage(monster1.AttackDamage);
player.Damage(monster2.AttackDamage);
综上所述,我们基本可以认为带过多的switch-case或if-else的简单工厂模式是违背开闭原则的,会增加代码的耦合度,会造成当系统变得庞大时,极其难以维护。
其实我们有很多的方法,可以绕开过多swith-case或if-else的条件,以满足开闭原则来实现简单的工厂模式。
关于更多的工厂模式和开闭原则,我们今后再继续讨论,今天先说这么多。如果有什么错误或不对的,欢迎大家多多指正。