设计模式的原则

设计模式之间的关系:
设计模式之间的关系

1、开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类。在开闭原则的定义中,软件实体可以指一个软件模块、一个由多个类组成的局部结构或一个独立的类。
当软件系统需要面对新的需求时,我们应该尽量保证系统的设计框架是稳定的。如果一个软件设计符合开闭原则,那么可以非常方便地对系统进行扩展,而且在扩展时无须修改现有代码,使得软件系统在拥有适应性和灵活性的同时具备较好的稳定性和延续性。随着软件规模越来越大,软件寿命越来越长,软件维护成本越来越高,设计满足开闭原则的软件系统也变得越来越重要。
为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。在Java、C#等编程语言中,可以为系统定义一个相对稳定的抽象层,而将不同的实现行为移至具体的实现层中完成。在很多面向对象编程语言中都提供了接口、抽象类等机制,可以通过它们定义系统的抽象层,再通过具体类来进行扩展。如果需要修改系统的行为,无须对抽象层进行任何改动,只需要增加新的具体类来实现新的业务功能即可,实现在不修改已有代码的基础上扩展系统的功能,达到开闭原则的要求。

2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
在使用里氏代换原则时需要注意如下几个问题:
(1)子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。
(2) 我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。

3、依赖倒置原则(Dependence Inversion Principle)
抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。
在引入抽象层后,系统将具有很好的灵活性,在程序中尽量使用抽象层进行编程,而将具体类写在配置文件中,这样一来,如果系统行为发生变化,只需要对抽象层进行扩展,并修改配置文件,而无须修改原有系统的源代码,在不修改的情况下来扩展系统的功能,满足开闭原则的要求。
在实现依赖倒转原则时,我们需要针对抽象层编程,而将具体类的对象通过依赖注入(DependencyInjection, DI)的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式有三种,分别是:构造注入,设值注入(Setter注入)和接口注入。构造注入是指通过构造函数来传入具体类的对象,设值注入是指通过Setter方法来传入具体类的对象,而接口注入是指通过在接口中声明的业务方法来传入具体类的对象。这些方法在定义时使用的是抽象类型,在运行时再传入具体类型的对象,由子类对象来覆盖父类对象。

4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
根据接口隔离原则,当一个接口太大时,我们需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。这里的“接口”往往有两种不同的含义:一种是指一个类型所具有的方法特征的集合,仅仅是一种逻辑上的抽象;另外一种是指某种语言具体的“接口”定义,有严格的定义和结构,比如Java语言中的interface。对于这两种不同的含义,ISP的表达方式以及含义都有所不同:
(1) 当把“接口”理解成一个类型所提供的所有方法特征的集合的时候,这就是一种逻辑上的概念,接口的划分将直接带来类型的划分。可以把接口理解成角色,一个接口只能代表一个角色,每个角色都有它特定的一个接口,此时,这个原则可以叫做“角色隔离原则”。
(2) 如果把“接口”理解成狭义的特定语言的接口,那么ISP表达的意思是指接口仅仅提供客户端需要的行为,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口。在面向对象编程语言中,实现一个接口就需要实现该接口中定义的所有方法,因此大的总接口使用起来不一定很方便,为了使接口的职责单一,需要将大接口中的方法根据其职责不同分别放在不同的小接口中,以确保每个接口使用起来都较为方便,并都承担某一单一角色。接口应该尽量细化,同时接口中的方法应该尽量少,每个接口中只包含一个客户端(如子模块或业务逻辑类)所需的方法即可,这种机制也称为“定制服务”,即为不同的客户端提供宽窄不同的接口。

5、迪米特法则,又称最少知道原则(Demeter Principle)
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
如果一个系统符合迪米特法则,那么当其中某一个模块发生修改时,就会尽量少地影响其他模块,扩展会相对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。
迪米特法则还有几种定义形式,包括:不要和“陌生人”说话、只与你的直接朋友通信等,在迪米特法则中,对于一个对象,其朋友包括以下几类:
(1) 当前对象本身(this);
(2) 以参数形式传入到当前对象方法中的对象;
(3) 当前对象的成员对象;
(4) 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友;
(5) 当前对象所创建的对象。
任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”,否则就是“陌生人”。在应用迪米特法则时,一个对象只能与直接朋友发生交互,不要与“陌生人”发生直接交互,这样做可以降低系统的耦合度,一个对象的改变不会给太多其他对象带来影响。
迪米特法则要求我们在设计系统时,应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。
在将迪米特法则运用到系统设计中时,要注意下面的几点:在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及;在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限;在类的设计上,只要有可能,一个类型应当设计成不变类;在对其他类的引用上,一个对象对其他对象的引用应当降到最低。

6、合成复用原则(Composite Reuse Principle)
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。

7.单一职责原则:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。

Shader光照模型

