PHP?refimg=" + this.src)" style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" border="0" alt="" width="504" height="379" src="http://img.jb51.cc/vcimg/static/loading.png" src="http://images.cnblogs.com/cnblogs_com/leoo2sk/WindowsLiveWriter/a5703ca6e7d5_D180/01_3.gif">
图1.1 需求描述ppt
@H_403_0@各个开发人员,面对这份需求,展开了热烈的讨论,下面我们看看讨论会上都发生了什么。 @H_403_0@1.2 实习生小李的实现方式 @H_403_0@在经过一番讨论后,项目组长Peter觉得有必要整理一下各方的意见,他首先询问小李的看法。小李是某学校计算机系大三学生,对游戏开发特别感兴趣,目前是IGame公司的一名实习生。 @H_403_0@经过短暂的思考,小李阐述了自己的意见: @H_403_0@“我认为,这个需求可以这么实现。HP当然是怪物的一个属性成员,而武器是角色的一个属性成员,类型可以使字符串,用于描述目前角色所装备的武器。角色类有一个攻击方法,以被攻击怪物为参数,当实施一次攻击时,攻击方法被调用,而这个方法首先判断当前角色装备了什么武器,然后据此对被攻击怪物的HP进行操作,以产生不同效果。” @H_403_0@而在阐述完后,小李也飞快的在自己的电脑上写了一个Demo,来演示他的想法,Demo代码如下。 @H_403_0@Code:怪物1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace IGameLi
7: {
8: /// <summary>
9: /// 怪物
10: /// </summary>
11: internal sealed class Monster
12: {
13: /// <summary>
14: /// 怪物的名字
15: /// </summary>
16: public String Name { get; set; }
17:
18: /// <summary>
19: /// 怪物的生命值
20: /// </summary>
21: public Int32 HP { get; set; }
22:
23: public Monster(String name,Int32 hp)
24: {
25: this.Name = name;
26: this.HP = hp;
27: }
28: }
29: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace IGameLi
7: {
8: /// <summary>
9: /// 角色
10: /// </summary>
11: internal sealed class Role
12: {
13: private Random _random = new Random();
14:
15: /// <summary>
16: /// 表示角色目前所持武器的字符串
17: /// </summary>
18: public String WeaponTag { get; set; }
19:
20: /// <summary>
21: /// 攻击怪物
22: /// </summary>
23: /// <param name="monster">被攻击的怪物</param>
24: public void Attack(Monster monster)
25: {
26: if (monster.HP <= 0)
27: {
28: Console.WriteLine("此怪物已死");
29: return;
30: }
31:
32: if ("WoodSword" == this.WeaponTag)
33: {
34: monster.HP -= 20;
35: if (monster.HP <= 0)
36: {
37: Console.WriteLine("攻击成功!怪物" + monster.Name + "已死亡");
38: }
39: else
40: {
41: Console.WriteLine("攻击成功!怪物" + monster.Name + "损失20HP");
42: }
43: }
44: else if ("IronSword" == this.WeaponTag)
45: {
46: monster.HP -= 50;
47: if (monster.HP <= 0)
48: {
49: Console.WriteLine("攻击成功!怪物" + monster.Name + "已死亡");
50: }
51: else
52: {
53: Console.WriteLine("攻击成功!怪物" + monster.Name + "损失50HP");
54: }
55: }
56: else if ("MagicSword" == this.WeaponTag)
57: {
58: Int32 loss = (_random.NextDouble() < 0.5) ? 100 : 200;
59: monster.HP -= loss;
60: if (200 == loss)
61: {
62: Console.WriteLine("出现暴击!!!");
63: }
64:
65: if (monster.HP <= 0)
66: {
67: Console.WriteLine("攻击成功!怪物" + monster.Name + "已死亡");
68: }
69: else
70: {
71: Console.WriteLine("攻击成功!怪物" + monster.Name + "损失" + loss + "HP");
72: }
73: }
74: else
75: {
76: Console.WriteLine("角色手里没有武器,无法攻击!");
77: }
78: }
79: }
80: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace IGameLi
7: {
8: class Program
9: {
10: static void Main(string[] args)
11: {
12: //生成怪物
13: Monster monster1 = new Monster("小怪A",50);
14: Monster monster2 = new Monster("小怪B",50);
15: Monster monster3 = new Monster("关主",200);
16: Monster monster4 = new Monster("最终Boss",1000);
17:
18: //生成角色
19: Role role = new Role();
20:
21: //木剑攻击
22: role.WeaponTag = "WoodSword";
23: role.Attack(monster1);
24:
25: //铁剑攻击
26: role.WeaponTag = "IronSword";
27: role.Attack(monster2);
28: role.Attack(monster3);
29:
30: //魔剑攻击
31: role.WeaponTag = "MagicSword";
32: role.Attack(monster3);
33: role.Attack(monster4);
34: role.Attack(monster4);
35: role.Attack(monster4);
36: role.Attack(monster4);
37: role.Attack(monster4);
38:
39: Console.ReadLine();
40: }
41: }
42: }
PHP?refimg=" + this.src)" style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" border="0" alt="" width="289" height="226" src="http://img.jb51.cc/vcimg/static/loading.png" src="http://images.cnblogs.com/cnblogs_com/leoo2sk/WindowsLiveWriter/a5703ca6e7d5_D180/02_3.gif">
图1.2 小李程序的运行结果
@H_403_0@1.3 架构师的建议 @H_403_0@小李阐述完自己的想法并演示了Demo后,项目组长Peter首先肯定了小李的思考能力、编程能力以及初步的面向对象分析与设计的思想,并承认小李的程序正确完成了需求中的功能。但同时,Peter也指出小李的设计存在一些问题,他请小于讲一下自己的看法。 @H_403_0@小于是一名有五年软件架构经验的架构师,对软件架构、设计模式和面向对象思想有较深入的认识。他向Peter点了点头,发表了自己的看法: @H_403_0@“小李的思考能力是不错的,有着基本的面向对象分析设计能力,并且程序正确完成了所需要的功能。不过,这里我想从架构角度,简要说一下我认为这个设计中存在的问题。 @H_403_0@首先,小李设计的Role类的Attack方法很长,并且方法中有一个冗长的if…else结构,且每个分支的代码的业务逻辑很相似,只是很少的地方不同。 @H_403_0@再者,我认为这个设计比较大的一个问题是,违反了OCP原则。在这个设计中,如果以后我们增加一个新的武器,如倚天剑,每次攻击损失500HP,那么,我们就要打开Role,修改Attack方法。而我们的代码应该是对修改关闭的,当有新武器加入的时候,应该使用扩展完成,避免修改已有代码。 @H_403_0@一般来说,当一个方法里面出现冗长的if…else或switch…case结构,且每个分支代码业务相似时,往往预示这里应该引入多态性来解决问题。而这里,如果把不同武器攻击看成一个策略,那么引入策略模式(Strategy Pattern)是明智的选择。 @H_403_0@最后说一个小的问题,被攻击后,减HP、死亡判断等都是怪物的职责,这里放在Role中有些不当。” @H_403_0@Tip:OCP原则,即开放关闭原则,指设计应该对扩展开放,对修改关闭。 @H_403_0@Tip:策略模式,英文名Strategy Pattern,指定义算法族,分别封装起来,让他们之间可以相互替换,此模式使得算法的变化独立于客户。 @H_403_0@小于边说,边画了一幅UML类图,用于直观表示他的思想。 @H_403_0@PHP?refimg=" + this.src)" style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" border="0" alt="" width="586" height="407" src="http://img.jb51.cc/vcimg/static/loading.png" src="http://images.cnblogs.com/cnblogs_com/leoo2sk/WindowsLiveWriter/a5703ca6e7d5_D180/03_3.jpg">
图1.3 小于的设计
@H_403_0@Peter让小李按照小于的设计重构Demo,小李看了看小于的设计图,很快完成。相关代码如下: @H_403_0@Code:IAttackStrategy接口1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace IGameLiAdv
7: {
8: internal interface IAttackStrategy
9: {
10: void AttackTarget(Monster monster);
11: }
12: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace IGameLiAdv
7: {
8: internal sealed class WoodSword : IAttackStrategy
9: {
10: public void AttackTarget(Monster monster)
11: {
12: monster.Notify(20);
13: }
14: }
15: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace IGameLiAdv
7: {
8: internal sealed class IronSword : IAttackStrategy
9: {
10: public void AttackTarget(Monster monster)
11: {
12: monster.Notify(50);
13: }
14: }
15: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace IGameLiAdv
7: {
8: internal sealed class MagicSword : IAttackStrategy
9: {
10: private Random _random = new Random();
11:
12: public void AttackTarget(Monster monster)
13: {
14: Int32 loss = (_random.NextDouble() < 0.5) ? 100 : 200;
15: if (200 == loss)
16: {
17: Console.WriteLine("出现暴击!!!");
18: }
19: monster.Notify(loss);
20: }
21: }
22: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace IGameLiAdv
7: {
8: /// <summary>
9: /// 怪物
10: /// </summary>
11: internal sealed class Monster
12: {
13: /// <summary>
14: /// 怪物的名字
15: /// </summary>
16: public String Name { get; set; }
17:
18: /// <summary>
19: /// 怪物的生命值
20: /// </summary>
21: private Int32 HP { get; set; }
22:
23: public Monster(String name,Int32 hp)
24: {
25: this.Name = name;
26: this.HP = hp;
27: }
28:
29: /// <summary>
30: /// 怪物被攻击时,被调用的方法,用来处理被攻击后的状态更改
31: /// </summary>
32: /// <param name="loss">此次攻击损失的HP</param>
33: public void Notify(Int32 loss)
34: {
35: if (this.HP <= 0)
36: {
37: Console.WriteLine("此怪物已死");
38: return;
39: }
40:
41: this.HP -= loss;
42: if (this.HP <= 0)
43: {
44: Console.WriteLine("怪物" + this.Name + "被打死");
45: }
46: else
47: {
48: Console.WriteLine("怪物" + this.Name + "损失" + loss + "HP");
49: }
50: }
51: }
52: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace IGameLiAdv
7: {
8: /// <summary>
9: /// 角色
10: /// </summary>
11: internal sealed class Role
12: {
13: /// <summary>
14: /// 表示角色目前所持武器
15: /// </summary>
16: public IAttackStrategy Weapon { get; set; }
17:
18: /// <summary>
19: /// 攻击怪物
20: /// </summary>
21: /// <param name="monster">被攻击的怪物</param>
22: public void Attack(Monster monster)
23: {
24: this.Weapon.AttackTarget(monster);
25: }
26: }
27: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace IGameLiAdv
7: {
8: class Program
9: {
10: static void Main(string[] args)
11: {
12: //生成怪物
13: Monster monster1 = new Monster("小怪A",1000);
17:
18: //生成角色
19: Role role = new Role();
20:
21: //木剑攻击
22: role.Weapon = new WoodSword();
23: role.Attack(monster1);
24:
25: //铁剑攻击
26: role.Weapon = new IronSword();
27: role.Attack(monster2);
28: role.Attack(monster3);
29:
30: //魔剑攻击
31: role.Weapon = new MagicSword();
32: role.Attack(monster3);
33: role.Attack(monster4);
34: role.Attack(monster4);
35: role.Attack(monster4);
36: role.Attack(monster4);
37: role.Attack(monster4);
38:
39: Console.ReadLine();
40: }
41: }
42: }
PHP?refimg=" + this.src)" style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" border="0" alt="" width="566" height="352" src="http://img.jb51.cc/vcimg/static/loading.png" src="http://images.cnblogs.com/cnblogs_com/leoo2sk/WindowsLiveWriter/a5703ca6e7d5_D180/04_6.jpg">
图3.1 Setter注入示意
@H_403_0@上图展示了Setter注入的结构示意图,客户类ClientClass设置IServiceClass类型成员_serviceImpl,并设置Set_ServiceImpl方法作为注入点。Context会负责实例化一个具体的ServiceClass,然后注入到ClientClass里。 @H_403_0@下面给出Setter注入的示例代码。1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace SetterInjection
7: {
8: internal interface IServiceClass
9: {
10: String ServiceInfo();
11: }
12: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace SetterInjection
7: {
8: internal class ServiceClassA : IServiceClass
9: {
10: public String ServiceInfo()
11: {
12: return "我是ServceClassA";
13: }
14: }
15: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace SetterInjection
7: {
8: internal class ServiceClassB : IServiceClass
9: {
10: public String ServiceInfo()
11: {
12: return "我是ServceClassB";
13: }
14: }
15: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace SetterInjection
7: {
8: internal class ClientClass
9: {
10: private IServiceClass _serviceImpl;
11:
12: public void Set_ServiceImpl(IServiceClass serviceImpl)
13: {
14: this._serviceImpl = serviceImpl;
15: }
16:
17: public void ShowInfo()
18: {
19: Console.WriteLine(_serviceImpl.ServiceInfo());
20: }
21: }
22: }