36

请不要随便修改基类

 3 years ago
source link: https://segmentfault.com/a/1190000023746303
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

v2iqQr.png!mobile

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

初级版本

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

//玩家的基础抽象类
    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)在不改动的情况下动态的给它赋予新的行为,在业务上实现的功能和用继承的结果类似。总之一句话:

现有的类型优雅的添加新行为,并且可以灵活叠加和替换

理想中的设计图大致如下:

mQvIzeN.jpg!mobile

再次优化

现在我们认真分析一下,如果每个新的行为要想扩展对象而又能保持该对象的自身特性,新行为对象必须是扩展对象的子类,还必须包含对象的一个引用才能实现。

uY7FVfN.jpg!mobile

重要提示

  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

重要提示

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

关注领取架构师进阶大礼包

VnQRJzi.gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK