【游戏开发】浅谈游戏开发中常见的设计原则

  俗话说得好:“设计模式,常读常新~”。的确,每读一遍设计模式都会有些新的体会和收获。马三不才,才读了两遍设计模式(还有一遍是在学校学的),属于菜鸟级别的。这次准备把阅读设计模式的想法记录下来,并且把设计模式应用在Unity游戏开发上,做些小案例。

什么是设计模式

  每一种模式都在说明某种一再出现的问题,并描述解决方法的核心,之后让你能够举一反三,从而解决数个类似的问题。每一种设计模式除了按照“面向对象的设计原则”加以分析设计之外,还满足:”解决一再出现的问题“、”解决问题的方案和问题核心的关键点“、”可以重复利用解决方案“这样几个要求。

  通过引入”模式“的概念,以经验积累的方式,将一些经常用来解决特定问题的”类设计“、”对象组装“加以整理并定义成一种”设计模式“。而这些设计模式让开发者在以后遇到相同的问题时,可以从中找出对应的解决方案直接使用,不必再过多地思考如何设计分析。这样不但可以减少不必要的时间花费,还可以加强软件的”稳定性“和”可维护性“。

游戏开发设计中的常见7大设计原则

  虽然标题写的是”游戏开发设计中的常见7大设计原则“,其实这些原则本来就是适用于普遍性的面向对象设计。如果软件设计人员能够充分了解这些设计原则并且加以利用,那么就可以让实现出来的系统更加稳定、容易维护、具有更好的拓展性。

单一职责原则(SRP)

  这个原则强调的是”当设计封装一个类时,该类应该只负责一件事“。当然,这与在类抽象化的过程中,对于该类所负责的功能有关。一个类应该只负责系统中的一个单独功能的实现,但是对于功能的划分和归属,通常也是开发过程中最困扰设计人员的问题。设计人员在一开始的时候不太容易遵循这个原则,会在项目开发的过程中,不断地向同一个类上添加功能,最后导致类过于庞大、接口过于复杂后才会发现这个问题,最后可能让整个项目过度依赖这个类,使得项目失去弹性。

  但是,只要通过不断地进行”类重构“,将类中与实现相关功能的部分抽取出来,另外封装成新的类,之后利用组合的方式,将新增的类加入到原有的类中,慢慢地就能符合类单一职责化的要求了,即项目中每一个类都只负责单一功能的实现。

开闭原则(OCP)

  一个类应该”对拓展开放,对修改封闭“。何为对拓展开放对修改封闭呢?其实这里提到的类指的是实现系统某项功能的类。而这个功能的类,除非是修正错误,否则,当软件的开发流程进入“完工测试期”或者“上市维护期”时,对于已经测试完成或者已经上线运行的功能,就应该“关闭对修改的需求”,也就是不能再修改这个类的任何接口或者实现。

  但是,当增加系统功能的需求发生的时候,又不能置之不理,所以也必须对“功能的增加保持开放”。为了满足这个原则的要求,系统分析时就要朝向“功能接口化”的方向进行设计,将系统功能的“操作方法”向上提升,抽象化为接口,将“功能的实现”向下移到子类中。因此,在面对增加系统功能的需求时,就可以使用“增加子类”的方法来满足。具体的实现方式是:重新实现一个新的子类,或者继承就得实现类,并在新的子类中实现新增的系统功能。这样,对于旧系统的功能实现就可以保持不变(封闭),同时又对功能的新增需求保持开放。

里氏替换原则(LSP)

  这个原则指的是“子类必须能够替换父类”。如果按照这个设计原则去实现一个有多层继承的类群组,那么其中的父类通常是“接口”或者“可被继承的类”。父类中一定包含了可被子类重新实现的方法,而客户端使用的操作接口也是由父类来定义的。客户端在使用的过程中,必须不能使用到“对象强制转型成子类”的语句,客户端也不应该知道,目前正在使用的对象是哪一个子类实现的。至于使用哪个子类对象来代替父类对象,则是由类本身的对象产生机制来决定,外籍无法得知。里氏替换原则基本上也是对于开放——封闭原则提供了一个实现的法则,说明如何设计才能保持正确的需求开放。

