玩编程,你得记住这么一条:只要一门语言是图灵完备的,那么它当然就能够做到任何事。
没错。图灵完备=万能。无非是有时候你需要绕个弯子、有时候又需要额外付出点性能或别的什么代价而已。
C/C++系:函数名本身就是一个函数指针,可以放进函数数组。你的需求已经被直接满足。
所有面向对象语言:多态本来就是用来做这个的。你只要让所有角色都实现同一个接口,使得它们都能提供一个攻击方法即可(注意每个角色的攻击方法可以有不同实现,比如火球、暗影箭之类),然后这样调用就够了:
foreach 角色 in 角色数组: 角色.攻击()
面向过程但又不支持函数指针的老奶奶语言(比如BASIC):你需要使用一个叫做dispatch的思路。
//把技能ID和执行这个技能的函数联系起来 bool dispatchSpell(spellID id) { switch(id){ case spellFireball: return Fireball(); case spellFirebolt: return Firebolt(); //依此类推,列出其他法术 ... //如果法术id非法,返回false default: ASSERT(false); //debug版还是直接崩掉吧 return false; } } //给角色数据结构里面声明一个defaultSpell变量: roleType { ... spellID defaultSpell; ... } //现在,直接循环调用角色数组里面每个角色的defaultSpell: foreach role in rolielist: dispatchSpell(role.defaultSpell);
各种语言的各种“奇技淫巧”,其他答主提到的更多。
总之就一句话,只要语言是图灵完备的,那么你需要的各种程序内效果就一定有办法实现,只是未必那么自动化而已。
或者说,如果你需要自动语法检查之类辅助性功能,那么你或者等编译器支持,或者就只能通过第三方程序实现;但在程序里面,你实现的一切功能,只要它是可计算的,那么就一定有办法实现。
唯一的问题,就是你自己知不知道该怎么实现、知不知道常见问题的更优化的解决方案、能否因地制宜的创造出最适合自己所面对的问题的最优化解决方案——你需要冷静、睿智的通盘考虑问题,千万别陷在局部细节中。尤其不要只看到那些细节上的好处。它们不仅不解决问题,很多时候反而是搅乱项目的行家里手。
举例来说,玩多了面向对象的,很容易直觉的“用继承来消除if语句”;然后呢,不同角色从角色基类继承;法师术士都是施法者,都要从“施法者”继承……如果这样使得你需要写if,那么就说明你需要增加继承层次,直到所有的if被消除……
很遗憾。但这是胡闹。
事实上,鼓吹这些的人压根就没长程序员脑子。
要称得上“程序员脑子”,你需要看透问题,不要浮在“法师术士牧师都是施法者”这样的表面。
事实上,在计算机里面,“施法”是什么呢?
施法是这样一件事:
1、施法往往需要消耗一些资源(魔力、能量、怒气、体力、hp等)
不管这些资源叫什么,它就是一个数字,保存于一个变量;消耗资源就是变量减去一个值。
2、施法可能需要某些先决条件(比如被施法者的状态限制:浮空,特定buf/debuf,和施法者之间的距离等)
3、施法需要角色执行一些动画(骨骼动画,典型如unity的avatar)
4、施法需要一些光影效果(手部发光/气团、面部表情、眼睛发光等)
5、施法会制造一些游戏物体(抛射体、地面动画、粒子效果等)
6、施法会有一些影响范围(单一目标、多目标、传染等)
7、被法术影响的对象会出现一些状态改变(扣血、扣蓝、加血、加速、减速等等,归根结底是修改了角色的某个变量所存储的数值)
……
基本就是这样。
那么,一个施法框架应该是这样:
bool cast(spellID id) { //根据法术id读取规则 spellConfig = loadSpellConfig(id); //执行资源检定操作 resourceCheck(id); //判断先决条件 ... //执行角色动画 ... //处理光影效果 ... //制造法术实体(火球、暴风雪、子弹乃至刀光剑影,全都可以是法术实体) ... //计算影响范围 ... //被击中目标效果处理(身上带火、呼叫、乱跑等) ... //伤害计算 ... }
然后,这些效果可以整理成若干种出来、存入数据库(所谓的技能数据化);那么无论你能想出多少个法术、如何千变万化,这套框架都可以无需修改的支持下来——反正就是那么几个效果的排列组合嘛,至多法阵啥的需要换张图片。那么,以后无论调什么,改数据库就完了,何必动程序代码呢。
类似的,当你不再把法术释放的细节和角色绑定之后,你的要求就更容易完成了:
foreach role in roleList { cast(role.defaultSpell); }
动用某个技术的目的是让程序更好写、更井井有条、更能应对需求变更。
不妨想一想,看看是这个方案更简单、更清爽、更稳固呢;还是那些“面向对象带师”们忽悠你的、用海量继承消除if的方案更不浪费生命。
绝大多数编程语言根本没有你说的“为什么不能把函数当做一个变量”这种问题,这个问题大多数情况下和语言本身没有什么关系,单纯的是个人技艺达不达标的问题。我们以常见的C语言为例,把一个函数当作一个变量使用在日常编程中简直再正常不过了。比如:
#include <stdio.h> #include <unistd.h> struct FuncTable { void (*begin)(void); void (*run)(unsigned int s); void (*end)(void); }; void begin_f() { printf("Begin
"); } void run_f(unsigned int s) { printf("Run %d seconds
", s); sleep(s); } void end_f() { printf("End
"); } int main(int argc, char *argv[]) { struct FuncTable ft = { .begin = begin_f, .run = run_f, .end = end_f, }; ft.begin(); ft.run(3); ft.end(); return 0; }
编译执行:
$ gcc -o mytest mytest.c -Wall $ ./mytest Begin Run 3 seconds End
再说你描述中的例子,又是在纠结if...else的问题。首先面对多英雄不同技能的这样一个需求,我们首先想到的是面向对象的编程思想,即总体英雄角色这个大类以及每个角色不同所继承和派生出的子类以及对象不同。用C++等面向对象的语言我们都知道很容易实现,那要是用不是面向对象语言的C语言呢?
我们举个简单的例子,假设所有英雄都可以做移动Move, 普通攻击Hit,和一个技能SkillA。然后每个不同的英雄可能有不同的技能和攻击范围,比如Karma这个英雄有一个SkillB的特别技能,还有一个叫Jax的英雄攻击都带范围攻击,且他也有一个特别的技能SkillB(区别于Karma的SkillB)。
这样的需求你怎么实现?难道要用一堆if...else吗?
我简单的实现了下面一个例子,来用C语言模拟一次简单的面向对象的编程。首先我们要有一个名为Hero的大类,这个大类里有英雄的名字,有一些功能方法,有可供使用的英雄数据。我们简单的让每个英雄都能有四个基础功能——设置移动/攻击坐标,移动,攻击,基础技能攻击,如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> /* abstract interface declaration */ struct Hero { char name[256]; void *funcTable; void *privateData; }; struct HeroFuncTable { void (*SetTarget)(struct Hero * obj, int x, int y); void (*Move)(struct Hero * obj); void (*Hit)(struct Hero * obj); void (*SkillA)(struct Hero * obj); }; struct HeroPrivateData { int x, y; }; void HeroSetTarget(struct Hero * obj, int newx, int newy) { struct HeroPrivateData * rdata = (struct HeroPrivateData*)obj->privateData; rdata->x = newx; rdata->y = newy; } void HeroMove(struct Hero * obj) { struct HeroPrivateData * rdata = (struct HeroPrivateData*)obj->privateData; printf("%s move to: [%d, %d]
", obj->name, rdata->x, rdata->y); } void HeroHit(struct Hero * obj) { struct HeroPrivateData * rdata = (struct HeroPrivateData*)obj->privateData; printf("%s hit target at: [%d, %d]
", obj->name, rdata->x, rdata->y); } void HeroSkillA (struct Hero * obj) { struct HeroPrivateData * rdata = (struct HeroPrivateData*)obj->privateData; printf("%s is using skillA on: [%d, %d]
", obj->name, rdata->x, rdata->y); }
然后我们现在需要一个叫Karma的英雄,他除了具备基本英雄技能以外还有一个自己特有的SkillB技能:
/* Class Karma */ void KarmaSkillB (struct Hero *obj, int arg) { struct HeroPrivateData * rdata = (struct HeroPrivateData*)obj->privateData; printf("Karma is using skillB * %d at: [%d, %d]
", arg, rdata->x, rdata->y); } struct KarmaFuncTable { void (*SetTarget)(struct Hero * obj, int x, int y); void (*Move)(struct Hero * obj); void (*Hit)(struct Hero * obj); void (*SkillA)(struct Hero * obj); void (*SkillB)(struct Hero * obj, int arg); } karmaFuncTable = { HeroSetTarget, HeroMove, HeroHit, HeroSkillA, KarmaSkillB }; struct Hero * MakeKarma (int initx, int inity) { struct Hero * obj = malloc (sizeof(struct Hero)); struct HeroPrivateData * rdata = malloc (sizeof(struct HeroPrivateData)); strcpy(obj->name, "Karma"); obj->funcTable = (struct KarmaFuncTable*) &karmaFuncTable; obj->privateData = rdata; rdata->x = initx; rdata->y = inity; return obj; }
如上代码,我们让Karma继承所有的Hero的方法和类型,然后在给他提供一个SkillB的特殊技能,最后我们提供一个Karma英雄的构造函数。
下面我们再给出一个Jax英雄,让Jax英雄拥有范围攻击效果,且也有一个特殊技能:
/* Class Jax */ struct JaxPrivateData { int x, y; int range; }; void JaxSetRange(struct Hero * obj, int rg) { struct JaxPrivateData * rdata = (struct JaxPrivateData*)obj->privateData; rdata->range = rg; } void JaxHit(struct Hero * obj) { struct JaxPrivateData * rdata = (struct JaxPrivateData*)obj->privateData; printf("Jax hit target at: %d[%d, %d]
", rdata->range, rdata->x, rdata->y); } void JaxSkillA(struct Hero * obj) { struct JaxPrivateData * rdata = (struct JaxPrivateData*)obj->privateData; printf("Jax is using SkillA at: %d[%d, %d]
", rdata->range, rdata->x, rdata->y); } void JaxSkillB(struct Hero * obj) { struct JaxPrivateData * rdata = (struct JaxPrivateData*)obj->privateData; printf("Jax is using SkillB at: %d[%d, %d]
", rdata->range, rdata->x, rdata->y); } struct JaxFuncTable { void (*SetTarget)(struct Hero * obj, int x, int y); void (*SetRange)(struct Hero * obj, int rg); void (*Move)(struct Hero * obj); void (*Hit)(struct Hero * obj); void (*SkillA)(struct Hero * obj); void (*SkillB)(struct Hero * obj); } jaxFuncTable = { HeroSetTarget, JaxSetRange, HeroMove, JaxHit, JaxSkillA, JaxSkillB }; struct Hero * MakeJax (int initx, int inity, int initr) { struct Hero * obj = malloc (sizeof(struct Hero)); struct JaxPrivateData * rdata = malloc (sizeof(struct JaxPrivateData)); strcpy(obj->name, "Jax"); obj->funcTable = (struct JaxFuncTable*) &jaxFuncTable; obj->privateData = rdata; rdata->x = initx; rdata->y = inity; rdata->range = initr; return obj; }
如上,我们实现了Jax英雄类,我们让他继承了SetTarget和Move的方法,同时因为其特有的范围攻击我们给它提供一个额外的range成员变量,并提供了一个SetRange的方法,接着重构了其普通攻击和技能攻击的方法,让这些攻击都带范围效果,然后还实现了一个他特有的SkillB技能,最后我们给出一个Jax英雄的构造函数。
来让我们测试一下上面的代码:
/* Do some test */ void KarmaDoSomething(struct Hero *h) { struct KarmaFuncTable *f = h->funcTable; f->SetTarget(h, 10, 20); f->Move(h); f->Hit(h); f->SkillA(h); f->SkillB(h, 100); } void JaxDoSomething(struct Hero *h) { struct JaxFuncTable *f = h->funcTable; f->SetTarget(h, 50, 88); f->SetRange(h, 8); f->Move(h); f->Hit(h); f->SkillA(h); f->SkillB(h); } int main(int argc, char *argv[]) { struct Hero * heros[2]; heros[0] = MakeKarma(0, 0); heros[1] = MakeJax(0, 0, 5); printf("-- Welcome Karma do something for us --
"); KarmaDoSomething(heros[0]); printf("
"); printf("-- Welcome Jax do something for us --
"); JaxDoSomething(heros[1]); return 0; }
如上,我们通过funcTable的类型转换,实现一个多态的效果,让Hero去使用它自己的方法集合。我们看一下执行效果:
$ gcc -o hero hero.c -Wall $ ./hero -- Welcome Karma do something for us -- Karma move to: [10, 20] Karma hit target at: [10, 20] Karma is using skillA on: [10, 20] Karma is using skillB * 100 at: [10, 20] -- Welcome Jax do something for us -- Jax move to: [50, 88] Jax hit target at: 8[50, 88] Jax is using SkillA at: 8[50, 88] Jax is using SkillB at: 8[50, 88]
可以看出不同的英雄展现出了不同的动作和技能。
当然这只是我花了二十分钟写的一个极其简单的程序,实际的项目设计肯定要比这个复杂的多,而且也能比我这个写的好的多。我这里只是想告诉一些初学者,很多初学者钻牛角尖的“复杂问题”往往是因为知识还没有学到位而陷入的自我纠结。我已经碰到过很多人以不同形式问过我类似优化if...else...、优化判断条件、优化内存结构……等等等等的问题。这些自己以为自己正在面对架构、优化等问题的问题,很多时候根本不是你以为的做架构和优化的样子。
学习更多的计算机知识,多阅读优秀的项目代码,很多时候回看很多问题时才会知道自己当时根本只是维度还不够高而过早的陷入高维度的纠结而已,包括我自己也是经常这样。
这个问题不应该用面向对象的多态来解决吗?还要自己写一堆的if else?
任何一种现代编程语言,哪怕是c,都有现成的解决方案。
你这种需求,在OO就用多态,在FP就用模式匹配,只有在命令式,才需要if...else。
而if...else和函数能不能当变量用也没有半毛钱关系。在OO和FP的解决方案都不需要把函数当变量来用。本质上来说,你的想法里面两种代码是同构的。因为就算你把函数放个数组里面,这个数组不需要初始化?初始化代码和if else是同构的。
初始化代码可能长这样:
skill[0] = function() { xxx }; skill[1] = function() { xxx }; skill[2] = function() { xxx };
但事实上,这等价于:
if ( a == 0 ) xxx else if ( a == 1 ) xxx else if ( a == 2 ) xxx
可以看出来两者的抽象结构是一样的。
本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度,google,bing,sogou 等
© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有