5

零代码以“王者荣耀”为例解析设计七原则,助你面试拿“五杀”

 2 years ago
source link: https://blog.csdn.net/Czhenya/article/details/117753263
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

所有举例都是王者荣耀相关内容(不玩王者荣耀的同学,看起来稍费劲)。为了增加阅读兴趣和方便掌握这个七大原则,举例和原则的连接,我已经用尽毕生所学。陆陆续续写了一周还多,不喜勿喷哈~ 有收获的同学,记得点个赞再走…

PS:文中涉及到王者荣耀的相关名字部分使用绿色3号字标识,所以有了不知道是什么的小伙伴不用追溯,理解为一个装备名,英雄名或者直接理解为类名都是可以的。


一,单一职责原则

1.1 举例说明: 惩戒上单

时间:某休息日,地点:王者峡谷,人物:惩戒白起

  版本描述:这个版本双烧流上单玩法很流行,这导致很多肉坦上单英雄都愿意携带惩戒,然后出红莲斗篷(日炎)。既能反野加快发育也能提高伤害加成…

1.1

  情景再现:敌我双方拖至20分钟风暴龙王现身,可以说实力相当场面十分焦灼。到了抢夺风暴龙王一局定胜负的局面。话说我方双惩戒+白起长时间团战控制有更大的优势…

局内对话:

  • 打野:对面要打龙逼团了,我绕后找机会切对面射手法师,你们正面拉扯下,白起尝试抢龙
  • 辅助:白起一会打团你直接入场控制开团,我保双C(我方射手法师)
  • 上路白起:好的,龙马上快到斩杀血线了,大家准备… 我入场了,龙没抢到
  • 射手:没事没事,你找机会配合打野切对面鲁班,鲁班Si了就能打
  • 上路白起:打野准备切入,鲁班闪现了我大招距离不够,打野看你的了,鲁班没闪…

5S后,对面凭借风暴龙王Buff和鲁班输出,团灭我方。带好兵线就可以直接推掉我方水晶,取得胜利。

赛后复盘:

  1. 虽然白起携带惩戒,但是并没有抢到龙王。
  2. 也是因为携带惩戒,所以团站也没有控制到对面核心鲁班七号,导致输掉游戏。

但凡这两点能做到任意一点也不至于输掉游戏。

1.2 原则解析: 单一职责

  其实大多数时候,一个位置的英雄简单一些,职责单一一些, 或许是更好的选择。这就和设计模式中的一大原则 —— 单一职责的道理是一样的。

  就一个类而言,应该仅有一个引起它变化的原因,我们在写代码的时候,很自然的就会给一个类加各种各样的功能。比如我们写一款游戏,一般定义一个GameManager这样的类,于是我们就把各种各样代码,像处理逻辑算法啊,访问数据库啊什么的都写在这个类中。这就意味着,只要有需求改动,我们都需要修改这个游戏管理器,这其实是很差的写法,维护麻烦,不能复用,也缺少灵活性。

  我们刚开始学习面向对象的时候,就知道面向对象的好处:可维护、可扩展、可复用、灵活性好。 所以这种写法是需要进行改正的。

  如果一个类承担的职责过多,就等于吧这些职责耦合在一起,一个职责的变化可能削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。

  • 单一职责的定义:

单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。


二,开放封闭原则

2.1 举例说明: 黄刀由来

2.1

黄色打野刀上线也有几个版本了,简单猜测一下它的代码层面是如何实现的。

  • 既然是打野刀,那么也就有打野刀的通用属性(可对野怪释放)-- 可以通过继承实现。
  • 既然是新装备,那么也就有和其他打野刀不同的属性 – 创建自己的类实现。

  像这种修改就符合开闭原则。对扩展开启,对修改封闭。这时候你可能在想,我这不是说一堆废话嘛。新添加了一个装备可不要扩展吗,怎么也不会在红色打野刀类中去写黄色打野刀的逻辑啊…

  确实是这样,可是你想过没有,这是在一个成熟的框架下去添加新装备。若这是刚开始开发的程序呢?我们实现的时候不会将所有的二级打野刀都写在一个类中,然后使用属性或者枚举来区分当前使用的打野刀是什么,然后进行相应的逻辑处理…