光照模型(illumination model),也称为明暗模型,用于计算物体某点处的 光强(颜色值)。从算法理论基础而言,光照模型分为两类:一种是基于物理理 论的,另一种是基于经验模型的。基于物理理论的光照模型,偏重于使用物理的 度量和统计方法,比较典型的有ward BRDF模型,其中的不少参数是需要仪器测 量的,使用这种光照模型的好处是“效果非常真实”,但是“计算复杂,实现起来 也较为困难”;经验模型更加偏重于使用特定的概率公式,使之与一组表面类型 相匹配,所以经验模型大都比较简洁,效果偏向理想化。从使用角度而言,光照模型分为局部光照模型和全局光照模型。所谓局部光 照模型,是将光照的种类进行分解,在计算时只考虑其中的一种;而全局光照模 型则是考虑到所有的光照种类。
当光照射到物体表面时,一部分被物体表面吸收,另一部分被反射,对于透 明物体而言,还有一部分光穿过透明体,产生透射光。被物体吸收的光能转化为 热量,只有反射光和透射光能够进入眼睛,产生视觉效果。通过反射和透射产生 的光波(光具有波粒二相性)决定了物体呈现的亮度和颜色,即反射和投射光的 强度决定了物体表面的亮度,而它们含有的不同波长光的比例决定了物体表面的 色彩。 所以,物体表面光照颜色由入射光、物体材质,以及材质和光的交互规律共同决定。
环境光(Ambient Light):从物体表面所产生的反射光的统一照明,称为环境光 或背景光。例如房间里面并没有受到灯光或者太 阳光的直接照射,而是由墙壁、天花板、地板及室内各物体之间光的多次反射进 行自然照明。通常我们认为理想的环境光具有如下特性:没有空间或方向性;在 所有方向上和所有物体表面上投射的环境光强度是统一的恒定值。 由于环境光给予物体各个点的光照强度相同,且没有方向之分,所以在只有 环境光的情况下,同一物体各点的明暗程度均一样,因此,只有环境光是不能产 生具有真实感的图形效果。
粗糙的物体表面向各个方向等强度地反射光,这种等同地向各个方向散射的 现象称为光的漫反射(diffuse reflection)。产生光的漫反射现象的物体表面称为 理想漫反射体,也称为朗伯(Lambert)反射体。Lambert 漫反射模型:

I 是点光源强度, θ 是入射光方向与顶点法线的夹角,称为入射 (0≤θ ≤90°), ldiff I 是漫反射体与方向光交互反射的光强。入射角为零时,说 明光线垂直于物体表面,漫反射光强最大;90°时光线与物体表面平行,物体接 收不到任何光线。 若 N 为顶点单位法向量,L 表示从顶点指向光源的单位向量(注意,是由顶 点指向光源,不要弄反了),则cosθ 等价于 N 与 L 的点积。所以公式(9-2)可以 表示为公式(9-3):

综合考虑环境光和方向来,Lambert 光照模型可写为:

Unity 贴图整理

贴图的英语 Map 其实包含了另一层含义就是“映射”。其功能就是把纹理通过 UV 坐标映射到3D 物体表面。贴图包含了除了纹理以外其他很多信息,比方说 UV 坐标、贴图输入输出控制等等。材质是一个数据集,主要功能就是给渲染器提供数据和光照算法。

漫反射贴图diffuse map :漫反射贴图在游戏中表现出物体表面的反射和表面颜色。换句话说,它可以表现出物体被光照射到而显出的颜色和强度。我们通过颜色和明暗来绘制一幅漫反射贴图,在这张贴图中,吸收了比较多的光线的部分比较暗,而表面反射比较强的部分,吸收的光线比较少。
列举一下,物体的固有色以及纹理,贴图上的光影。前面的固有色和纹理我们很容易理解,至于后面的光影,我们再绘制漫反射贴图的时候需要区别对待,比如我们做一堵墙,每一块砖都是用模型做出来的,那么我们就没有必要绘制砖缝,因为这个可以通过打灯光来实现。可是我们如果用模型只做了一面墙,上面的砖块是用贴图来实现,那么就得绘制出砖缝了。从美术的角度,砖缝出了事一条单独的材质带外,还有就是砖缝也是承接投影的,所以在漫反射图上,绘制出投影也是很有必要的。没有什么物体能够反射出跟照到它身上相同强度的光。因此,让你的漫反射贴图暗一些是一个不错的想法。通常,光滑的面只有很少的光会散射,所以你的漫反射贴图可以亮一些。漫反射贴图应用到材质中去是直接通过DiffuseMap的。再命名规范上它通常是再文件的末尾加上“_d”来标记它是漫反射贴图。

高度贴图Height maps:所谓高度图实际上就是一个2D数组。创建地形为什么需要高度图呢?可以这样考虑,地形实际上就是一系列高度不同的网格而已,这样数组中每个元素的索引值刚好可以用来定位不同的网格(x,y),而所储存的值就是网格的高度(z)。
我们在这里叙述高度图,其实也是为了更好的绘制法线贴图,很多情况下我们的法线贴图只能在已有的漫反射贴图作为素材进行绘制,这样就是需要由一个HeightMap转换成法线贴图的一个过程,明白了这个原理,做起来也就可以更好的驾驭其效果。
高度贴图是一种黑白的图像,它通过像素来定义模型表面的高度。越亮的地方它的高度就越高,画面越白的地方越高,越黑的地方越低,灰色的在中间,从而表现不同的地形。使用高度贴图仅仅是为了适应简单的工作流程。高度贴图通常通过“Heightmap”函数来调用到3D软件中去的,我们通常再文件名后面加一个”_h”来标示它。

