zoukankan      html  css  js  c++  java
  • 程序员过关斩将--请不要随便修改基类

    菜菜哥,又来找你了

    YY妹子,没和你男票分吧?

    暂时还没有....找你还是因为前几天和你说的那个游戏的事

    产品经理又要改?

    you are right! 这次产品狗要给玩家(player)加升级机制,例如经验值到100升级到二级

    这个好办呀,不就是修改一下任务的级别信息吗?

    麻烦的不是这里,除了级别信息变动之外,有可能还获取到技能,比如:升到10级获取了跳跃的技能

    这样的功能在尽量不修改源代码的基础上还是需要好好设计一下,来靠近一点,菜菜哥详细和你说

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


    ◆◆
    初级版本
    ◆◆


    这是玩家的抽象基础类,这个设计很好,把一些玩家共有的特性抽象出来

    //玩家的基础抽象类
        abstract class Player
        {     
            //玩家的级别
            public int Level { getset; }
            //其他属性代码省略一万字
        }


    这是新加需求:10级可以跳跃,具体跳跃动作是客户端做处理

     //玩家的基础抽象类
        abstract class Player
        {     
            //玩家的级别
            public int Level { getset; }
            //其他属性代码省略一万字

            //新加玩家跳跃动作,由于需要到达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 { getset; }
            //其他属性代码省略一万字

        }
        //真实玩家
        class PersonPlayer : PlayerIJump
        {
            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 { getset; }
            //其他属性代码省略一万字
        }
        //真实玩家
        public class PersonPlayer : Player
        {

        }


    现在的需求是给真实玩家添加一个10级能跳跃的行为,在不修改原有玩家代码的情况下,扩展跳跃行为代码如下

    //玩家行为的扩展积累
        public class PlayerExtension : Player
        {
           protected Player player;

        }
        //跳跃玩家的行为扩展类
        public class PlayerJumpExtensionPlayerExtension
        {
           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


    重要提示

    以上代码级别上属于演示代码,但是设计的理念却很重要。基于以上的设计思想,扩展的行为完全有能力修改,覆盖玩家的某些行为。比如玩家对象本身有一个喊话的行为,那扩展类根据业务完全可以让喊话行为执行两次等等修改。



  • 相关阅读:
    spring boot 中统一异常处理
    An Errors/BindingResult argument is expected to be declared immediately after the model attribute, the @RequestBody or the @RequestPart arguments to which they apply: public com.rongrong.springboot.de
    SPI(Service Provider Interface)机制
    局域网的路由器&网卡
    Kafka
    限流算法
    生成对抗网络(GAN)
    神经网络
    AR介绍
    NIO的Buffer&Channel&Selector
  • 原文地址:https://www.cnblogs.com/zhanlang/p/10502051.html
Copyright © 2011-2022 走看看