2.2 原则解析: 开闭原则

  因为我们在最初写代码的时候,都假设需求不会产生改变。当需求变化时,我们就创建抽象来隔离以后发生同类的变化。 比如原来王者中只有两种类型的打野刀:一个是物理伤害,一个是法术伤害的。其他各种属性都一样,那么此时我们写代码的时候完全可能将这两个打野刀写在一个类中。后来又来一个打野刀,它也是物理伤害的,但是属性从加伤害变成加防御了。

  那么此时我们就需要考虑未来游戏平衡会不会再添加新的打野刀,会不会修改现有单一打野刀的属性和数值…这时候我们的原来写的一个类中实现的两个打野刀,就会自然的演变成一个打野刀基类,两个子类继承的形式。进而有了后续添加打野刀时的添加方式。

  我们在做任何应用的时候,都不要指望一开始时需求确定,就再也不会有修改。既然需求一定会变化,那么如何在面对需求变化时,使得我们的程序可以相对容易的修改,不至于说,新需求一来,我们要删除原来部分代码,重新写一套。这就是开放封闭原则存在的意义。

  对于开发时呈现出频繁变化的那些部分做出抽象,然而,对于程序中的每个部分都可以的进行抽象同样是一种不好的做法。拒绝不成型的抽象和抽象本身一样重要。

   开放-封闭原则是面向对象设计的核心所在。遵循这个原 则可以带来面向对象技术所声称的巨大好处,也就是可维护、可扩 展、可复用、灵活性好。

  • 开闭原则的定义:

开放-封闭原则:是说软件实体(类,模块,函数等等)应该是可以扩展,但是不可修改。


三,里氏代换原则

3.1 举例说明: 吸血之镰

吸血之镰俗称小吸血刀,可合成装备如下:
3.1

由上图我们可以看到小吸血刀的属性:

  • +10 物理攻击
  • +8% 物理吸血

  当我们点击它可合成装备时,可以看到三个装备的属性值都是包含 +物理攻击 和 +百分比物理吸血 的。这就是说明,大件装备由小件装备合成,并且继承了小件装备的属性值(多出来的部分时大件私有的)。

  在游戏中不管你此时购买了末世,泣血,制裁这三个装备中的哪一个,你都获得了其父类小吸血刀的属性值。在程序的角度讲使用到小吸血刀(父类)的代码完全可以被这三个装备(子类)任意一个去替换,并且不会对游戏逻辑产生影响,这就是里氏代换原则了。

3.2 原则解析: 里氏代换

进一步描述:
  子类对象能够替换程序中的父类对象出现的任何对象,并且保证原来的程序逻辑行为不变及正确性不被破坏。这么一说有点类似多态,多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。他是一种代码实现思路。而里氏替换是一种设计原则,是用来指导继承关系中子类该如何设计,子类的设计要保证在替换父类的时候,不改变原有程序逻辑以及不破坏原有程序的正确性。

回到举例:
  若在我们上面的举例中有一个小吸血刀类中(父类)GetAttribute()方法可以返回当前装备的属性,此时父类返回【 +10物理攻击,+8%物理吸血】;在大吸血刀类中(子类)GetAttribute()返回【 +100物理攻击,+25%物理吸血】,那么此时这个子类的设计就违背了里氏替换原则。

  一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且察觉不出父类对象和子类对象的区别;也就是说,在程序里面,把父类都替换成它的子类,程序的行为没有变化;简单地说,子类型必须能够替换掉它们的父类型。

  • 里氏原则的定义:

里氏代换原则:子类型必须能够替换掉他们的父类型。


四,迪米特法则

又称:最少知道法则

4.1 举例说明: 妲己抓人

时间:某休息日,地点:王者峡谷,人物:亚瑟,妲己

妲己在中路清完线,来上路帮助亚瑟抓人。
4.1

妲己一连发起三个快捷消息:

  1. 二技能已经好了
  2. 大招还有3秒

亚瑟回复快捷消息:

  3秒后,妲己走到上路草丛埋伏。亚瑟卖血假装打不过,撤向妲己所在草丛,妲己一套二三一,配合亚瑟收下对面上路人头。

  对于亚瑟来说,他只知道妲己在准备来上路抓人,技能马上好了,这两个消息,他并不知道妲己技能当前加点等级,也不知道妲己还差多少钱可以出下个装备。这些妲己的 “ 内部实现 ",亚瑟都不知道,他也不需要知道。这就是迪米特法则。

4.2 原则解析: 迪米特法则

  “迪米特法则首先强调的是前提是在类的结构设计上,每一个类都应当尽量降低成员的访问权限;也就是说,一个类包装好自己的private状态,不需要别的类知道的字段或行为就不要公开”