高光贴图 Specular maps:高光贴图是用来表现当光线照射到模型表面时,其表面属性的.(如金属和皮肤、布、塑料反射不同量的光)从而区分不同材质。高光贴图在引擎中表现为镜面反射和物体表面的高光颜色。
材质的反光程度就越强。(强弱度是指,如果将这张Specularmap去色成为黑白图,图上越偏向RGB0,0,0,的部分高光越弱,越偏向RGB255,255,255的部分高光越强.)
我们建立高光贴图的时候,我们使用solid value来表现普通表面的反射,而暗的地方则会给人一种侵蚀风化的反射效果。
颜色在高光贴图中将会用来定义高光的颜色,组成砖的材料应该是一些沙子,他们将会反射出一些微小的具有质感的光。(为了丰富高光贴图,我们有很多方法:做局部高光的细微变化,添加纹理(这个纹理要和材质本身的纹理区分开),叠加彩色图层(谨慎用))。高光贴图是通过Specularmap函数调用到引擎中的,通常我们再贴图的后面加一个”_s”来区别它。要记住的是,单单凭借高光贴图是无法充分的表现材质特性的,只有Didffuse,Normal,和Specular三张配合才能充分的表现材质特性。

AO贴图Ambient Occlusiont:中文一般叫做环境阻塞贴图,是一种目前次时代游戏中常用的贴图技术,AO贴图的计算是不受任何光线影响的,仅仅计算物体间的距离,并根据距离产生一个8位的通道。计算物体的AO贴图的时候,程序使每个像素,根据物体的法线,发射出一条光,这个光碰触到物体的时候,就会产生反馈,标记这里附近有物体,就呈现黑色。而球上方的像素所发射的光,没有碰触到任何物体,因此标记为白色。简单了解算法后,大家就明白,全局光的烘焙师模拟GI(全局光)所呈现的阴影效果,而AO贴图时模拟模型的各个面之间的距离。二者性质是完全不一样的。
在unity中,我们有两个地方可以调整AO,一个是在光照贴图渲染器中,有一个调整AO的参数,这个是确实渲染了一层AO。还有一个就是通过摄影机特效,有一个屏幕空间环境阻塞的特效screen speace ambient occlusion(SSAO).这两个都可以实现部分的AO效果。

环境贴图CUBEMAP:Cube map技术说到底就是用一个虚拟的立方体(cube)包围住物体,眼睛到物体某处的向量eyevec经过反射(以该处的法线为对称轴),反射向量reflectvec射到立方体上,就在该立方体上获得一个纹素了(见下图)。明显,我们需要一个类似天空盒般的6张纹理贴在这个虚拟的立方体上。按CUBE MAPPING原意,就是一种enviroment map,因此把周围场景渲染到这6张纹理里是“正统”的。也就是每次渲染时,都作一次离线渲染,分别在每个矩形中心放置相机“拍下”场景,用FBO渲染到纹理,然后把这张纹理作为一个cube map对象的六纹理之一。这样即使是动态之物也能被映射到物体表面了(虽然缺点是不能映射物体自身的任何部分)。

注:原文这里应有一张 Cube Map 反射示意图,但当前仓库未保留对应图片。

光照纹理LIGHTMAP:把物体光照的明暗信息保存到纹理上, 实时绘制时不再进行光照计算, 而是采用预先生成的光照纹理(lightmap)来表示明暗效果。
好处:由于省去了光照计算, 可以提高绘制速度 。对于一些过度复杂的光照(如光线追踪, 辐射度, AO等算法), 实时计算不太现实. 如果预先计算好保存到纹理上, 这样无疑可以大大提高模型的光影效果,保存下来的lightmap还可以进行二次处理, 如做一下模糊, 让阴影边缘更加柔和
缺点:模型额外多了一层纹理, 这样相当于增加了资源的管理成本(异步装载, 版本控制, 文件体积等). 当然, 也可以选择把明暗信息写回原纹理, 但这样限制比较多, 如纹理坐标范围, 物体实例个数。静态的光影效果与对动态的光影没法很好的结合. 如果光照方向改变了的话, 静态光影效果是无法进行变换的. 而且对于静态的阴影, 没法直接影响到动态的模型. 这一点, 反而影响了真实度。

MIPMAP:把一张贴图按照2的倍数进行缩小。直到1X1。把缩小的图都存储起来。在渲染时,根据一个像素离眼睛为之的距离,来判断从一个合适的图层中取出texel颜色赋值给像素。
透过它的工作原理我们可以发现,硬件总是根据眼睛到目标的距离,来选择最适合当前屏幕像素分辨率的图层。假设一张256x256的mipmap贴图,当前屏幕分辨率为10241024。眼睛距离物体比较近时,mipmap最大也只可能从10241024的Mipmap图层选取texel。再次,当使用三线性过滤(trilinear)时,最大也只能访问20482048的图层选取texel,来和10241024图层中的像素进行线性插值。

detailmap:顾名思义,就是细节的贴图,其实就是图层的叠加与混合。在这里有几个关键词,一个是Detail的Tiling值,一个是这个Detailmap需要在导入的时候设置为Mipmap。
法线贴图是凸凹贴图(Bump mapping)的一种常见应用,简单说就是在不增加模型多边形数量的前提下,通过渲染暗部和亮部的不同颜色深度,来为原来的贴图和模型增加视觉细节和真实效果。简单原理是在普通的贴图的基础上,再另外提供一张对应原来贴图的,可以表示渲染浓淡的贴图。通过将这张附加的表示表面凸凹的贴图的因素于实际的原贴图进行运算后,可以得到新的细节更加丰富富有立体感的渲染效果。法线贴图多用在CG动画的渲染以及游戏画面的制作上,将具有高细节的模型通过映射烘焙出法线贴图,贴在低端模型的法线贴图通道上,使之拥有法线贴图的渲染效果,却可以大大降低渲染时需要的面数和计算内容,从而达到优化动画渲染和游戏渲染的效果。法线贴图就是记录了一个需要进行光影变换的贴图上的各个点的凹凸情况的贴图,显示芯片根据这个贴图的内容,来实时的生成新的有过光影变化的贴图,从而实现立体效果。
法线贴图其实并不是真正的贴图,所以也不会直接贴到物体的表面,它所起的作用就是记录每个点上的法线的方向。所以这个贴图如果看起来也会比较诡异,经常呈现一种偏蓝紫色的样子。
大多数法线图一般都和下面的图类似,是一张以蓝紫色为主的图。这张法线图其实是一张RGB贴图,其中红,绿,蓝三个通道分别表示由高度图转换而来的该点的法线指向:Nx、Ny、Nz。在其中绝大部分点的法线都指向z方向,因此图更偏向于蓝色。在shader进行处理时,我们将光照与该点的法线值进行点积后即可得到在该光线下应有的明暗特性,再将其应用到原图上,即可反应在一定光照环境下物体的凹凸关系了。

如何在Unity 中播放视频

Unity视频播放有很多种实现方式,可根据要求来选择适当的实现,这里总结一下:

  1. MovieTexture
    Unity标准接口,支持的播放视频格式有.mov、.mpg、.mpeg、.mp4、.avi和.asf。仅支持PC端的本地视频播放。

  2. 在游戏对象中播放,就好比在游戏世界中创建一个Plane面对象,摄像机直直的照射在这个面上
    在新建的一个plane平面,将其纹理绑定为电影纹理即可
    //设置当前对象的主纹理为电影纹理
    renderer.material.mainTexture = movTexture;
    //设置电影纹理播放模式为循环
    movTexture.loop = true;
    并可通过
    movTexture.Play();
    movTexture.Pause();
    movTexture.Stop();
    来进行播放控制。
    此时可以通过直接缩放plane平面来达到缩放视频的目的
    至于MovieTexture的赋值,在5.0x版本上是无法通过将视频拖入Project视频来自动造成纹理的。

  3. 在GUI层面播放。它其实和贴图非常相像,因为播放视频用到的MovieTexture属于贴图Texture的子类。
    //绘制电影纹理
    GUI.DrawTexture(newRect(0,0, Screen.width,Screen.height),movTexture,ScaleMode.StretchToFill);
    播放视频的大小是屏幕的宽高,如果想动态的修改视频的宽或高直接修改new Rect()视频显示区域即可

  4. Handheld.PlayFullScreenMovie
    Unity标准的视频播放接口,支持的播放视频格式有.mov、.mpg、.mpeg、.mp4、.avi和.asf。支持PC/移动端播放,支持本地在线播放
    url_movie = “http://dl.nbrom.cn/17051/c3e408229342723fbdf62d0bcf1d549c.mp4?fsname=Criminal_Minds_S01E01.mp4“;
    Handheld.PlayFullScreenMovie(url_movie, Color.black, FullScreenMovieControlMode.Full);
    Handheld.PlayFullScreenMovie(“test.mp4”, Color.black, FullScreenMovieControlMode.CancelOnInput);
    将视频文件放置在Assets/StreamingAssets/路径下
    上面的方法在移动端是边下载边播放网络视频的,属于在线播放,不好的地方就是,再次观看还需要再次加载。可能在播放的时候判断是否已下载到本地如果在本地就可以播放本地,如果没有再从网上下载到本地

  5. EasyMovieTexture
    Unity移动端第三方视频播放插件,支持视频本地播放,支持RTSP。
    1>. 初始化加载,该部分主要在Unity中将播放视频的地址(本地/URL)传送到Android,并完成MediaPlayer的初始化
    2>. Android创建一个Surface,并将其与之前创建的MediaPlayer绑定
    3>. 结合视频绘制载体计算图像拉伸比
    4>. 根据视频宽高比创建VideoTexture并传到Android与
    m_VideoTexture = new Texture2D(Call_GetVideoWidth(), Call_GetVideoHeight(), TextureFormat.RGB565, false);
    Call_SetUnityTexture(m_VideoTexture.GetNativeTextureID());
    5>. 设置视频窗口,完成TextureId与surface的绑定
    SetWindowSize(GetVideoWidth(),GetVideoHeight(),m_iUnityTextureID ,m_bRockchip);
    6>. 更新纹理
    Call_UpdateVideoTexture();
    m_SurfaceTexture.updateTexImage();
    7>. 播放视频
    使用MediaPlayer播放视频

  6. MediaPlayer + SurfaceTexture
    播放组件上层使用MediaPlayer来处理,在成功创建并设置好setDataSource后,需要创建GL_TEXTURE_EXTERNAL_OES格式的纹理ID来与MediaPlayer生成联系。
    在这里我们需要使用SurfaceTexture的理由是,它能代替SurfaceHolder,使得当我们指定图像流的输出目标为照相机预览或视频解码时,我们在每一帧中得到的所有数据不需要直接用于显示在设备上,而是可以选择先输出到SurfaceTexture上,在上屏之前可能做一些自定义扩展。当调用updateTexImage()时,用来创建SurfaceTexture的纹理对象内容被更新为包含图像流中最近的图片。
    SurfaceTexture对象可以在任何线程里创建。但updateTexImage()只能在包含纹理对象的OpenGL ES上下文所在的线程里创建。可以得到帧信息的回调可以在任何线程被调用。这一点要注意,上下文如果不一致,视频无法上屏。
    这里还有个要求就是在创建纹理的时候,需要使用使用GL_TEXTURE_EXTERNAL_OES作为纹理目标,其是OpenGL ES扩展GL_OES_EGL_image_external定义的。这种纹理目标会对纹理的使用方式造成一些限制。每次纹理绑定的时候,都要绑定到GL_TEXTURE_EXTERNAL_OES,而不是GL_TEXTURE_2D。而且,任何需要从纹理中采样的OpenGL ES 2.0 shader都需要声明其对此扩展的使用,例如,使用指令”#extension GL_OES_EGL_image_external:require”。这些shader也必须使用samplerExternalOES采样方式来访问纹理。

