Unity学习日志_面向对象思想

Unity学习日志_面向对象思想

面向对象的主要思想

面向对象编程单元模型

在这里插入图片描述

面向对象的思想

(主要应用于做类,做方法,不管调用)

  1. 分而治之:将一个大的需求拆分成若干的小类,每个类单独处理一个独立的模块。独立模块便于分工,便于复用,便于拓展。拆分的原则是只有一个变化点。

  2. 封装变化:功能(行为)不同,功能不同的变化点最好只有一个,则应当封装成不同的类。产生变化的地方独立封装,避免影响其他模块。比如:行走,游泳,飞行都可以算是移动,但明显行为不同,这是就需要写3个类。

  3. 高内聚:类的单一职责,指的是类中所有的方法都在完成一项任务。复杂的实现被封装,对外只提供简单的调用方法。

  4. 低耦合:类与类之间的依赖度低。即一个类改变对其它类不影响或者影响小。(类的作用域问题)推荐再调用类时调用父类而不是子类,这样可以降低耦合。这里体现依赖倒置。

面向对象的优势

高复用,高拓展,高维护,高移植。

面向对象中类与类的区别,对象与对象的区别

类与类之间是功能不同(行为不同)。

对象和对象之间是数据不同。

面向对象和面向过程对比

  1. 面向过程的编程思路:关注实现程序的过程步骤。
  2. 面向对象的编程思路:关注对象的责任。
    1. 找出对象。
    2. 分配工作。
    3. 建立交互。(为实现逻辑相互调用方法)

三大特征

继承

封装

多态

八大原则

封装开闭原则OCP:对功能拓展开放,对代码修改关闭。允许添加新功能,但禁止修改原有代码。(目标,总的指导思想)

组合复用原则CRP:如果仅仅想要复用代码优先推荐使用关联。关联相对于继承更加灵活。关联表达出的是有一个的关系,而不是is a的关系。

**依赖倒置(依赖抽象)**DIP:设计调用参数时使用父级参数而不是使用子级参数。这样只要是继承于父级参数的都可以传进来。这里的父级参数往往是作为一个抽象概念。抽象的往往是稳定的,实现的往往是变化的。父类型的选择应当以实际情况为准。

单一职责SRP:每个类的功能应当只有一个,且类内部的所有方法都在完成这一项功能。适用于基础类,并不适用于由基础类聚合成的聚合类。聚合类常充当调用者的角色整理功能逻辑。

里氏转换LSP:父类出现的地方可以被子类替换,再替换后任然保持原有功能。子类在重写方法时尽量选择拓展重写,防止改变原有功能。

接口隔离(功能拆分)ISP:尽量定义小而精的接口,避免大而全的接口。和类的单一原则是一个意思,实现多功能时多实现就好。

面向接口编程:增强代码通用性,也属于依赖倒置的一种。客户端通过一系列抽象操作实例,而无需关注具体类型。可以实现软件的并行开发。

迪米特法则(类与类之间的交互原则)LOD:类与类之间在满足功能的基础上,交互的数据越少越好。这样可以降低耦合。可以通过接口屏蔽不需要的数据。或者通过事件来屏蔽不需要的数据。具体的例子类似于AnimationEventBehaviour和AnimationEventAciton的调用关系。

类与类的四大关系

泛化:A,B类之间为继承关系。耦合最高,当父类发生改变,子类和之后继承的类一定会发生改变。

实现:实现抽象类或者接口。

关联:类似于起到文件夹的作用。耦合中等,

依赖:以方法参数的关系联系起来。耦合较低,

封装

定义

  1. 数据角度:

多种数据类型描述同一种事物可以使用类进行封装,当多种类描述同一抽象事物,可以将这些类进一步封装。是一种抽象逻辑概括

  1. 程序角度:

向类外提供功能,屏蔽实现细节。

  1. 设计角度:

封装变化,分而治之,高内聚低耦合。

抽象类

抽象类往往表示一个抽象的概念,只表示做什么,有什么数据但没有具体的做法,所以适合用来作为若干类的父类使用。

抽象方法和虚方法的区别

抽象方法:推迟给子类实现,方法体不明

虚方法:子类选择实现,方法体明确

继承

定义

重用现有类的功能,并拓展功能和概念。继承不光有功能上的继承也有概念上的继承。

优点

代码复用的一种方式。

统一概念,用层次化的关系去管理类。

缺点

继承之间耦合度较高。有继承关系的类在代码设计时会看作一个整体。

切换不灵活。

适用性

*当物体之间存在现实逻辑上的 is a 关系且需要统一处理时使用继承,否则不要使用继承。用关联。

多个类具有相同的数据结构或者行为,可以提取出一个统一的父类。

关联层次化与继承层次化

有现实继承关系的且内容存在共性的优先使用继承。

关联层次化适合于有层级关系,但内容独立(不需要父类的成员)的信息类。且上一级无法管理下一级(想要管理需要写额外的函数和数据结构)但关联层次化对于调用者较为友好,调用时可以看到清晰的层次化关系