依赖倒置原则(DIP)

  这个原则包含了两个主题:

  • 高层模块不应该依赖于低层模块,两者都应该依赖于抽象概念;
  • 抽象接口不应该依赖于实现,而实现应该依赖于抽象接口。

  从生活中举例来解释第一个原则主题(高层模块不应该依赖于低层模块),两者都应该依赖于抽象,可能会比单纯使用软件设计来解释更为容易,所以下面就以汽车为例进行说明。

  汽车与汽车引擎就是一个很明显违反这个原则的例子:汽车就是所谓的高层模块,当要组装一台汽车时,需要有不同的低层模块进行配合才能完成,如引擎、传统系统、轮胎、汽车骨架等,有了这些低层模块的相互配合才能完成一辆汽车的装配。但是汽车很容易被引擎系统给限定。汽油机的汽车不能加装柴油,柴油机的汽车不能加装汽油。每当汽车要加油的时候,都必须根据引擎来选择不同的加油设施,汽车因为引擎而被限定了加油的品项。虽然这是一个很难去改变的例子,但是在软件系统的设计上,反倒有很多方法可以去避免这个“高层依赖于低层”的问题,也就是将它们之间的关系反转,让低层模块按照高层模块所定义的接口去实现。

  以电脑的组成为例,位于高层的电脑定义了USB接口,而这个接口定义了硬件所需的规格及软件驱动程序的编写规范。只要任何低层模块,如储存卡、手机、U盘、读卡器等设备,凡是符合USB接口规范的,都能加入到电脑中,成为电脑的一部分。通过这个电脑的例子我们大概就可以明白如何由“高层模块定义接口”再由“底层模块遵循这个接口实现”的过程,这个过程可以让他们之前的依赖关系反转。同时,这个反转的过程也说明了第二项原则的含义:“抽象接口不应该依赖于实现,而实现应该依赖于抽象接口”。当高层模块定义了沟通接口以后,与低层模块的沟通就应该只通过接口来进行,在具体实现上,这个接口可能是以一个类的变量或者对象引用来表示的。请注意,在使用这个变量或者对象引用的过程中,不能做任何的类型转换,因为这样就限定了高层模块只能使用某一个底层模块的特定实现。而且,子类在重新实现时,都要按照接口类所定义的方法进行实现,不应该再新增其他方法,不能让高层模块有利用类型转换的方法去调用的机会。

  依赖倒转原则的本质就是通过抽象类(接口或抽象类),使各个类或者模块间实现彼此独立,不互相影响,从而实现模块间的松散耦合。那么我们如何在开发中遵守或者使用这个原则呢,下面是一些建议:

  • 每个类尽量都继承自接口或者抽象类,或者抽象类和接口两者都具备。这是依赖倒转的基本要求,接口和抽象类都是抽象的,有了抽象才能依赖倒转。
  • 变量的显示类型尽量是接口或者抽象类
  • 很多书上说变量的类型一定是接口或者抽象类,其实这样说有点过于绝对了。比如一个工具类一般是不需要接口或者抽象类的。
  • 类要尽量避免从具体的类派生。如果一个项目正处于开发状态,确实不应该有从具体类派生出子类的情况,但这也不是绝对的,因为人都会犯错误,有的时候设计的缺陷在所难免,因此只要继承深度不超过两层都是可以接受的。特别说明的是做项目维护的工程师,基本上可以不考虑这个规则,因为维护工作基本上都是做拓展开发、修复bug。通过一个继承关系,覆盖一个方法就可以修复一个很大的bug,何必在要去继承最高的基类呢?
  • 尽量不要覆盖基类的方法。如果基类是一个抽象类,而且这个方法已经实现了,子类就尽量不要去覆盖。类间依赖的是抽象,覆盖了抽象方法,对依赖的稳定性会产生一定的影响。

接口隔离原则(ISP)

  “客户端不应该被迫使用它们用不到的接口方法。”这个问题一般随着项目开发的进行而越来越明显。当项目中出现了一个负责主要功能的类,而且这个类还必须负责和其他子系统进行沟通时,针对每一个子系统的需求,主要类就必须增加对应的方法。但是,增加越多的方法就等同于增加类的接口的复杂度。因此每当要使用这个类的方法的时候,就要小心翼翼地从中选择正确的方法,无形之中增加了开发和维护的难度。通过“功能的切分”和“接口的简化”可以减少这类问题的发生,或者运用设计模式来重新规划类,也可以减少不必要的操作接口出现在类中。

最少知识原则(LKP)

   当设计实现一个类时,这个类应该越少使用到其它类提供的功能越好。意思是,当这个类能够只靠本身的“知识”去完成功能的话,那么就相应地减少与其他对象“知识”的依赖度。这样的好处就是减少了这个类与其他类的耦合度(即依赖度),换个角度来看,就是增加了这个类被不同项目复用的可能性,提高类的重用性。