设计模式之访问者模式

定义:使用一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。通过访问者来访问自身的一些方法。accept方法的参数为访问者,访问者方法visit通过参数元素访问元素本身。

角色:
抽象访问者:抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法中的参数定义哪些对象是可以被访问的。
访问者:实现抽象访问者所声明的方法,它影响到访问者访问到一个类后该干什么,要做什么事情。
抽象元素类:接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。抽象元素一般有两类方法,一部分是本身的业务逻辑,另外就是允许接收哪类访问者来访问。
元素类:实现抽象元素类所声明的accept方法,通常都是visitor.visit(this),基本上已经形成一种定式了。
结构对象:一个元素的容器,一般包含一个容纳多个不同类、不同接口的容器,如List、Set、Map等,在项目中一般很少抽象出这个角色。

优点:

1、符合单一职责原则。凡是适用访问者模式的场景中,元素类中需要封装在访问者中的操作必定是与元素类本身关系不大且是易变的操作,使用访问者模式一方面符合单一职责原则,另一方面,因为被封装的操作通常来说都是易变的,所以当发生变化时,就可以在不改变元素类本身的前提下,实现对变化部分的扩展。
2、优秀的扩展性。元素类可以通过接受不同的访问者来实现对不同操作的扩展。 3、灵活性。

缺点:1、具体元素对访问者公布细节,违反了迪米特原则。

2、具体元素变更比较困难。

3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象。

使用:创建一个定义接受操作的 ComputerPart 接口。Keyboard、Mouse、Monitor 和 Computer 是实现了 ComputerPart接口的实体类。我们将定义另一个接口 ComputerPartVisitor,它定义了访问者类的操作。Computer 使用实体访问者来执行相应的动作。
1.定义一个表示元素的接口:

public interface ComputerPart
{
public void accept(ComputerPartVisitor computerPartVisitor);
}
2.创建扩展了上述类的实体类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Keyboard  implements ComputerPart 
{
public Override void accept(ComputerPartVisitor computerPartVisitor)
{
computerPartVisitor.visit(this);
}
}

public class Monitor implements ComputerPart
{
public Override void accept(ComputerPartVisitor computerPartVisitor)
{
computerPartVisitor.visit(this);
}
}

public class Mouse implements ComputerPart
{
public Override void accept(ComputerPartVisitor computerPartVisitor)
{
computerPartVisitor.visit(this);
}
}

public class Computer implements ComputerPart
{
ComputerPart[] parts;

public Computer()
{
parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};
}

public Override void accept(ComputerPartVisitor computerPartVisitor)
{
for (int i = 0; i < parts.length; i++)
{
parts[i].accept(computerPartVisitor);
}

computerPartVisitor.visit(this);
}
}

3.定义一个表示访问者的接口。

1
2
3
4
5
6
7
public interface ComputerPartVisitor 
{
public void visit(Computer computer);
public void visit(Mouse mouse);
public void visit(Keyboard keyboard);
public void visit(Monitor monitor);
}

4.创建实现了上述类的实体访问者。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ComputerPartDisplayVisitor implements ComputerPartVisitor 
{
public Override void visit(Computer computer)
{
System.out.println("Displaying Computer.");
}

public Override void visit(Mouse mouse)
{
System.out.println("Displaying Mouse.");
}

public Override void visit(Keyboard keyboard)
{
System.out.println("Displaying Keyboard.");
}

public Override void visit(Monitor monitor)
{
System.out.println("Displaying Monitor.");
}
}

5.使用 ComputerPartDisplayVisitor 来显示 Computer 的组成部分。

1
2
3
4
5
6
7
8
public class VisitorPatternDemo 
{
public static void main(String[] args)
{
ComputerPart computer = new Computer();
computer.accept(new ComputerPartDisplayVisitor());
}
}

谈谈我对陌生人社交的理解

