某人做了一套模拟鸭子的游戏《SimDuck》,游戏中会出现各种鸭子,一边游泳戏水,一边呱呱的叫。她设计了一个鸭子的父类,并让各种鸭子继承自此类,代码结构如下。
/// <summary> /// 父类鸭子 /// </summary> public class Duck { public virtual void quack() { //呱呱叫 } public virtual void swim() { //游泳 } public virtual void display() { //外观 } }
/// <summary> /// 野鸭 /// </summary> public class MallardDuck : Duck { public override void display() { //外观是野鸭 Debug.Log("外观是野鸭"); } }
/// <summary> /// 红头鸭子 /// </summary> public class RedHeadDuck : Duck { public override void display() { //外观是红头鸭 Debug.Log("外观是红头鸭"); } }
这时新需求来了,要让鸭子会飞!那还不简单,直接在父类的鸭子里增加一个Fly的方法,不就行了,你的代码如下。
//只需要改动鸭子的父类即可 /// <summary> /// 父类鸭子 /// </summary> public class Duck { public virtual void quack() { //呱呱叫 } public virtual void swim() { //游泳 } public virtual void display() { //外观 } public virtual void fly() { //飞 } }
这时新需求又来了,增加橡胶鸭和木头鸭。同样简单,你直接一套继承完事。这时候你的代码如下。
/// <summary> /// 橡胶鸭 /// </summary> public class RubberDuck : Duck { public override void quack() { //覆盖成吱吱叫 //橡胶鸭子吱吱叫,没玩过吗? Debug.Log("覆盖成吱吱叫"); } public override void display() { //外观是橡胶鸭 Debug.Log("外观是橡胶鸭"); } public override void fly() { //橡胶鸭子不会飞,什么都不做 } }
/// <summary> /// 木头鸭子 /// </summary> public class DecoyDuck : Duck { public override void quack() { //木头鸭子不会叫,什么这里什么都不做 } public override void display() { //外观是木头鸭 Debug.Log("外观是木头鸭"); } public override void fly() { //木头鸭子也不会飞,什么都不做 } }
这时候问题就来了。 ① 相同代码在子类中不停重复,写的烦死了,你甚至都懒的使用CV大法。 ② 代码已经写死,运行时不易改变。 ③ 添加功能时造成其他鸭子不想要的改变。 ④ 你思考过后准备重构代码,把易变的模块提取成接口,你的想法如下图。
设计原则:找出变化之处,把他们独立出来。不要和那些不变的东西混在一起。
这样虽然解决了一部分的问题,但是代码还是有重复问题,实现IFly接口的类还是要写关于鸭子飞行的方法,尽管这听起来有点不可思议。所以你决定再次重构代码。
重构之后的代码增加了代码可重用性,易扩展性,可维护性。
设计原则:要针对接口编程,不要针对实现编程。
针对实现编程: Dog dog = new Dog(); dog.Dark(); 针对接口/父类编程: Animal animal = new Dog(); animal.MakeSound()
只有在在运行是才指定具体实现的对象,我们一点不管他是什么类型,我们只关心他正确的实现MakeSound方法就好了。有了上面的思路,我们需要重新写一下我们的代码,代码如下图。
下边是鸭子行为的接口和此行为的具体类。
/// <summary> /// 所有飞行行为的接口 /// </summary> public interface IFlyBehaviour { void Fly(); }
/// <summary> /// 所有叫行为的接口 /// </summary> public interface IQuackBehaviour { void Quack(); }
/// <summary> /// 使用翅膀飞 /// </summary> public class FlyWithWings : IFlyBehaviour { public void Fly() { //使用翅膀飞 Debug.Log("使用翅膀飞"); } }
/// <summary> /// 不会飞 /// </summary> public class FlyNoWay : IFlyBehaviour { public void Fly() { //什么都不做,不能飞 Debug.Log("什么都不做,不能飞"); } }
/// <summary> /// 呱呱叫 /// </summary> public class Quack : IQuackBehaviour { public void Quack() { //呱呱的叫 Debug.Log("呱呱的叫"); } }
/// <summary> /// 吱吱叫 /// </summary> public class Squeak : IQuackBehaviour { public void Quack() { //吱吱的叫 Debug.Log("吱吱的叫"); } }
/// <summary> /// 不会叫 /// </summary> public class MuteQuack : IQuackBehaviour { public void Quack() { //什么都不做,不会叫 Debug.Log("什么都不做,不会叫"); } }
下面是鸭子的抽象类,关于抽象类需要注意以下几点: ① 抽象类只能用做其他类的基类。 ② 抽象类可以包含抽象成员和普通的非抽象成员。 ③ 抽象可以继承自另一个抽象类 ④ 所有抽象类的成员必须使用override关键字实现。 ⑤ 抽象类不能创建实例。
/// <summary> /// 鸭子抽象类 /// </summary> public abstract class SuperNewDuck { public IFlyBehaviour flyBehaviour; public IQuackBehaviour quackBehaviour; //必须要实现的可以考虑抽象方法 public abstract void Swim(); public abstract void Display(); public void PerformFly() { //鸭子不会亲自处理飞的行为,而是委托给flyBehaviour引用的对象 flyBehaviour.Fly(); } public void PerformQuack() { //鸭子不会亲自处理呱呱叫的行为,而是委托给quackBehaviour引用的对象 quackBehaviour.Quack(); } public void SetFlyBehaviour(IFlyBehaviour flyType) { flyBehaviour = flyType; } public void SetQuackBehaviour(IQuackBehaviour quackType) { quackBehaviour = quackType; } }
下面是具体的鸭子类。
/// <summary> /// 野鸭 /// </summary> public class SuperMallardDuck : SuperNewDuck { public SuperMallardDuck() { flyBehaviour = new FlyWithWings(); quackBehaviour = new Quack(); } public override void Display() { Debug.Log("外观是野鸭"); } public override void Swim() { Debug.Log("在花式游泳"); } }
/// <summary> /// 木头鸭 /// </summary> public class SuperMecoyDuck : SuperNewDuck { public SuperMecoyDuck () { flyBehaviour = new FlyNoWay(); quackBehaviour = new MuteQuack(); } public override void Display() { Debug.Log("外观是木头鸭"); } public override void Swim() { Debug.Log("在正常游泳"); } }
//Unity测试代码... public class DuckSimulator : MonoBehaviour { private void Start() { //实例一只野鸭 SuperNewDuck superMallardDuck = new SuperMallardDuck(); superMallardDuck.Display(); superMallardDuck.Swim(); superMallardDuck.PerformFly(); superMallardDuck.PerformQuack(); Debug.Log("--------动态改变鸭子飞行行为-----------"); //运行时改变飞行状态 superMallardDuck.SetFlyBehaviour(new FlyWithWings()); superMallardDuck.PerformFly(); } }
这时新需求来了,要增加一只超级鸭子,他使用的是导弹飞行器,你怎么办? ① 添加FlyWithRocket类实现自IFlyBehaviour接口 ② 给超级鸭子创建FlyWithRocket行为
整理下,最终的代码如下图。
设计原则:多用组合,少用继承。这里的少用并不是不用。
此设计模式名称:策略模式,他定义了算法族,分别封装起来。让他们之间可以互相替换,此算法独立于使用算法的客户。
设计谜题: ① 现在想象一下你要做一款动作冒险类(ACT)的游戏 ② 有一个主角会使用武器 ③ 但是同一时刻只能使用一种武器 ④ 武器之间可以随意的切换 请你仔细想想你该怎么设计你的代码的架构并运用到游戏中去...
好,现在来说说想法: ① 首先想到的应该是武器的抽象,将所有武器抽象成一个名为IWeaponBehaviour的接口,让其他的武器去实现这个接口,已达到动态改变行为的目的 ② 然后就是角色的父类,具有配备武器的属性,战斗的属性,动态改变武器类型的属性 ③ 你的代码结构可能如下
/// <summary> /// 角色父类 /// </summary> public class BaseCharacter { protected IWeaponBehaviour weaponBehaviour; //角色配备武器的属性 public virtual void fight() { } //战斗 public void SetWeapon(IWeaponBehaviour w) //切换武器 { weaponBehaviour = w; } }
/// <summary> /// 武器行为接口 /// </summary> public interface IWeaponBehaviour { void useWeapon(); }
using UnityEngine; /// <summary> /// 武器巴雷特狙击枪 /// </summary> public class Barrett : IWeaponBehaviour { public void useWeapon() { Debug.Log("使用巴雷特狙击枪"); } }
using UnityEngine; /// <summary> /// 武器加特林 /// </summary> public class Gatling : IWeaponBehaviour { public void useWeapon() { Debug.Log("使用武器加特灵机关枪"); } }
using UnityEngine; /// <summary> /// 武器石头 /// </summary> public class Stone : IWeaponBehaviour { public void useWeapon() { Debug.Log("使用石头攻击"); } }
using UnityEngine; /// <summary> /// 武器剑 /// </summary> public class Sword : IWeaponBehaviour { public void useWeapon() { Debug.Log("使用剑"); } }
/// <summary> /// 角色皇帝 /// </summary> public class King : BaseCharacter { public King() { weaponBehaviour = new Barrett(); } public override void fight() { weaponBehaviour.useWeapon(); } }
/// <summary> /// 角色皇后 /// </summary> public class Queen : BaseCharacter { public Queen() { weaponBehaviour = new Gatling(); } public override void fight() { weaponBehaviour.useWeapon(); } }
/// <summary> /// 角色骑士 /// </summary> public class Knight : BaseCharacter { public Knight() { weaponBehaviour = new Sword(); } public override void fight() { weaponBehaviour.useWeapon(); } }
/// <summary> /// 角色侏儒(或者叫哥布林哈哈) /// </summary> public class Troll : BaseCharacter { public Troll() { weaponBehaviour = new Stone(); } public override void fight() { weaponBehaviour.useWeapon(); } }
using System.Collections; using System.Collections.Generic; using UnityEngine; public class WarSimulator : MonoBehaviour { void Start() { BaseCharacter king = new King(); BaseCharacter queen = new Queen(); king.fight(); king.SetWeapon(new Sword()); king.fight(); Debug.Log("---------分割线----------"); queen.fight(); } }
最后再来复习一下我们学习的设计模式 ① 此设计模式名为策略模式 ② 其核心设计思想是封装可互相替换的算法和策略,每一个算法都独立于使用算法的客户
Logo

分享前沿Unity技术干货和开发经验,精彩的Unity活动和社区相关信息

更多推荐