继承的层次化管理:想要管理一系列子类,只需找到他们的直接父类即可,其他的父类不用管。

方法隐藏(隐藏不是多态)

在这里插入图片描述

多态

定义

简单来说就是父类型的引用调用同一方法在不同的子类上有不同的实现的现象叫做多态,继承在体现子类之间的共性,多态在体现子类之间的个性。

多态的实现手段

虚方法和实现方法:父引用指向子对象的形况下调用虚方法必然会调用子类的重写方法。

抽象方法重写:父引用指向子对象的形况下调用虚方法必然会调用子类的重写方法。

接口:实现接口。

注意:

  1. 多态的体现在于重写方法。而重写方法只有在程序运行时才知道调用哪个子类对象。
  2. 父类型引用指向子类对象,可以使用什么取决于引用类型。
  3. 子类之间不可转换类型。as关键词转换失败结果为null,注意使用引用类型的变量时要记得判空。

方法重写

在这里插入图片描述

方法重写的原理

在类被加载时,会在内存的方法区中存放对应类的方法表。更改的时机是程序运行时出现父引用指向子对象的形况时,下图所示:

在这里插入图片描述

untiy实现多态步骤

  1. 直接将子类拖拽到游戏对象上。
  2. 创建父类引用。
  3. GetComponent子类赋值给父类引用,实现多态。

静态绑定和动态绑定

方法重写override是动态绑定。

方法隐藏new是静态绑定。隐藏方法通过子类型引用调用,会覆盖掉父类型的同名方法。

在这里插入图片描述

简单来说:动态绑定的数据是在程序执行时确定的,可以随时更改;静态绑定的数据是在程序编译的时候确定的,是不可更改的。

隐藏方法解决脚本生命周期冲突

可以使用方法隐藏来解决脚本生命周期冲突问题。在隐藏方法内部通过base关键字调用父类脚本生命周期。

虽然方法重写现象上也可以解决这个问题,但:

  1. 性能不如new
  2. 关键点在于base的调用,而不是重写和隐藏。

接口

定义

一组对外的行为规范。要求其实现类必须遵循。只关注行为,不关注数据,且不关注行为的实现。其中:

一组:接口中可以包含多个方法。

对外:接口成员是要求子类实现的。

行为:接口中只包含方法成员包括:属性,方法。

规范:接口中的成员是要求子类必须实现的,

抽象选择策略

普通类,抽象类,接口,委托

当抽象与子类之间的关系是一种 is a 的关系,抽象使用普通类/抽象类。

  1. 存在抽象方法使用抽象类,否则使用不同类。

当抽象与子类之间的关系是一种 can do 的功能关系,抽象使用接口/委托。

  1. 只有一个行为使用委托,否则使用接口。

类做父类主要突出类别一致,接口做父类主要突出有相同的能力,功能,行为。

C#中表达抽象的三种形式:1. 抽象类 2. 接口 3. 委托(委托传递方法引用,属于动态绑定)代码写活,意味着代码充满了抽象代码。

接口的优势

类与类只能单继承,类与接口可以多实现,接口与接口可以多继承。做依赖倒置推荐优先使用接口

接口的作用

  1. 规范不同类型的行为,打倒了不同类型在行为上是一致的。
  2. 拓展一个已有类的功能。

显式实现接口

当实现类继承了多个接口,这些接口中可能存在相同名字不同用途的行为。这时在实现类中就要使用显式实现接口的方式来逐个实现避免歧义。

注:如果不使用显示实现接口则该行为会被认为是两个接口的方法。

显式实现接口的调用

显式实现接口需要用父类引用指子类对象的形式进行调用:接口 变量名 = new 实现类名();当然一般接口也可以这么调用。显式实现接口默认为private。

显式实现接口的作用
  1. 解决多接口实现时的二义性。
  2. 解决接口中的成员对实现类不适用的问题。(接口中的部分方法可能实现类并不需要使用,所以在实现类中屏蔽这些方法就用到了显示实现接口)

接口使用技巧

对于参数列表是Object的接口,实现时应当将Object类型强转成想要的类型。

常用接口

Array.Sort方法使用的接口:IComparable使类型支持比大小的功能;IComparer提供比较大小的方法,常用于排序比较。

foreach实现使用的接口:IEnumerable,IEnumerator迭代器

自定义使用Array.Sort

实现IComparable来使用常用排序方法,实现IComparer<>来使用不常用的排序方法。

Array.Sort通用类图:
在这里插入图片描述

foreach的原理
  1. 获得迭代器,此步需要实现IEnumerable,使用GetEnumerator方法获得。
  2. GetEnumerator返回值类型为IEnumerator所以需要实现IEnumerator并返回。
  3. foreach的核心方法MoveNext用于改变索引值。

迭代器的优势:可以使用一种方式,顺序访问集合类型元素。

但是写迭代器非常繁琐,所以C#为我们提供了语法糖yield。

yield迭代器

yield迭代器可以自动生成IEnumerator接口的实现类方法。