迪米特法则其根本思想是强调了类之间的松耦合。

  • 迪米特法则的定义:

迪米特法则:如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某个方法的话,可以通过第三者转发这个调用。


五,接口分离原则

5.1 如何理解接口隔离原则?

理解“接口隔离原则”的重点是理解其中“接口”二字:

  • 若把“接口”理解为面向对象中的接口,那接口的设计要尽量单一,不要让实现类有用不到的接口函数。
    比如说:A类实现I接口,I接口中有X(),Y()两个函数;若A类只需要用X(),那么这样的设计是不合理的。
  • 若把“接口”理解为一组接口的集合,可以是某个类库的接口。如果使用的类只需要调用其中的部分接口,那么我们需要将这部分接口隔离出来,单独给部分调用者使用。

5.2 与单一职责原则的区别

  • 单一职责针对的是模块、类、接口的设计。接口隔离原则相对于单一职责原则,一方面更侧重接口的设计,另一方面它的思考角度也是不同的
  • 接口隔离原则则提供了一种判断接口的职责是否单一的标准:通过调用者如何使用接口间接地判定。如果调用者只使用部分接口或部分接口的功能,那接口设计的就不够单一。

接口隔离原则:不应该强迫对象依赖它不需要的接口。


六,依赖倒置原则

6.1 举例说明: 电脑主板

  昨天公司美术妹子的电脑用着用着突然蓝屏了,来找我帮忙看看咋回事。根据我的经验是内存条坏了,于是我打开机箱拆下内存条,更换插槽各种操作,最终确定了就是其中的一个内存条坏了。换了个新的内存条,电脑成功启动。
9cdfd3363bd9780e261843d387d8d201.gif#pic_center

  能这么轻松的解决问题还是要归功于PC的易插拔设计,不管是内存、显卡、硬盘等任何部件坏了,我们只需更换坏的那个就可以了。**这种易插拔在面向对象中就是强内聚,低耦合。

   ** 因为无论是那个厂家制造的这内存条,也不管它的内部实现是什么样的,它最终都需要支持主板的插槽。这就是针对接口设计。若针对实现设计,那么很打可能我们的内存条坏了,也需要更换对应的主板。

6.2 原则解析: 依赖倒置

  依赖倒置设计理念: 相对于细节的多变性,抽象的东西要文档的多。以抽象为基础搭建的架构比细节为基础搭建的架构要稳定的多。依赖倒置的中心思想是面向接口编程。

  面向过程开发时,为了使得常用的代码可以复用,一般都会吧这些常用的代码写成许许多多函数的程序库,这样我们在做新项目时,去调用这些低层的函数就可以了。

  • 依赖倒置的定义:

依赖倒置原则:
A.高层模块不应该依赖底层模块。两个都应该依赖抽象。
B.抽象不应该依赖细节。细节应该依赖抽象。


七,合成/聚合复用原则

之前我的理解是:合成复用 和 聚合复用 是两个名字一个意思。后来具体学习了才知道,其实并不是这样,可以说这是两种相近的设计模式。到底是怎么回事? 往下看看吧~

7.1 举例说明: 兵线队列

合成和聚合都是关联的特殊种类:

  • 聚合表示一种弱的‘拥有’关系,体现的是A对象包含B对象,但B对象不是A的对象的一部分
  • 合成则表示一种强的‘拥有’关系,体现了严格的部分和整体的关系,部分和整体的生命周期是一致的。

7.1

  比方说:王者荣耀中的兵线,每一波兵线都由多个小兵组成,每个小兵都属于一队兵线,一队兵线和多个小兵是聚合关系。 而每个小兵都有一个的武器(攻击类),武器和小兵是部分与整体的关系,并且他们的生命周期是相同的,于是小兵和武器就是合成关系。

7.2 原则解析: 合成/聚合复用

  合成/聚合复用的好处:优先使用对象的合成/聚合将有助于我们保存封装每个类,并被集中在单个任务上。这样类和类的继承层次会保持较小规模,并且不太可能增长为不可控制打庞然大物。

  • 合成/聚合复用原则的定义:

合成/聚合复用:尽量使用合成/聚合,尽量不要使用类的继承。


  文中有两个原则的举例说明,我实在是没想出怎么用王者荣耀相关内容举例分析。若你有好的想法欢迎评论区补充。

学习完了休息一下吧,走打王者去~
王者


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK