乐趣区

关于后端:请不要随便修改基类

如果你对问题的背景不太熟悉,不如温习一下上一篇,入口》.

高级版本

这是玩家的形象根底类,这个设计很好,把一些玩家共有的个性形象进去

 // 玩家的根底抽象类
    abstract class Player
    {     
        // 玩家的级别
        public int Level {get; set;}
        // 其余属性代码省略一万字
    }

这是新加需要:10 级能够跳跃, 具体跳跃动作是客户端做解决

 // 玩家的根底抽象类
    abstract class Player
    {     
        // 玩家的级别
        public int Level {get; set;}
        // 其余属性代码省略一万字

        // 新加玩家跳跃动作,因为须要达到 10 级所以须要判断 level
        public virtual bool Jump()
        {if (Level >= 10)
            {return true;}
            return false;
        }
    }

这种代码高级人员很容易犯,有什么问题呢?

  1. 跳跃的动作被增加到了基类,那所有的子类就都有了这个行为,如果子类机器人玩家不须要这个跳跃的行为呢?
  2. 为了新需要,批改了基类,如果每次需要都须要批改基类,工夫长了,我的项目大了,这个是比拟要命的。

优化版本

因为需要是减少玩家一个行为,依据上一节的介绍,咱们应该理解到,行为在代码级别更偏向于用接口来示意。而且不是所有的玩家类型都须要附加跳跃这个行为。据此优化如下:

// 玩家跳跃的行为
    interface IJump
    {bool Jump();
    }
    // 玩家的根底抽象类
    abstract class Player
    {     
        // 玩家的级别
        public int Level {get; set;}
        // 其余属性代码省略一万字
                
    }
    // 实在玩家
    class PersonPlayer : Player, IJump
    {public bool Jump()
        {if (Level >= 10)
            {return true;}
            return false;
        }
    }

不错,到此咱们曾经防止了高级人员所犯的谬误了,每种玩家类型能够依据须要自行去扩大行为,改天产品狗在加一个 10 级玩家能够飞的行为,顶多在加一个 IFly 的行为接口,而后实现即可。然而这样的设计就没有问题了吗?有,当然有

  1. 每次需要其实还是改变了曾经存在的并且稳固运行的老代码,这是不可取的。而且批改老代码,大大增加了 bug 呈现的概率。
  2. 如果当初咱们的游戏有 20 种玩家类型,其中 19 种须要增加跳跃的行为,那咱们须要批改 19 个玩家的子类,工作量是如此之大。
  3. 利用相似继承的形式扩大对象的行为,是在编译期就把对象的行为确定了。也就是说在设计层面,其实你曾经把代码写死了。

有很多同学的代码就到目前为止了


假如以下为产品狗一个月之后的新需要:

  1. 能跳跃的等级调整为 11 级
  2. 玩家增加能遁地的行为
  3. 新加了 10 种玩家类型

如果你读到了这里,阐明大家都是对于设计谋求卓越的技术人。这里菜菜再强调一遍架构设计的一项重要准则

类应该对批改敞开,对扩大凋谢。

这里须要强调一点,设计的每个局部想要都遵循凋谢 - 敞开准则,通常很难做到。因为要想在不批改现有代码的状况下,你须要破费许多工夫和精力。遵循凋谢敞开准则,通常须要引入更多的形象,减少更多的档次,增大代码的复杂度。因而菜菜倡议把注意力集中在业务中最有可能变动的点上,这些中央利用凋谢敞开准则。至于怎么确定哪些是变动的点,这须要对业务畛域很强的了解和教训了。

当初咱们剖析一下咱们要做的事件,咱们心愿一个对象(player)在不改变的状况下动静的给它赋予新的行为,在业务上实现的性能和用继承的后果相似。总之一句话:

现有的类型优雅的增加新行为,并且能够灵便叠加和替换

现实中的设计图大抵如下:

再次优化

当初咱们认真剖析一下,如果每个新的行为要想扩大对象而又能放弃该对象的本身个性,新行为对象必须是扩大对象的子类,还必须蕴含对象的一个援用能力实现。

重要提醒

  1. 在零碎设计过程中,实现一个接口泛指实现某个对象的超类型,也就是说能够是类或者接口。
  2. 在你零碎设计中,如果你的代码依赖于某个具体的类型,并非形象的超类型,利用此篇介绍的设计办法可能会受到影响。
  3. 附加在对象最外层的行为,不应该窥视被包装的类型外部的一些个性。
  4. 附加在对象外层的行为,能够在内层对象的行为前后退出本人的行为,甚至能够笼罩掉内层对象的行为。
  5. 如果扩大的行为过多,会呈现很多小对象,适度应用会使程序变的很简单,所以设计扩大行为时候须要留神。

落实到代码

假如当初实在玩家的定义如下:

// 玩家的根底抽象类
    public abstract class Player
    {
        // 玩家的级别
        public int Level {get; set;}
        // 其余属性代码省略一万字
    }
    // 实在玩家
    public class PersonPlayer : Player
    { }
    

当初的需要是给实在玩家增加一个 10 级能跳跃的行为, 在不批改原有玩家代码的状况下,扩大跳跃行为代码如下

    // 玩家行为的扩大积攒
    public class PlayerExtension : Player
    {protected Player player;}
    // 跳跃玩家的行为扩大类
    public class PlayerJumpExtension: PlayerExtension
    {public PlayerJumpExtension(Player _player)
        {player = _player;}
        public bool Jump()
        {if (player. Level >= 10)
            {return true;}
            return false;
        }
    }

测试代码如下:

        PersonPlayer player = new PersonPlayer();
        // 给用户动静增加跳跃的行为
            PlayerJumpExtension jumpPlayer = new PlayerJumpExtension(player);
           var ret= jumpPlayer.Jump();
            Console.WriteLine("玩家能不能跳跃:"+ret);
            // 当初玩家降级到 10 级了
            player.Level = 10;
            ret = jumpPlayer.Jump();
            Console.WriteLine("玩家能不能跳跃:" + ret);

测试加过如下:

 玩家能不能跳跃:False
玩家能不能跳跃:True

一个月后产品狗新加一个需要:实在玩家 20 级取得航行的行为,无序改变现有代码,只需持续增加一个能够航行的新扩大

// 玩家能够航行的扩大
    public class PlayerFlyExtension : PlayerExtension
    {public PlayerFlyExtension(Player _player)
        {player = _player;}
        public bool Fly()
        {if (player.Level >= 20)
            {return true;}
            return false;
        }
    }

测试代码如下:

  PlayerFlyExtension flyPlayer = new PlayerFlyExtension(player);
            Console.WriteLine("玩家能不能航行"+flyPlayer.Fly());
            player.Level = 20;
            Console.WriteLine("玩家能不能航行" + flyPlayer.Fly());

测试后果:

 玩家能不能航行 False
玩家能不能航行 True

重要提醒

以上代码级别上属于演示代码,然而设计的理念却很重要。基于以上的设计思维,扩大的行为齐全有能力批改,笼罩玩家的某些行为。比方玩家对象自身有一个喊话的行为,那扩大类依据业务齐全能够让喊话行为执行两次等等批改。

关注支付架构师进阶大礼包

退出移动版