通过创建一个类来支持新类型的灵活创建,其每个实例都代表一个不同的对象类型。
# 问题
试着想象一下,如果我们来设计一个怪物的数据我们会如何?
- 名称
- 种族
- 生命值
- 攻击力
- …
以及我们的 Attack 行为。
可以想到诸如这些的属性,在以这些数据的基础上来派生更多的特殊化数据。
# 传统设计
按照我们的常规思维,我们很容易想到如下的设计方案。
public abstract class MosterBase | |
{ | |
public string Name { get; set; } | |
public int Hp { get; set; } | |
public string Race { get; set; } | |
public int Damage { get; set; } | |
protected virtual void Attack() { } | |
} | |
public class BigCat : MosterBase | |
{ | |
protected override void Attack() | |
{ | |
base.Attack(); | |
Console.WriteLine("喵!"); | |
} | |
} | |
public class Elf: MosterBase | |
{ | |
protected override void Attack() | |
{ | |
base.Attack(); | |
Console.WriteLine("精灵攻击"); | |
} | |
} |
假如,此时我们希望怪物的数值和种族挂钩。
我们第一时间想到的肯定是将 种族
也划分为一个类。
于是我们有了以下的代码:
public class Race | |
{ | |
public string Name { get; set; } | |
public int Hp { get; set; } | |
public int Damage { get; set; } | |
} | |
public abstract class MosterBase | |
{ | |
public string Name { get; set; } | |
public int Hp { get; set; } | |
public int Damage { get; set; } | |
public Race Race { get; set; } | |
protected virtual void Attack() { } | |
} |
Race
类可以帮助我们存放所有同种族 怪物的共享信息。
这种,类似组合的设计方案,帮助我们共享一部分数据,但在行为层面 也导致了我们子类过多的问题。
# 使用类型对象
在类中放置一个对象引用来表述类型,通过怪物 + 种族的组合方式,来解决子类过多的设计。
❤️ 放置的对象引用已经表述了类型,就无需通过继承来表达关系。
❤️ 通过类型对象搭建工厂生成对象。
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
/* | |
* 类型对象:是指通过组合的方式去解决继承导致的强耦合问题 | |
* 例如:在下面的例子中,如果我们将种族放在 Moster 中,然后通过继承的方式实现抽象类 Moster,会导致子类过多,耦合强。 | |
* 使用一个单独的对象将种族抽离出来,通过怪物和种族的组合方式就可以创建不同的怪物。 | |
*/ | |
var breed = new Breed { AttackLevel = 5, BaseHp = 50, Name = "狼" }; | |
Console.WriteLine(new Moster() { Hp = 100, Name = "灰狼", Breed = breed }); | |
Console.WriteLine(new Moster() { Hp = 50, Name = "白狼", Breed = breed }); | |
Console.WriteLine(breed.NewMoster()); | |
var breed2 = new Breed { AttackLevel = 6, Name = "死灵狼", Parent = breed }; | |
var mos = breed2.NewMoster("恐怖之狼"); | |
//Console.WriteLine(mos); | |
Console.WriteLine(breed2); | |
} | |
} | |
/// <summary> | |
/// 怪物 | |
/// </summary> | |
class Moster | |
{ | |
public Breed Breed { get; set; } | |
public int Hp { get; set; } | |
public int SumHp => Breed.BaseHp + Hp; | |
public string Name { get; set; } | |
public override string ToString() | |
{ | |
return $"{Name} HP:{SumHp} breedName:{Breed.Name} AttackLevel:{Breed.AttackLevel}"; | |
} | |
} | |
/// <summary> | |
/// 种族 | |
/// </summary> | |
class Breed | |
{ | |
public string Name { get; set; } | |
public int AttackLevel { get; set; } | |
// 为了区分是否为子种族重写值,我们可以如此判定 | |
private int baseHp; | |
public int BaseHp | |
{ | |
get | |
{ | |
if (baseHp == 0 && Parent != null) | |
{ return Parent.baseHp; } | |
return baseHp; | |
} | |
set { baseHp = value; } | |
} | |
// 可以通过这种方式为种族添加一个方法工厂化生成 Moster | |
public Moster NewMoster(string name = "") | |
{ | |
return new Moster() { Breed = this, Name = name }; | |
} | |
// 可以通过指定父种族指定层级关系,不使用继承,存储内存地址,在子类和父类仅仅只是数据上不同时,可以避免子类数目过多 | |
public Breed Parent { get; set; } | |
public override string ToString() | |
{ | |
return $"{Parent.Name}->{Name} AttackLevel:{AttackLevel} BaseHp:{BaseHp}"; | |
} | |
} |