浅墨知乎阅读笔记——12.20
P1
命令模式是回调机制的面向对象版本。帧同步中Presentation可以通过接口来实现,也能通过类型加回调来实现。
Input->Serialize->Network->Deserlize->Application
最基本的处理输入的写法 if(Input.GetKeyDown(KeyCode.A)) DoSomething();这种写法的耦合比较重,且不太好处理联机的情况。
如果改成if (Input.GetKeyDown(KeyCode.A)) PushCommand(Command.DoSomething)
Logic->if (cmd == Command.DoSomething) DoSomething(); 这样逻辑能统一处理,不需要在command处理时增加太多的
判断条件(这可能会导致bug的出现),并且command能经过网络中转。
其实命令模式逻辑比较直接,帧同步用的就是这套处理方法。
https://zhuanlan.zhihu.com/p/22620827
状态模式,有限状态机
状态机是比较熟悉的设计,能够比较好的处理多状态的情况,但是状态多并且判断条件比较多的情况下就会显得比较乱。
之前使用的结构都是
switch(state)
{
case STATE.A:
DoA();
break;
case STATE.B:
DoB();
break;
}
这样所有逻辑写在一起,状态一多就会很乱。
P2
namespace StateMachine
{
public class Hero
{
public HeroState State;
public Hero()
{
State = Pool<HeroState_Run>.Allocate();
}
public static void Command(ref Hero hero, INPUT input)
{
hero.State?.OnCommand(ref hero, input);
}
public static void Update(ref Hero hero)
{
hero.State?.Update(ref hero);
}
}
public enum INPUT
{
NONE,
UP,
DOWN
}
public abstract class PooledObject
{
public virtual void OnAllocated()
{
}
public virtual void OnReleased()
{
}
}
public static class Pool<T> where T : HeroState, new()
{
private static List<T> m_lt = new List<T>();
public static T Allocate()
{
if (m_lt.Count == 0)
{
Console.WriteLine($"{typeof(T)} created");
var v = new T();
v.OnAllocated();
return new T();
}
else
{
var v = m_lt[m_lt.Count - 1];
v.OnAllocated();
m_lt.RemoveAt(m_lt.Count - 1);
return v;
}
}
public static void Release(T t)
{
t.OnReleased();
m_lt.Add(t);
}
}
public abstract class HeroState : PooledObject
{
public abstract void OnCommand(ref Hero hero, INPUT input);
public abstract void Update(ref Hero hero);
}
public class HeroState_Swim : HeroState
{
private int Count = 0;
public HeroState_Swim()
{
Count = 0;
}
public override void OnCommand(ref Hero hero, INPUT input)
{
if (input == INPUT.UP)
{
HeroState_Run stateRun = Pool<HeroState_Run>.Allocate();
hero.State = stateRun;
Console.WriteLine("Up, got to run");
Pool<HeroState_Swim>.Release(this);
}
}
public override void Update(ref Hero hero)
{
Count++;
if (Count == 5)
{
HeroState_Run stateRun = Pool<HeroState_Run>.Allocate();
hero.State = stateRun;
Console.WriteLine("swim too long ,go to run");
Pool<HeroState_Swim>.Release(this);
}
}
public override void OnReleased()
{
Count = 0;
}
}
public class HeroState_Run : HeroState
{
public override void OnCommand(ref Hero hero, INPUT input)
{
if (input == INPUT.DOWN)
{
HeroState_Swim stateSwim = Pool<HeroState_Swim>.Allocate();
hero.State = stateSwim;
Console.WriteLine("Down, got to swim");
Pool<HeroState_Run>.Release(this);
}
else if (input == INPUT.UP)
{
HeroState_Fly stateFly = Pool<HeroState_Fly>.Allocate();
hero.State = stateFly;
Console.WriteLine("Up, got to fly");
Pool<HeroState_Run>.Release(this);
}
}
public override void Update(ref Hero hero)
{
}
}
public class HeroState_Fly : HeroState
{
private int Count;
public HeroState_Fly()
{
Count = 0;
}
public override void OnCommand(ref Hero hero, INPUT input)
{
if (input == INPUT.DOWN)
{
HeroState_Run stateRun = new HeroState_Run();
hero.State = stateRun;
Console.WriteLine("Down, got to run");
Pool<HeroState_Fly>.Release(this);
}
}
public override void Update(ref Hero hero)
{
Count++;
if (Count == 5)
{
HeroState_Run stateRun = new HeroState_Run();
hero.State = stateRun;
Console.WriteLine("Fly too long ,go to run");
Pool<HeroState_Fly>.Release(this);
}
}
public override void OnReleased()
{
Count = 0;
}
}
}
上面这个demo提供了状态机的另外一种处理方法,将状态提炼为对象,这样一些状态内的逻辑就能更好地封装,复杂情况下用这种还是能提高代码的可读性。
其中还写了个Pool去处理状态对象,毕竟游戏中gc还是个挺大的影响因素。
https://zhuanlan.zhihu.com/p/22976065
P3
提高unity中C#代码质量的22条准则
原则1:尽可能使用属性,而不是直接访问数据成员
private int _data;
public int Data {
get => _data;
set => _data = value;
}
属性增加多线程的支持比较方便。属性可定义为virtual,属性可定义为abstract,可以使用泛型类型的属性接口,属性可以定义为接口。属性可控制类成员可见性。
其实吧,就相当于get,set两个函数。
原则2:偏向于使用运行时常量而不是编译时常量
运行时常量readonly 编译时常量const
编译时常量会被目标代码中的值直接取代,仅用于数值和字符串,一个dll引用另一个dll的const,如果另一个修改了,这边是不知道的
运行时常量可以为任意类型,必须在构造函数或者初始化器中初始化。
原则3:推荐使用is或as操作符而不是强制类型转换
is:检查一个对象是否兼容于其它指定的类型,并返回一个bool值,永远不会抛出异常。
as: 作用和强制类型转换一样,永远不会抛出一场,即使转换不成功,也会抛出null。
as对于结构体永远返回null,可使用is搭配强制类型转换使用。
原则4:推荐使用条件属性而不是#if条件编译
[Conditional("Test")] 使用Attribute
原则5:理解等同性判断之前的关系
如果两个引用类型的变量指向同一个变量,即引用相等
如果两个值类型的变量类型相同并且包含相同的内容,即值相等
public static bool ReferenceEquals (object left, object right) 判断对象标识而不是内容
public static bool Equals (object left, object right); 两个变量运行时是否相等,GetHashCode()
public virtual bool Equals(object right); 用于重载
public static bool operator ==(MyClass left, MyClass right); 用于重载
值类型总是覆写上面两个函数,引用类型看情况。
原则6:了解GetHashCode()的一些坑
实现自己的GetHashCode()时,要遵循一下三条原则
如果两个都想相等,那么他们必须生成相同的散列码,否则,这样的散列码就不能用来查找容器中的对象。
对于任何一个对象A,A.GetHashCode()必须保持不变。
对于所有的输入,散列函数应该在所有整数中按随机分别生成散列码,这样散列容易才能得到足够的效率提升。
原则7:理解短小方法的优势
将C#代码翻译成可执行的机器码需要两个步骤。C#编译器将生成IL,并放在程序集中。随后,
JIT将根据需要逐一为方法(或是一组方法,如果涉及内联)生成机器码。短小的方法让JIT编译器能够更好地平摊编译的代价。
短小的方法也更适合内联。
原则8:选择变量初始化而不是赋值语句
字面意思
原则9:正确地初始化静态成员变量
class SimpleClass
{
// Static variable that must be initialized at run time.
static readonly long baseline;
// Static constructor is called at most one time, before any
// instance constructor is invoked or member is accessed.
static SimpleClass()
{
baseline = DateTime.Now.Ticks;
}
}
静态构造函数不能有函数,没有访问修饰符,一个类只能有一个,由CLR调用,用户无法控制。
静态构造函数用于初始化任何静态数据,或执行仅需执行一次的特定操作。
将在创建第一个实例或引用任何静态成员之前自动调用静态构造函数。
原则10:使用构造函数链(减少重复的初始花逻辑)
这是C#里的最后一个关于对象构造的原则,是时候复习一下,一个类型在构造时的整个事件顺序了。你须
要同时 明白一个对象 的操作顺序和默认的预置方法的顺序。你构造过程中,你应该努力使所有的成员变量
只精确的初始化一次。最好的完成这个 目标的方法就是尽快的完成变量的初始化 。这是某个类型第一次构
造一个实例时的顺序:
1、静态变量存储位置0 。
2 、静态变量预置方法执行 。
3、基类 的静态构造函数执行 。
4 、静态构造函数执行 。
5、实例变量存储位置0 。
6、实例变量预置方法执行 。
7、恰当 的基类实例构造函数执行 。
8、实例构造函数执行 。
后续的同样类型的实例从第5步开始,因为类 的预置方法只执行一次。同样,第6和第7步是优化了的,它可
以让编译器在构造函数预置方法上移除重复的指令 。
C# 的编译器保证所有的事物在初始化使用同样的方法来生成。至少,你应该保证在你的类型创建时,对象
占用的所有内存是 已经置0 的。对静态成员和实例成员都是一样的。你的 目标就是确保你希望执行的初始化
代码只执行一次。使用预置方法来初始化简单的资源,使用构造函数来初始化一些具有复杂逻辑结构的成
员。同样,为了减少重复尽可能的组织调用其它的构造函数
原则11:实现标准的销毁模式
降低GC。协程,linq表达式,pool,静态变量,stringbuilder,拆箱装箱,常用的局部变量转成成员变量。
原则12:区分值类型和引用类型
值类型只是数据。引用类型更常见。
原则13:保证0为值类型的有效状态
创建自定义枚举时,保证0是一个有效的选项。比如NONE。
原则14:保证值类型的常量性和原子性
原则15:限制类型的可见性
原则16:通过定义并实现接口替代继承
原则17: 理解接口方法和虚方法的区别
原则18: 用委托实现回调
Acton<> Predicate<> Func<>
如果申明event,则只能在包含这个回调的类中触发。
原则19:用事件模式实现通知
原则20:避免返回内部类对象的引用
内部数据容易被篡改
原则21:仅用new修饰符处理基类更新
new操作符修饰类成员可以重新定义继承自基类的非虚成员。
new操作符修饰能解决基类和派生类方法冲突。
new操作符要小心使用,容易造成二义性。
原则22:尽量减少在共有api中使用动态对象。
https://zhuanlan.zhihu.com/p/24553860