上周您在大群说了公司目前的状态和公司目前的产品,以及到2020年实现的目标,我不知道其他同事看了有何感想,我是觉得热血沸腾,你最后说陌生人社交是你一直想做的,任何时候入场都有机会,只是现在没有一个切入点,我非常赞同您的观点,我又不确定您是否是真的要做陌生人社交还只是随口说说,所以我想给你汇报一下我对这一块的理解和想法。
目前各种社交软件和社区太多太多,而且移动互联网发展到现在已经非常成熟了,我们现在再杀入陌生人设交这块红海很很困难,随便在Google或App store等应用市场搜索,方向为陌生人社交的App鳞次栉比,微博的信息流广告、快手抖音的短视频,陌陌的直播,各种稀奇古怪的产品名称,明明知道已经是红海了,为什么我们还要做陌生人社交呢?

我是一个典型的80后,经历过互联网和移动互联网的兴起,从2000年第一次接触qq的兴奋激动,到现在移动互联网手机上各类的社交app的熟视,少说也用了一百来款了。有人的地方,就有社交,所以任何时候社交都是生活和工作中不可或缺的,现在的社交软件不管多完美,还是一样无法满足所有人的要求,随着网络的提速和生活节奏的加快,东西方文化进一步融合,出现了很多很多的社交形式:图片社交,视频社交,性格社交,颜值社交,即时社交,知识社交,游戏社交。。。等等,目前熟人社交有qq和微信两座大山堵在前面,近期很难改变这种格局,而陌生人的社交就是百花争艳,百家争鸣,其中典型的代表就是陌陌,探探,微博,秘密,快手等等。国外比较有著名的facebook,twitter, instagram, linkin, whatsapp,snapchat 等等。对于陌生人社交目前还没有哪一家可以像腾讯和Facebook一样的量级。所以我们还有机会。

社交的本质其实就是“人与人之间的关系和互动”,社交产品的核心就是连接人与人的关系,这个产品在互联网早期和移动互联网早期就是靠这种关系去抢占市场,通过口碑相传建立圈子基础,然后在这个平台上进行各类的社交活动,即便后期没有什么创新用户也不怎么会流失,因为他们已经在这里扎根了,原来的陌生人也变成了熟人,切换平台的代价太大。现在移动互联网已经发展很成熟了,移动用户经历这么多年的沉淀,选择多了,也就变的很挑剔的人了,如果没有新颖的形式出现,很难想互联网初期哪样全民火爆了。

随着这俩年抖音和快手的爆发式增长,可以发现陌生人社交形态慢慢在趋向于社区文化和短视频,为什么呢?
陌生人社交和熟人社交,看起来是按照产品上的用户的熟悉程度进行的区分,然而这两条道的设计产品却有根本的区别,这种区别就是在与为用户“建立关系”的逻辑不同,熟人社交产品上,用户原来是有联系的,用户是将自身的朋友圈子关系沉淀到产品上,利用关系提供服务,而陌生人社交的用户并不存在“联系和关系”,如何让用户发生关系就是做陌生社交的产品要想清楚的。而社区和短视频是目前最好的发生关系的信息媒介。

1、社区是建立用户关系较好的选择
熟人社交由于不存在帮助用户“发现”他人的要求(或者优先级很低),所以产品的重心在建立关系之后的“互动和沟通上”,所以产品形态多为IM;用户之间的沟通互动都是实时或者及时的,而陌生人社交产品需要为用户展现海量的他人信息,所以形态更多的是社区,这是一种异步的互动,这是一种单方面的关系,如果用户没有同意建立关系,他们将不能互相通信,这种社交显的效率低下,但是可以降低用户的防备心理。让用户觉得保持陌生人的距离由用户自己来控制。
陌生人社交的寻找好友,但无非以下几种形式:
1、让注册用户先填写一些信息(兴趣,星座,关注行业,特长等),然后进行推荐和匹配,比如微博,
2、基于LBS的附近或同城的关系进行匹配,还有基于男女性别进行搜索匹配
3、直接向用户展示首页各类内容,信息,任由用户浏览,然后再引导用户关注内容背后的人
4、通过提问,评论回复建立的关系
5、通过搜索关键词找到感兴趣的内容和感兴趣的人

说到陌生人社交,很多人第一想到的就是用它来约炮,这是陌生社交发生关系的最原始的驱动,要做陌生人社交就避不开这个敏感的话题。这是人性和需求的驱使,既然无法避开,我们正好可以利用人性的弱点来吸引的用户的目光,鼓励用户在合理范围内按照自己的理解来使用这个平台,有关注才有量,有量才有后面的内容,待用户达到一定的体量,就可以对新注册用户加强审核,避免涉黄。对用户发布的内容(文字,图片,视频)进行严格的审核和清理。

2、短视频,用户信息展现的极佳形式
短视频的优势在于信息量和留存,相对于文字和图片,更能使你想关注的对象具体和接地气,短视频更能展现一个人的性格,短时间内展现更多元信息,不在单纯颜控,以貌取人,至于能提高留存率,即使不互动,刷短视频也比刷图文有趣的多。

用户信息的呈现形式有文字、图片、乃至视频,这些都没有本质的区别。至于如何用这些形式去展现“用户是谁”,各家陌生人社交产品都给出了自己解决方案:传统的可能就是基于LBS的图文社区,比较新鲜的诸如“如故”中的性格测试和观点广场、“same”建立的粒度极细的话题讨论等。而目前来看,最近很火的短视频这种形态,是被各家认可的解决方案(抖音、陌陌、快手、火山小视频、映客、YY等),陌陌就曾铺天盖地地打广告:“用视频,认识我”。

