C#中OOP平时的一些反思与思考

“从来就没有什么元编程,只有编程而已。”这是《Ruby元编程》中,讲完所有的元编程技巧之后,作者说的话,我感觉很有道理,不管用什么方式,本质上都是在追求让代码有更高的可读性,更少的耦合,更少的重复,封装也好,继承也好都只是实现这个目标的一些手段。

最近在学unity和C#,准备把以前写到一般坑了的战棋游戏重新开工,写了一些代码。C#也是一种OOP的语言,但与Ruby和Python不同,它不是脚本语言,而是需要编译的静态语言,在写的时候,会有一种与之前写脚本语言不太一样的感觉。这里就记录一些C#中OOP实现上的思考,可能大部分只是提出问题,解决问题的方式等之后C#学得更深一点再来看看,可能会有一些不同的想法。

  • 技能、物品、以及攻击,都需要两个范围,一个是射程(与动作发起者的距离),一个是攻击范围(与目标点的距离)。对于范围,我写了一个Range类,来实现一些坐标上的判断函数,如Reach(可否覆盖到)等等。

    • 但既然技能 物品 攻击,都需要两个范围,那我总不能在这三个地方都写一模一样的代码吧。
    • 一个解决办法是使用类,让技能类 物品类 攻击都继承自一个ActionWithRange的类。但这显然有点问题,因为C#不支持多重继承,而这个"WithRange"的特性又不是这些类的主要特性。
    • 使用接口是否可以?接口只能定义方法和属性,不能定义实例变量。
    • 另一个方法是把这两个Range再封装成一个类,然后技能 物品 攻击中,再定义一个这个类的对象。
  • Unity 2D中,角色等物体使用的是世界坐标,UI等用的是屏幕坐标

  • 鸭子类型与泛型

    • 鸭子类型是Python中的一个概念。意思是说,一个类,只要长得像鸭子,那么它就是鸭子。举个例子,比如说有个函数,用来做坐标变换。接收的参数是一个对象。那么它不需要管这个对象具体属于什么类,只要这个对象有.x() .y()的方法,那么这个函数就可以正常运行。
    • C#中有个类似概念,叫做泛型。但是C#由于是编译型语言,并不能像Python那样写。如果要实现像上面那个坐标变换的函数,C#中得声明一个泛型的函数,同时还必须进行泛型约束,约束它支持的对象,必须有.x .y,具体到语言上,就是必须实现一个接口。这个接口可能叫coordinate_like,实现这个接口的都有两个方法.x() 和.y()。那么实现这个接口的类的对象,就可以作为那个泛型函数的参数。
  • 属性

    • C#中,如果有个实例变量,不想写成public,因为不希望外部能修改它的值,但是又希望外部能访问。
    • 写一个get_XXX的方法,在这里返回变量的值。
    • 简便方式,定义public XXX{get;} 这种方式是定义了一个属性,可以在里面定义get 和set方法,这里只写get,那么外部就不能对齐赋值,该变量只能在构造函数中被赋值。
  • 过约束

    • 过约束是机械建模中的概念,大概就是指对一个参考面提供过多的约束条件。
    • 一个角色,有属性cell,代表其现在所在的格子,还有属性position,代表其所在的坐标。但是格子类Cell,自己也有一个坐标。当角色位于某个格子上的时候,角色坐标与cell的坐标一定是一样。但如果cell只是一个普通的实例变量,那么每次修改角色的坐标之后,还得手动改变cell变量,将其指向新位置的格子。此时最好将cell使用属性方法,访问cell,相当于调用get_cell(position.x,position.y),这样来获取格子对象,就不需要在角色类中自己再维护一个cell了。
  • 反射

    • C# 中的一个概念,在运行时获取此时的对象类型信息。可以实现
      typeof(DynamicSample).GetMethod("Add");

      类似这样的代码

    • 我还是更习惯将其称为“自省”
  • 委托

    • C# 中的概念,实际上类似于一个函数指针
    • 委托的简化调用,先判断是否为null,否则就调用它
      call_back?.Invoke(this.value);

发表评论