行为型模式-类型对象

通过创建一个类来支持新类型的灵活创建,其每个实例都代表一个不同的对象类型。

问题

试着想象一下,如果我们来设计一个怪物的数据我们会如何?

  • 名称
  • 种族
  • 生命值
  • 攻击力

以及我们的Attack行为。

可以想到诸如这些的属性,在以这些数据的基础上来派生更多的特殊化数据。

传统设计

按照我们的常规思维,我们很容易想到如下的设计方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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("精灵攻击");
}
}

假如,此时我们希望怪物的数值和种族挂钩。

我们第一时间想到的肯定是将种族也划分为一个类。

于是我们有了以下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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类可以帮助我们存放所有同种族 怪物的共享信息。

这种,类似组合的设计方案,帮助我们共享一部分数据,但在行为层面 也导致了我们子类过多的问题。

使用类型对象

在类中放置一个对象引用来表述类型,通过怪物+种族的组合方式,来解决子类过多的设计。

❤️ 放置的对象引用已经表述了类型,就无需通过继承来表达关系。

❤️ 通过类型对象搭建工厂生成对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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}";
}

}