3、社交效率是陌生人社交的当前最要解决的问题
上陌生社交平台的用户,一般可分为俩类,一种一个人寂寞无聊了,想找个人聊聊,一种是生活中遇到某些困难了,想寻求安慰和寻去帮助,这是如果用户低效率的刷首页,还很长时间内得不到反馈,用户很可能就会提出删程序,例如使用基于LBS属性寻找目标用户,对于互动的及时性就有更高的要求,对于基于地域,热度和兴趣的社交平台,可以通过大数据算法,通过内容分发,吧用户生产的内容分发给“最有可能得到反馈的人”,同时让用户在首页看到“最可能想点赞,评论”的内容,通过技术来最大程度地精准匹配内容及其受众,以期待得到最快的反馈。
然而不管算法如何精准,图文视频的社区都只是一个异步社交。用户一旦建立了关系,都会互换微信或QQ等其他即时通信工具进行后续沟通,而很少再回到社交平台上来。所以,对于追求快感和快节奏的用户,更高效的即时社交则似乎是一种需求,目前有直播,视频快聊,和多人视频派对等几种即时社交。但效果都不是很理想,直播属于一对多的形式,这种模式决定大部分用户在互动中是得不到反馈的,可能最初用户会因为新奇而关注,但在慢慢在互动过程中的得不到反馈,用户很自然的会流失。而视频快聊和多人视频是多对多的关系,但在国内国人明显比较内敛和羞涩,使得大部分用户不敢尝试快聊,即使尝试也也是处于“尬聊”的状态。面对面的快速互动反而无限放大了这种尴尬气氛。

4、游戏社交,提供更多有趣的话题
17年上半年狼人杀很火,我们去看狼人杀的本质,可以发现其实它就是一种主题视频趴,即使没有“狼人杀”这个产品,大家一块开视频也能玩,只不过这个需求量够大,大到应该单独拿出来做一个功能模块。比如还有yy短视频的你画我猜,表情包大战,抖音的“热门挑战”等,游戏为即时社交提供了互动场景,会玩的不会玩的都能参与其中,全程都有话题,解决了“尬聊”的问题。也间接提高了社区的活跃度。

其实早在前两年直播比较火的时候我就想设计一款代社交属性的视频软件–技能展示和交易短视频平台,当时单纯只是想做一个技能直播的平台,后来听说直播需要审核资质而且门槛很高,不是几个程序员就可以搞定的,就一直没有理会。正好乘上周周末有时间整理了一下思路。不知道算不算是一个陌生社交切入点,还有没有机会,所以想听听您的意见和建议。

设计模式之策略模式

定义:针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式让算法独立于使用它的客户而独立变化。

角色:
Context(应用场景):需要使用ConcreteStrategy提供的算法。 内部维护一个Strategy的实例,通过动态改变Strategy实例(赋值或其他)改变调用的算法。负责动态设置运行时Strategy具体的实现算法。负责跟Strategy之间的交互和数据传递。
Strategy(抽象策略类):定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,Context使用这个接口调用不同的算法,一般使用接口或抽象类实现。
ConcreteStrategy(具体策略类):实现了Strategy定义的接口,提供具体的算法实现。

结构图:

优点:

1、提供了一种替代继承的方法,而且既保持了继承的优点(代码重用)还比继承更灵活(算法独立,可以任意扩展)。

2、避免使用多重条件判断,使系统更灵活,并易于扩展。

3、遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。

缺点:

1、策略类会增多。

2、所有策略类都需要对外暴露。

使用:Context 是一个使用了某种策略的类。StrategyPatternDemo,我们的演示类使用 Context 和策略对象来演示 Context 在它所配置或使用的策略改变时的行为变化。
1.创建一个接口:

public interface Strategy
{
public int doOperation(int num1, int num2);
}
2.创建实现接口的实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class OperationAdd implements Strategy
{
public Override int doOperation(int num1, int num2) {
return num1 + num2;
}
}

public class OperationSubstract implements Strategy
{
public Override int doOperation(int num1, int num2) {
return num1 - num2;
}
}

public class OperationMultiply implements Strategy
{
public Override int doOperation(int num1, int num2) {
return num1 * num2;
}
}

3.创建 Context 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Context 
{
private Strategy strategy;

public Context(Strategy strategy)
{
this.strategy = strategy;
}

public int executeStrategy(int num1, int num2)
{
return strategy.doOperation(num1, num2);
}
}

4.使用 Context 来查看当它改变策略 Strategy 时的行为变化。

1
2
3
4
5
6
7
8
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

context = new Context(new OperationSubstract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));

context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));

设计模式之状态模式

定义:在面向对象软件设计时,常常碰到某一个对象由于状态的不同而有不同的行为。如果用if else或是switch case等方法处理,对象操作及对象的状态就耦合在一起,碰到复杂的情况就会造成代码结构的混乱。在这种情况下,就可以使用状态模式来解决问题。通过不同的状态创建不同的实例啦体处理逻辑。

角色:
Context代表需要改变状态的那个对象,它维护了一个State实例,该实例定义了对象的当前状态。
State定义了一个抽象类或是接口,用以封装与特定状态相关的行为。
ConcreteState实现了State定义的行为。

优点:

1、封装了转换规则。

2、枚举可能的状态,在枚举状态之前需要确定状态种类。

3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。

4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。

5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

缺点:

1、状态模式的使用必然会增加系统类和对象的个数。