yield将一个方法分成了多个部分,每个部分以yield return结束。简单来说yield可以切割循环体。

C#里,每次调用MoveNext(),走到下一个yield处 ( 从第一个开始,先走第一个 ) ,然后Current值就是yield return后面的值,遇到yield再停住,直到再次调用;如果没有找到下一个yield则MoveNext()返回false。

除了yield return, 还有yield break。yield break的作用是停止循环,yield break之后的语句,不会被执行!

unity协程

在实际使用时协程可以理解为又开了一个“线程“,主程序不会因为协程而等待。进入协程后碰见yield return就会返回上一层,直到条件满足后协程调用MoveNext进入下一个yield。

定义

具有多个返回点(yield),可以在特定时机分步执行的函数。

原理

Unity每帧处理GmaeObject中的协同函数,直到函数执行完毕。(unity中的协程执行在于游戏对象而不是脚本。)

当一个协同函数启动时,本质是创建了一个迭代器对象并传给协程方法;协程调用MoveNext方法,每当执行到yield暂时退出,待满足条件(return 后跟下一次调用MoveNext的条件)后再次调用MoveNext方法,执行后续代码(此时的代码已属于下一个方法块),直到遇到下一个yield为止,如此循环直到整个函数结束。

unity yield return

可以被yield return的对象:

  1. null或者数字:在Update后执行,适合分解耗时的逻辑处理。
  2. WaitForFixedUpdate:在FixedUpdate后执行(脚本生命周期),适合分解物理操作。
  3. WaitForSeconds:在指定时间后执行,适合延迟调用。
  4. WaitForSecondsRealtime:同上,但是不受时间缩放影响。
  5. WaitForEndOfFrame:在每帧结束后调用,适合做相机的跟随操作。
  6. Corotine:在另一个协程执行完毕后在执行,协程嵌套可以实现for循环嵌套的效果。
  7. WaitUntil:在委托返回true时执行,适合等待某一操作。
  8. WaitWhile:在委托返回false时执行,适合等待某一操作。
  9. WWW:在请求结束后执行,适合加载数据,如文件,贴图,材质等。

unity协程就是一个不断调用迭代器MoveNext方法的函数。

unity协程作用

  1. 延时调用
  2. 分解操作

对于分解操作,将yield return添加到需要分解的循环内部。

对于分解操作,Update分解和协程如何选择:

Update适合贯穿游戏周期的分解操作,协程适合当满足条件后一段时间的分解操作。

反射

C#三大技术:泛型,委托,反射

定义

动态获取类型信息,动态创建对象,动态访问成员的过程。

作用

在编译时无法了解类型,在运行时获取类型信息,创建对象,访问成员。

流程

  1. 得到数据类型Type。下面方法返回类型均为Type
    1. 方式一:Type.GetType(“类型全名”);
      1. 适合于类型的名称已知,在框架设计时常用这种方式。要求写类型全名,指的形式是命名空间.数据类型
    2. 方式二:obj.GetType();
      1. 适合于类型名未知,类型未知,存在已有对象。
    3. 方式三:typeof(类型);
      1. 适合于已知类型。
  2. 动态创建对象。使用Activator类的方法。
    1. Activator.CreateInstance(Type type);
    2. Activator.CreateInstance(string 程序集名称,string类型全名);
  3. 查看类型信息。
    1. 使用Type类常用实例方法Get系列方法。
      1. GetMethod系列返回基类型:MethodInfo,重要方法Invoke。
      2. GetProperty系列返回基类型:PropertyInfo,重要方法SetValue,GetValue。
      3. GetField系列返回基类型:FieldInfo,重要方法SetValue,GetValue。
      4. GetConstruct系列返回基类型:ConstructInfo,重要方法Invoke。

注:

这里的类型指的是数据类型的Type

XXXInfo是Get方法/属性/字段/构造后对应的返回类型。

例子

Type type = Type.GetType(Console.ReadLine());
//获取数据类型的Type
Object targetObject = Activator.CreateInstance(type);
//根据Type创建对应的对象
PropertyInfo targetProperty1 = type.GetPorperty("ID");
//通过Type实例获得指定属性信息类实例
Object tempValue = Convert.ChangeType("1001",targetPorperty1.PorpertyType);
//使用万能数值转换来转换想要赋值给属性的数值。
targetPorperty1.SetValue(targetObject,tempValue);
//属性赋值
//再调用方法参数的Invoke方法如果方法本身没有参数,则Invoke第二个参数填写null

ChangeType(要转换的数据,转换成的类型);类型使用PropertyInfo的PropertyType动态获取。

通用的赋值设置流程为

获取属性。

类型转换。

Convert.ChangeType俗称C#中的万能转换。

设置属性。

优点

动态创建对象,反射是框架构建时必不可少的一部分。

缺点

性能极差。

unity实用小技巧

AnimationCurve + Lerp方法可以实现自定义插值变化。其中变化曲线由AnimationCurve提供。插值运算由Lerp提供。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值