面向对象 Object Oriented
概述
面向过程的程序 = 算法 + 数据结构; 关心解决问题的步骤。
面向对象的程序 = 对象 + 交互; 关心谁在解决问题。
类:一个抽象的概念,即为生活中的”类别”。
对象:类的具体实例,即归属于某个类别的”个体”。
同类型的多个对象,行为相同,数据不同。
主要思想
分而治之
— 将一个大的需求分解为许多类,每个类处理一个独立的模块。
拆分好处:独立模块便于分工,每个模块便于复用,可扩展性强。
封装变化
— 变化的地方独立封装,避免影响其他模块。
高 内 聚
— 类中各个方法都在完成一项任务(单一职责的类)。
复杂的实现封装在内部,对外提供简单的调用。
低 耦 合
— 类与类的关联性依赖度要低(每个类独立)。
让一个模块的改变,尽少影响其他模块。
[例如:硬件高度集成化,又要可插拔]
最高的内聚莫过于类中仅包含1个方法,将会导致高内聚高耦合。
最低的耦合莫过于类中包含所有方法,将会导致低耦合低内聚。
优势
高复用性、高扩展性、高维护性、高移植性。
三大特性
封装
数据角度讲,将一些基本数据类型复合成一个自定义类型。
方法角度讲,向类外提供功能,隐藏实现的细节。
设计角度讲,分而治之,高内聚低耦合,封装变化。
继承
统一概念。
重用现有类的功能,在此基础上进行扩展
多态
父类的同一种动作或者行为,在不同的子类上有不同的实现。
(父类调用同一方法,在不同的子类上有不同的执行效果)
实现手段:虚方法、抽象方法、接口方法。
类的四大关系
泛化:子类与父类的关系,概念的复用,耦合度最高;
B类泛化A类,意味B类是A类的一种;
做法:B类继承A类
实现:抽象行为的具体实现,两者体现功能的关系,变化只影响行为;
A类实现B类,意味A类必须具体实现B类中所有抽象成员。
做法:实现抽象类、接口中的抽象成员。
关联(聚合/组合):部分与整体的关系,功能的复用,变化影响一个类;
A与B关联,意味着B是A的一部分;耦合度低
做法:在A类中包含B类型成员。
依赖:合作关系,一种相对松散的协作,变化影响一个方法;
A类依赖B类,意味A类的某些功能靠B类实现;
做法:B类型作为A类中方法的参数,并不是A的成员。
设计的八大原则
开-闭原则(目标、总的指导思想)
Open Closed Principle
对扩展开放,对修改关闭。
增加新功能,不改变原有代码。
类的单一职责(一个类的定义)
Single Responsibility Principle
一个类有且只有一个改变它的原因。
适用于基础类,不适用基于基础类构建复杂的聚合类。
依赖倒置(依赖抽象)
Dependency Inversion Principle
客户端代码(调用的类)尽量依赖(使用)抽象的组件。
依赖父
抽象的是稳定的。实现是多变的。
组合复用原则(复用的最佳实践)
Composite Reuse Principle
如果仅仅为了代码复用优先选择组合复用
,而非继承复用。
组合的耦合性相对继承低。
里氏替换(继承后的重写,指导继承的设计)
Liskov Substitution Principle
父类出现的地方可以被子类替换,在替换后依然保持原功能
。
子类要拥有父类的所有功能。
子类在重写父类方法时,尽量选择扩展重写,防止改变了功能(即先调用一下父类方法)。
接口隔离(功能拆分)
Interface Segregation Principle
尽量定义小而精的接口interface
,少定义大而全的接口。本质与单一职责相同。
小接口之间功能隔离,实现类需要多个功能时可以选择多实现.或接口之间做继承。
面向接口编程而非面向实现(切换、并行开发)
客户端通过一系列抽象操作实例,而无需关注具体类型。
便于灵活切换一系列功能。
实现软件的并行开发。
迪米特法则(类与类交互的原则)
Law of Demeter
不要和陌生人说话。
类与类交互时,在满足功能要求的基础上,传递的数据量越少越好。
因为这样可能降低耦合度。
使用接口,接口屏蔽
封装
定义
- 数据角度讲,将一些基本数据类型复合成一个自定义类型(符合人的思维,便于操作数据)。
- 方法角度讲,向类外提供功能,隐藏实现的细节。
- 设计角度讲,分而治之,高内聚低耦合,封装
变化
。 `
作用
松散耦合
,降低了程序各部分之间的依赖性。简化编程
,使用者不必了解具体的实现细节,只需要调用对外提供的功能。增强安全性
,以特定的访问权限来使用类成员,保护成员不被意外修改,合理利用访问权限。
访问修饰符
- private:私有的,类成员默认级别,仅在类内部可见。
- internal:内部的,类默认级别,仅在程序集.dll内可见。
- protected:受保护的,类内部与派生类中可见。
- protected internal:意为 internal 或 protected; 程序集内或者派生类中可见(满足其一)
- public:公有的,类内类外都可见。
案例
需求
1.玩家可以通过键盘或者摇杆控制主角运动。
2.运动过程中播放相应的动画。
3.玩家控制主角打怪,怪受伤,可能死亡。
分析
角色系统,成长系统,技能系统,动画系统,运动系统,任务系统,背包系统,结算系统。
1.识别对象:主角,小怪,输入控制,动画。
2.分配职责:
主角:存储状态(攻击力,攻击速度,生命,魔法),受伤,死亡。
小怪:存储状态(攻击力,攻击速度,生命,魔法),受伤,死亡。
马达:移动, 旋转。
输入控制:控制移动,控制攻击。
动画系统:管理动画片段,提供动画事件。,
3.建立交互:
移动: 检测到玩家开始移动调用动画系统播动画调用马达的移动方法检测到玩家松开按钮结束触摸调用动画系统取消动画。
打怪: 按下技能按钮调用技能系统释放技能调用动画系统播动画处理动画事件
调用小怪的受伤可能调用死亡方法。
设计
运动系统
角色马达:CharacterMotor
数据:移动速度 moveSpeed,转向速度 rotationSpeed,角色控制器(chController)
行为:移动(Movement),转向(LookAtTarget)
动画系统
角色动画参数类:CharacterAnimationParameter
数据:动画片段
*动画事件行为:AnimationEventBehaviour
数据:动画组件(anim)
行为: 攻击时使用(OnAttack),撤销动画播放(OnCancelAnim)
角色系统
主角状态:PlayerStatus
数据:生命(HP,maxHP),魔法(SP,maxSP),基础攻击力(baseATK),防御(defence),攻击间隔 (attackInterval),攻击距离(attackDistance),动画参数(animParams)
行为:受击(Damage) 死亡(Dead)
小怪状态:MonsterStatus
数据:生命(HP,maxHP),魔法(SP,maxSP),基础攻击力(baseATK),防御(defence),攻击间隔 (attackInterval),攻击距离(attackDistance),动画参数(animParams)
行为:受击(Damage),死亡(Dead)
摇杆输入控制:CharacterInputController
数据:马达(chMotor), (EasyTouch插件)
行为:摇杆移动执行的方法,摇杆停止时执行的方法
继承 extends
定义
重用现有类的功能,在此基础上进行扩展(功能和概念扩展)。
优点
- 复用代码的一种方式。
- 统一概念,以层次化的方式管理类。
缺点
耦合度高
父类改变,无需通知子类
适用性
- 多个类具有相同的数据或行为。
- 多个类从概念上是一致的,且需要进行统一处理。
语法
class A: B
{
}
1.表示A类继承B类,A类称为子类(派生类),B类称为父类(基类,超类)
2.通过this关键字访问本类成员、通过base关键字访问父类成员。
3.一个类最多只能继承另一个类。
抽象类
语法
用abstract修饰类即为抽象类
- 抽象类中可能包含抽象成员(方法,属性)
- 抽象类不能创建对象
- 抽象类可以包含字段、属性、方法、构造函数等
语义
表示一个概念的抽象(可以存储子类直接使用的成员)
只表示做什么,拥有什么数据,但往往不表达具体做法。
适用性
- 当有行为,但是不需要实现的时候。
- 当有一些行为,在做法上有多种可能时,但又不希望客户了解具体做法。
- 不希望类创建时,无法产生对象。
抽象类与普通类区别
相同:都可以有静态、实例成员(数据、方法、构造函数)
不同:抽象类使用abstract修饰,可能有抽象方法,不能直接创建对象
抽象方法
语法
- 用abstract修饰并且没有实现的方法.只有方法声明,没有实现,没有方法体。
- 抽象方法只能出现在抽象类中。
- 抽象方法在本类中不实现,实现推迟到子类中,子类必须
重写override
实现。
语义
描述做什么,不描述怎么做。
一个行为的抽象。
抽象工厂模式
解决多种数据存储DAO
脚本不涉及数据存储方式
实现软件并行方式
多态
定义
父类同一种动作或者行为(父类型的引用调用同一方法),在不同的子类上有不同的实现。
继承将相关概念的共性进行抽象,并提供了一种复用的方式;多态在共性的基础上,体现类型及行为的个性化,即一个行为有多个不同的实现。
父类调用子类
override(运行时)
1.在子类方法表中添加新记录
2.修改父类方法表的地址
实现手段
1.虚方法: 父类型的引用 指向 子类的对象,调用虚方法,执行子类中的重写方法
。
2.抽象方法:抽象类的引用 指向 实现类的对象,调用抽象方法,执行实现类中重写方法
。
3.接口:接口的引用 指向 实现类的对象,调用接口方法,执行实现类中重新方法
。
方法隐藏
定义:在子类中使用new关键字
修饰的与父类同签名的方法。(隐藏覆盖掉父类方法)
作用:通过子类引用调用时,覆盖继承而来但不适用的旧方法,执行子类的新方法。
可以通过base.
调用父类方法,
隐藏原理
子类在自己的方法表中增加一个新地址。
- 通过
子类
引用调用时使用新纪录
,执行子类
中新方法; - 通过
父类
引用调用时使用旧纪录
,执行父类
中方法。
解决脚本生命周期冲突:方法隐藏,在方法体内部通过base关键字调用父类方法
虚方法
定义:用vritual关键修饰的已实现方法。
作用:可以在子类中重写的方法,从而实现调用父类执行子类的效果。
方法重写
语法:在子类中使用override关键字修饰的方法。
作用:父类的方法在子类中不适用(虚方法),或父类没有实现(抽象方法)。子类重写可以满足对该方法的不同需求。方法重写时必须在方法前加override关键字。
三种方法可以重写:
abstract 方法在子类必须重写,除非子类也是抽象类。
virtual 方法在子类可以重写,父类方法的做法与子类不同。
override方法,已经重写过的方法,在子类还可以继续重写,除非被标识为sealed。
重写原理
子类在方法表中修改对应的地址。
所以不管通过父类还是子类型的引用,调用方法时,都执行对象真实类型中定义的方法。
动态绑定(晚期绑定)与静态绑定(早期绑定)
绑定:类型与关联的方法的调用关系,通俗Q讲就是一个类型能够调用哪些方法。
静态绑定:是指调用关系是在运行之前确定的,即编译期间
。
动态绑定:是指调用关系是在运行期间
确定的。
静态绑定因为在编译期确定,不占用运行时间,所以调用速度比动态绑定要快。
动态绑定因为在运行期确定,占用运行时间,但是更灵活。
方法重写是动态绑定。
方法隐藏是静态绑定。
密封 Sealed
1.用在类的定义上,指示当前类不能做父类,也就是任何类都不可继承当前类
2.用在重写的成员,指示当前类的子类,不能再次重写该成员
接口
定义
一组对外的行为规范,要求它的实现类必须遵循。
只关注行为,不关注数据,且不关注行为的实现,实现由实现类完成。
一组行为的抽象。
作用
不同类型的行为,达到了不同类型在行为上是一致的。
扩展一个已有类的行为。
语法
使用interface
关键词定义。接口名建议用”I”开头,其后单词首字母大写。
- 接口中
不能包含字段
,可以包含:方法,属性,索引器,事件。 - 接口中的所有成员不能有实现,全部默认抽象的。
- 实现类实现接口用“:”与继承相同。
- 实现类必须自行实现
- 类与类是单继承,类与接口是多实现,接口与接口是多继承。
- 结构(struct)可以实现接口,但不能被继承。
抽象类与接口的选择策略
抽象类与子类之间关系:is a [是一种]
接口与实现类之间关系:can do [能够做(功能)]
接口的显式实现
作用:
1.解决多接口实现时的二义性
2.解决接口中的成员对实现类不适用的问题
屏蔽多接口冲突,使用接口显示实现
做法:
在实现的成员前加接口名,并且不能加任何访问修饰符,默认为private
显式实现成员只能通过接口类型的引用调用。
void InterFace1.Fun()
{ }
通过接口去调用
Framework常用接口
IComparable 可比较,使类型支持比大小的功能
IComparer 比较器,提供比较的方法,常用于排序比较
IEnumerable 可枚举,使类型支持简单迭代(foreach)
IEnumerator 枚举器,支持MoveNext ,自己可以控制迭代的节奏
Unity协同程序(Coroutine)
定义
具有多个返回点(yield),可以在特定时机分部执行的函数。
原理
Unity 每帧处理GameObject 中的协同函数,直到函数执行完毕。
当一个协同函数启动时, 本质创建迭代器对象;调用MoveNext方法,执行到yield暂时退出;待满足条件后再次调用MoveNext方法,执行后续代码, 直至遇到下一个yield为止,如此循环至整个函数结束。
语法
通过MonoBehaviour中的StartCoroutine方法启动,StopCoroutine方法停止。
协程函数返回值类型为IEnumerator,方法体中通过yield关键字定义返回点,通过return XX对象定义继续执行的条件。
`ZZerr可以被yield return的对象:
- null或数字 – 在Update后执行,适合分解耗时的逻辑处理。
- WaitForFixedUpdate – 在FixedUpdate后执行,适合分解物理操作。
- WaitForSeconds – 在指定时间后执行,适合延迟调用。
- WaitForSecondsRealtime – 同上,不受时间缩放影响。
- .WaitForEndOfFrame – 在每帧结束后执行,适合相机的跟随操作。
- .Coroutine – 在另一个协程执行完毕后再执行。
- .WaitUntil – 在委托返回true时执行,适合等待某一操作。
- WaitWhile – 在委托返回false时执行,适合等待某一操作。
- .WWW – 在请求结束后执行,适合加载数据,如文件、贴图、材质等。
作用
延时调用
分解操作
常用脚本生命周期
反射
ArrayHelp类非常重要
定义
动态
获取类型信息,
动态
创建对象,
动态
访问成员的过程。
动态=运行时
作用
在编译时无法了解类型,在运行时获取类型信息,创建对象,访问成员。
反射性能差,不能频繁使用反射
98QAP;DSSSS
流程
1.得到数据类型
2.动态创建对象
3.查看类型信息(了解本身信息,成员信息)
常用类
- 取得数据类型Type
方式一:Type.GetType(“类型全名”);
适合于类型的名称已知
方式二:obj.GetType();
适合于类型名未知,类型未知,存在已有对象
方式三:typeof(类型)
适合于已知类型
方式四:Assembly.Load(“XXX”).GetType(“名字”);
适合于类型在另一个程序集中
访问
Type类常用Get系列方法 Is系列属性。
-
MethodInfo(方法)
重要方法: Invoke -
PropertyInfo(属性)
重要方法:SetValue GetValue -
FieldInfo(字段)
重要方法:SetValue GetValue -
ConstructInfo(构造方法)
重要方法:Invoke
动态创建对象
Activator.CreateInstance(string 程序集名称,string 类型全名)
Activator.CreateInstance(Type type);
Assembly assembly = Assembly.Load(程序集);
assembly.CreateInstance(Type);
//找到有参构造方法,动态调用构造方法
type.GetConstructor(typeof(string)).Invoke()