零代码以“王者荣耀”为例解析设计七原则
摘要:一文助你深入理解设计模式七大原则。
本文分享自华为云社区《零代码以“王者荣耀”为例解析设计七原则,助你面试拿“五杀”》,作者: 陈言必行 。
前言
所有举例都是王者荣耀相关内容(不玩王者荣耀的同学,看起来稍费劲)。为了增加阅读兴趣和方便掌握这个七大原则,举例和原则的连接,我已经用尽毕生所学。陆陆续续写了一周还多,不喜勿喷哈~ 有收获的同学,记得点个赞再走…
PS:文中涉及到王者荣耀的相关名字部分使用绿色 3 号字标识,所以有了不知道是什么的小伙伴不用追溯,理解为一个装备名,英雄名或者直接理解为类名都是可以的。
一、单一职责原则
1、举例说明: 惩戒上单
时间:某休息日,地点:王者峡谷,人物:惩戒白起
版本描述:这个版本双烧流上单玩法很流行,这导致很多肉坦上单英雄都愿意携带惩戒,然后出红莲斗篷(日炎)。既能反野加快发育也能提高伤害加成…
情景再现:敌我双方拖至 20 分钟风暴龙王现身,可以说实力相当场面十分焦灼。到了抢夺风暴龙王一局定胜负的局面。话说我方双惩戒+白起长时间团战控制有更大的优势…
局内对话:
打野:对面要打龙逼团了,我绕后找机会切对面射手法师,你们正面拉扯下,白起尝试抢龙
辅助:白起一会打团你直接入场控制开团,我保双 C(我方射手法师)
上路白起:好的,龙马上快到斩杀血线了,大家准备… 我入场了,龙没抢到
射手:没事没事,你找机会配合打野切对面鲁班,鲁班 Si 了就能打
上路白起:打野准备切入,鲁班闪现了我大招距离不够,打野看你的了,鲁班没闪…
5S 后,对面凭借风暴龙王 Buff 和鲁班输出,团灭我方。带好兵线就可以直接推掉我方水晶,取得胜利。
赛后复盘:
1. 虽然白起携带惩戒,但是并没有抢到龙王。
2. 也是因为携带惩戒,所以团站也没有控制到对面核心鲁班七号,导致输掉游戏。
但凡这两点能做到任意一点也不至于输掉游戏。
2、原则解析: 单一职责
其实大多数时候,一个位置的英雄简单一些,职责单一一些, 或许是更好的选择。这就和设计模式中的一大原则 —— 单一职责的道理是一样的。
就一个类而言,应该仅有一个引起它变化的原因,我们在写代码的时候,很自然的就会给一个类加各种各样的功能。比如我们写一款游戏,一般定义一个 GameManager 这样的类,于是我们就把各种各样代码,像处理逻辑算法啊,访问数据库啊什么的都写在这个类中。这就意味着,只要有需求改动,我们都需要修改这个游戏管理器,这其实是很差的写法,维护麻烦,不能复用,也缺少灵活性。
我们刚开始学习面向对象的时候,就知道面向对象的好处:可维护、可扩展、可复用、灵活性好。 所以这种写法是需要进行改正的。
如果一个类承担的职责过多,就等于吧这些职责耦合在一起,一个职责的变化可能削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。
单一职责的定义:
单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。
二、开放封闭原则
1、举例说明: 黄刀由来
黄色打野刀上线也有几个版本了,简单猜测一下它的代码层面是如何实现的。
既然是打野刀,那么也就有打野刀的通用属性(可对野怪释放)-- 可以通过继承实现。
既然是新装备,那么也就有和其他打野刀不同的属性 – 创建自己的类实现。
像这种修改就符合开闭原则。对扩展开启,对修改封闭。这时候你可能在想,我这不是说一堆废话嘛。新添加了一个装备可不要扩展吗,怎么也不会在红色打野刀类中去写黄色打野刀的逻辑啊…
确实是这样,可是你想过没有,这是在一个成熟的框架下去添加新装备。若这是刚开始开发的程序呢?我们实现的时候不会将所有的二级打野刀都写在一个类中,然后使用属性或者枚举来区分当前使用的打野刀是什么,然后进行相应的逻辑处理…
2、原则解析: 开闭原则
因为我们在最初写代码的时候,都假设需求不会产生改变。当需求变化时,我们就创建抽象来隔离以后发生同类的变化。 比如原来王者中只有两种类型的打野刀:一个是物理伤害,一个是法术伤害的。其他各种属性都一样,那么此时我们写代码的时候完全可能将这两个打野刀写在一个类中。后来又来一个打野刀,它也是物理伤害的,但是属性从加伤害变成加防御了。
那么此时我们就需要考虑未来游戏平衡会不会再添加新的打野刀,会不会修改现有单一打野刀的属性和数值…这时候我们的原来写的一个类中实现的两个打野刀,就会自然的演变成一个打野刀基类,两个子类继承的形式。进而有了后续添加打野刀时的添加方式。
我们在做任何应用的时候,都不要指望一开始时需求确定,就再也不会有修改。既然需求一定会变化,那么如何在面对需求变化时,使得我们的程序可以相对容易的修改,不至于说,新需求一来,我们要删除原来部分代码,重新写一套。这就是开放封闭原则存在的意义。
对于开发时呈现出频繁变化的那些部分做出抽象,然而,对于程序中的每个部分都可以的进行抽象同样是一种不好的做法。拒绝不成型的抽象和抽象本身一样重要。
开放-封闭原则是面向对象设计的核心所在。遵循这个原 则可以带来面向对象技术所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。
开闭原则的定义:
开放-封闭原则:是说软件实体(类,模块,函数等等)应该是可以扩展,但是不可修改。
三、里氏代换原则
1、举例说明: 吸血之镰
吸血之镰俗称小吸血刀,我们可以看到小吸血刀的属性:
+10 物理攻击
+8% 物理吸血
当我们点击它可合成装备时,可以看到三个装备的属性值都是包含 +物理攻击 和 +百分比物理吸血 的。这就是说明,大件装备由小件装备合成,并且继承了小件装备的属性值(多出来的部分时大件私有的)。
在游戏中不管你此时购买了末世,泣血,制裁这三个装备中的哪一个,你都获得了其父类小吸血刀的属性值。在程序的角度讲使用到小吸血刀(父类)的代码完全可以被这三个装备(子类)任意一个去替换,并且不会对游戏逻辑产生影响,这就是里氏代换原则了。
2、原则解析: 里氏代换
进一步描述:
子类对象能够替换程序中的父类对象出现的任何对象,并且保证原来的程序逻辑行为不变及正确性不被破坏。这么一说有点类似多态,多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。他是一种代码实现思路。而里氏替换是一种设计原则,是用来指导继承关系中子类该如何设计,子类的设计要保证在替换父类的时候,不改变原有程序逻辑以及不破坏原有程序的正确性。
回到举例:
若在我们上面的举例中有一个小吸血刀类中(父类)GetAttribute()方法可以返回当前装备的属性,此时父类返回【 +10 物理攻击,+8%物理吸血】;在大吸血刀类中(子类)GetAttribute()返回【 +100 物理攻击,+25%物理吸血】,那么此时这个子类的设计就违背了里氏替换原则。
一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且察觉不出父类对象和子类对象的区别;也就是说,在程序里面,把父类都替换成它的子类,程序的行为没有变化;简单地说,子类型必须能够替换掉它们的父类型。
里氏原则的定义:
里氏代换原则:子类型必须能够替换掉他们的父类型。
四、迪米特法则
又称:最少知道法则
1、举例说明: 妲己抓人
时间:某休息日,地点:王者峡谷,人物:亚瑟,妲己
妲己在中路清完线,来上路帮助亚瑟抓人。
妲己一连发起三个快捷消息:
1. 发起进攻
2. 二技能已经好了
3. 大招还有 3 秒
亚瑟回复快捷消息:
收到
3 秒后,妲己走到上路草丛埋伏。亚瑟卖血假装打不过,撤向妲己所在草丛,妲己一套二三一,配合亚瑟收下对面上路人头。
对于亚瑟来说,他只知道妲己在准备来上路抓人,技能马上好了,这两个消息,他并不知道妲己技能当前加点等级,也不知道妲己还差多少钱可以出下个装备。这些妲己的“ 内部实现 ",亚瑟都不知道,他也不需要知道。这就是迪米特法则。
2、原则解析: 迪米特法则
“迪米特法则首先强调的是前提是在类的结构设计上,每一个类都应当尽量降低成员的访问权限;也就是说,一个类包装好自己的 private 状态,不需要别的类知道的字段或行为就不要公开”
迪米特法则其根本思想是强调了类之间的松耦合。
迪米特法则的定义:
迪米特法则:如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某个方法的话,可以通过第三者转发这个调用。
五、接口分离原则
1、如何理解接口隔离原则?
理解“接口隔离原则”的重点是理解其中“接口”二字:
若把“接口”理解为面向对象中的接口,那接口的设计要尽量单一,不要让实现类有用不到的接口函数。
比如说:A 类实现 I 接口,I 接口中有 X(),Y()两个函数;若 A 类只需要用 X(),那么这样的设计是不合理的。
若把“接口”理解为一组接口的集合,可以是某个类库的接口。如果使用的类只需要调用其中的部分接口,那么我们需要将这部分接口隔离出来,单独给部分调用者使用。
2、与单一职责原则的区别
单一职责针对的是模块、类、接口的设计。接口隔离原则相对于单一职责原则,一方面更侧重接口的设计,另一方面它的思考角度也是不同的
接口隔离原则则提供了一种判断接口的职责是否单一的标准:通过调用者如何使用接口间接地判定。如果调用者只使用部分接口或部分接口的功能,那接口设计的就不够单一。
接口隔离原则:不应该强迫对象依赖它不需要的接口。
六、依赖倒置原则
1、举例说明: 电脑主板
昨天公司美术妹子的电脑用着用着突然蓝屏了,来找我帮忙看看咋回事。根据我的经验是内存条坏了,于是我打开机箱拆下内存条,更换插槽各种操作,最终确定了就是其中的一个内存条坏了。换了个新的内存条,电脑成功启动。
能这么轻松的解决问题还是要归功于 PC 的易插拔设计,不管是内存、显卡、硬盘等任何部件坏了,我们只需更换坏的那个就可以了。这种易插拔在面向对象中就是强内聚,低耦合。
因为无论是那个厂家制造的这内存条,也不管它的内部实现是什么样的,它最终都需要支持主板的插槽。这就是针对接口设计。若针对实现设计,那么很打可能我们的内存条坏了,也需要更换对应的主板。
2、原则解析: 依赖倒置
依赖倒置设计理念: 相对于细节的多变性,抽象的东西要文档的多。以抽象为基础搭建的架构比细节为基础搭建的架构要稳定的多。依赖倒置的中心思想是面向接口编程。
面向过程开发时,为了使得常用的代码可以复用,一般都会吧这些常用的代码写成许许多多函数的程序库,这样我们在做新项目时,去调用这些低层的函数就可以了。
依赖倒置的定义:
依赖倒置原则:A.高层模块不应该依赖底层模块。两个都应该依赖抽象。B.抽象不应该依赖细节。细节应该依赖抽象。
七、合成/聚合复用原则
之前我的理解是:合成复用 和 聚合复用 是两个名字一个意思。后来具体学习了才知道,其实并不是这样,可以说这是两种相近的设计模式。到底是怎么回事?往下看看吧~
1、举例说明: 兵线队列
合成和聚合都是关联的特殊种类:
聚合表示一种弱的‘拥有’关系,体现的是 A 对象包含 B 对象,但 B 对象不是 A 的对象的一部分
合成则表示一种强的‘拥有’关系,体现了严格的部分和整体的关系,部分和整体的生命周期是一致的。
比方说:王者荣耀中的兵线,每一波兵线都由多个小兵组成,每个小兵都属于一队兵线,一队兵线和多个小兵是聚合关系。而每个小兵都有一个的武器(攻击类),武器和小兵是部分与整体的关系,并且他们的生命周期是相同的,于是小兵和武器就是合成关系。
2、原则解析: 合成/聚合复用
合成/聚合复用的好处:优先使用对象的合成/聚合将有助于我们保存封装每个类,并被集中在单个任务上。这样类和类的继承层次会保持较小规模,并且不太可能增长为不可控制打庞然大物。
合成/聚合复用原则的定义:
合成/聚合复用:尽量使用合成/聚合,尽量要使用类的继承。
版权声明: 本文为 InfoQ 作者【华为云开发者社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/8ecf683229b9339dc4e3311ea】。文章转载请联系作者。
评论