少用继承多用组合原则

  当子类继承一个“接口类”后,新的子类就要负责重新实现接口类中所定义的方法,而且不该额外扩充接口,以符合上述多个设计原则的要求。但是,当系统想要扩充或者增加某一项功能时,让子类继承原来的实现类,却也是最容易实现的方式之一。新增的子类在继承父类后,在子类内增加想要扩充的“功能方法”并加以实现,客户端之后就能直接利用子类对象进行新增功能的调用。

  但是对于客户端而言,当下可能只是需要子类所提供的功能,对父类中一些额外方法并不感兴趣,因为这样会增加开发者挑选方法时的难度。例如,“闹钟类”可以利用继承“时钟类”的方式,获得一个“时间功能”的方法,只要子类本身再另外加上“定时提醒”的功能,就能实现“闹钟功能”的目标。当客户端使用“闹钟类”的时候,可能期待的只不过是设定闹钟时间的方法而已,对于取得当前时间的功能并没有迫切的需求。因此,从“时钟父类”继承而来的方法,对于闹钟的用户来说,可能是多余的。

  如果将设计改为在闹钟的类中声明一个类型为时钟类的“类成员”,那么就可以减少不必要的方法出现在闹钟接口上,也可以减少“闹钟类”的客户端对“时钟类”的依赖。另外,在无法使用多重继承的程序设计语言中(Java、C#等),使用组合的方式会比层层继承的方式更加容易理解和维护,并且对于类的封装也有比较好的表现方式。

总结

  以上只是面向对象设计中的7个基本的设计原则,却也是最重要的原则,只有理解了这些基础的原则之后才能继续更深入地学习GOF所提到的23种设计模式。23种设计模式并不是教条式的规则和框架,他们都是解决问题的方法的概念的呈现。一些优秀的设计方案由于种种原因并没有被GOF写入到23种设计模式中,但这并不意味着它不是设计模式。GOF曾说过“没有规定一定要与书中一模一样的架构才能被称为一种模式。”,比如依赖注入和控制反转就是非常好的、应用非常广泛的一种设计方案。

  另外,对于之后的本系列博客一些DEMO工程,将会同步到Github,点我点我

作者:马三小伙儿 出处:http://www.cnblogs.com/msxh/p/6921679.html 请尊重别人的劳动成果,让分享成为一种美德,欢迎转载。另外,文章在表述和代码方面如有不妥之处,欢迎批评指正。留下你的脚印,欢迎评论!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏编程

Java语言零基础菜鸟入门Java程序学习的要点

一、掌握静态方法和属性 静态方法和属性用于描述某一类对象群体的特征,而不是单个对象的特征。Java中大量应用了静态方法和属性,这是一个通常的技巧。但是这种技巧在...

2080
来自专栏撸码那些事

【抽象那些事】缺失抽象

1373
来自专栏玄魂工作室

Python黑帽编程 2.0 第二章概述

于 20世纪80年代末,Guido van Rossum发明了Python,初衷据说是为了打发圣诞节的无趣,1991年首次发布,是ABC语言的继承,同时也是一...

3637
来自专栏web前端教室

初学js钻太深,不太好

其实我个人觉得新手不太应该追求彻底的学透每一个知识点。因为初学的时候,钻的太深并不太利于对JS有一个整体的理解。反而有可能钻牛角尖。但这种方法和心态却是必须有的...

2046
来自专栏landv

C#本质论第四版-1,抄书才能看下去,不然两三眼就看完了,一摞书都成了摆设。抄下了记忆更深刻

4193
来自专栏有趣的Python

1-玩转数据结构-欢迎学习数据结构

对于数据的存储这个任务,解决方案是很多的。我们需要根据应用的不同,灵活选择最合适的数据结构

2K5
来自专栏码农分享

工厂模式(Factory)

简单工厂和工厂方法这俩个设计模式不做详细介绍,请各位看官自行百度,有大量的解释。再次我简单引用一下其他博客主对这三种模式的理解。

771
来自专栏程序员维他命

出一套 iOS 高级面试题

一千个读者眼中有一千个哈姆雷特,一千名 iOS 程序员心目中就有一千套 iOS 高级面试题。本文就是笔者认为可以用来面试高级 iOS 程序员的面试题。

3912
来自专栏编程

大数据学习,为什么要先学Java?

计算机编程语言有很多,目前用的多的就是Java,C++,Python,PHP等等。目前大多数学习大数据的人都是选择学习Java,那Java到底好在哪呢?为什么学...

3328
来自专栏java思维导图

java基础思维导图,让java不再难懂

思维导图的好处 最近看了一些文章的思维导图,发现思维导图真是个强大的工具。了解了思维导图的作用之后,觉得把它运用到java上应该是个不错的想法,这样回顾知识点的...

5386

扫码关注云+社区