2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。

3、状态模式对”开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

使用:以下实现一个电灯开关状态。
1.状态接口:

public interface LightState
{
void PressSwitch(Light light);
}
2.实现具体的状态接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class LightOn : LightState
{
public void PressSwitch(Light light)
{
Console.WriteLine("Light Off");
light.State = new LightOff();
}
}

public class LightOff : LightState
{
public void PressSwitch(Light light)
{
Console.WriteLine("Light On");
light.State = new LightOn();
}
}

3.定义拥有该状态的对象即电灯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Light
{
public LightState State;

public Light()
{
State = new LightOff();
}

public void PressSwtich()
{
State.PressSwitch(this);
}
}
1
2
3
4
5
6
7
8
static void Main(string[] args)
{
Light light = new Light();
light.PressSwtich();
light.PressSwtich();
light.PressSwtich();
Console.ReadLine();
}

设计模式之观察者模式

定义:当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己。类似于事件,发生一个事件时,所有监听该事件的观察者会自动更新(或者说调用自身某些方法)。

角色:
1.抽象目标角色(Subject):目标角色知道它的观察者,可以有任意多个观察者观察同一个目标。并且提供注册和删除观察者对象的接口。目标角色往往由抽象类或者接口来实现。
2.抽象观察者角色(Observer):为那些在目标发生改变时需要获得通知的对象定义一个更新接口。抽象观察者角色主要由抽象类或者接口来实现。
3.具体目标角色(Concrete Subject):将有关状态存入各个Concrete Observer对象。当它的状态发生改变时, 向它的各个观察者发出通知。自身维护一个观察者容器并提供添加、删除观察者接口。
4.具体观察者角色(Concrete Observer):存储有关状态,这些状态应与目标的状态保持一致。实现Observer的更新接口以使自身状态与目标的状态保持一致。在本角色内也可以维护一个指向Concrete Subject对象的引用。

优点:

1、观察者和被观察者是抽象耦合的。

2、建立一套触发机制。

缺点:

1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。

3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
abstract class Subject 
{
private Vector<Observer> obs = new Vector<Observer>();

public void addObserver(Observer obs)
{
this.obs.add(obs);
}

public void delObserver(Observer obs)
{
this.obs.remove(obs);
}

protected void notifyObserver()
{
for(Observer o: obs){
o.update();
}
}
public abstract void doSomething();
}

class ConcreteSubject extends Subject
{
public void doSomething()
{
System.out.println("被观察者事件反生");
this.notifyObserver();
}
}

interface Observer
{
public void update();
}

class ConcreteObserver1 implements Observer
{
public void update()
{
System.out.println("观察者1收到信息,并进行处理。");
}
}

class ConcreteObserver2 implements Observer
{
public void update()
{
System.out.println("观察者2收到信息,并进行处理。");
}
}

public class Client
{
public static void main(String[] args)
{
Subject sub = new ConcreteSubject();
sub.addObserver(new ConcreteObserver1()); //添加观察者1
sub.addObserver(new ConcreteObserver2()); //添加观察者2
sub.doSomething();
}
}

设计模式之备忘录模式

定义:所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。备忘录模式将要保存的细节给封装在备忘录中,就是哪天要改变保存的细节也不会影响到客户端。类似存档和撤销的功能。

角色:
1.发起人:记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。
2.备忘录:负责存储发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。
3.管理角色:对备忘录进行管理,保存和提供备忘录。

优点:
1.当发起人角色中的状态改变时,有可能这是个错误的改变,我们使用备忘录模式就可以把这个错误的改变还原。
2.备份的状态是保存在发起人角色之外的,这样,发起人角色就不需要对各个备份的状态进行管理。

缺点:在实际应用中,备忘录模式都是多状态和多备份的,发起人角色的状态需要存储到备忘录对象中,对资源的消耗是比较严重的。

使用:Memento 包含了要被恢复的对象的状态。Originator 创建并在 Memento 对象中存储状态。Caretaker 对象负责从 Memento 中恢复对象的状态。
1.创建 Memento 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Memento 
{
private String state;

public Memento(String state)
{
this.state = state;
}

public String getState()
{
return state;
}
}

2.创建 Originator 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Originator {
private String state;

public void setState(String state)
{
this.state = state;
}

public String getState()
{
return state;
}

public Memento saveStateToMemento()
{
return new Memento(state);
}

public void getStateFromMemento(Memento Memento)
{
state = Memento.getState();
}
}

3.创建 CareTaker 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CareTaker 
{
private List<Memento> mementoList = new ArrayList<Memento>();

public void add(Memento state)
{
mementoList.add(state);
}

public Memento get(int index)
{
return mementoList.get(index);
}
}

4.使用 CareTaker 和 Originator 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MementoPatternDemo 
{
public static void main(String[] args)
{
Originator originator = new Originator();
CareTaker careTaker = new CareTaker();
originator.setState("State #1");
originator.setState("State #2");
careTaker.add(originator.saveStateToMemento());
originator.setState("State #3");
careTaker.add(originator.saveStateToMemento());
originator.setState("State #4");

System.out.println("Current State: " + originator.getState());
originator.getStateFromMemento(careTaker.get(0));
System.out.println("First saved State: " + originator.getState());
originator.getStateFromMemento(careTaker.get(1));
System.out.println("Second saved State: " + originator.getState());
}
}