首页 > 编程知识 正文

ecs框架思想,oop是啥

时间:2023-05-03 11:18:22 阅读:13182 作者:4066

背景规则现在,公司的用户中心需要根据用户的会员等级进行不同程度的折扣,提出了会员等级越高折扣引力越大的需求。 具体规则如下:

青铜会员,折扣9.9折金牌会员,折扣8.8折白金会员,折扣6.6折; 钻石会员,折扣5折。 OOP实现:对于熟悉面向对象的编程的学生来说,一个相对简单的实现是跨类继承关系。 这里省略了部分非核心代码。

publicabstractclassmember { publicabstractdoublediscount (doublesourceprice ) }; } publicclassbronzememberextendsmember { @ overridepublicdoublediscount (doublesourceprice ) {返回源price * 9.9/10; } publicclasssilvermemberextendsmember { @ overridepublicdoublediscount (doublesourceprice ) {返回源price * 8.8/10 } publicclassgoldmemberextendsmember { @ overridepublicdoublediscount (doublesourceprice ) {返回源price * 6.6/10; } publicclassdiamondmemberextendsmember { @ overridepublicdoublediscount (doublesourceprice ) {返回源优先级*5/10 }

publicvoiddiscounttest ((memberbronzemember=newbronzemember ); membersilvermember=newsilvermember (; 成员gold member=new gold member (; memberdiamondmember=newdiamondmember (; 双源价格=10d; assert.assert equals (bronze member.discount ) sourceprice ),9.9,0 ); assert.assert equals (silver member.discount (源价格),8.8,0 ); assert.assert equals (gold member.discount ) sourceprice ),6.6,0 ); assert.assert equals (diamond member.discount ) sourceprice ),5,0 ); }上述代码比较简单,所以不太说明。

OOP代码的设计缺陷知识级和操作级对象混用

知识层面的客体定义了对操作层面客体的合法配置,并根据相应的规则进行不同的约束;

操作级别的对象定义了模型经常变化的部分。

如上例所示,提取知识级别的公共对象rule作为规则的父类,然后使用具体规则:

折扣规则,提取为折扣规则; 点规则,提取为点规则;

构建模型的规则:抽离可变因素,减少模型的变动,尽量将变化集中在更少的地方。

对象继承导致代码强依赖父类逻辑,违反开闭原则Open-Closed Principle(OCP)

开闭原则(OCP )规定:“对象应该对扩展开放,对修改应该封闭。” 继承可以通过子类扩展新行为,但一个更改可能会影响所有对象,因为子类可能直接依赖于父类的实现。 在本例中,需要向会员添加积分属性。 根据等级制度的不同,不同等级的人在购买商品后会赠送额外的积分。

需要修改类似于以下内容的代码:

向成员添加“点”属性

然后修改所有会员的实现类,添加实现积分的方法publicclassbronzememberextendsmember { @ overridepublicdoublediscount (doublesourceprice ) }@Overridepublic int addPoint () {return this.point 10; }在复杂的软件中,为什么为了不违背OCP而建议“尽量”? 最核心的原因是现有逻辑的更改会影响现有代码,可能会产生意外的影响。 虽然这种风险只能通过完整的单元测试覆盖率来保障,但在实际开发中很难保障单个测试的覆盖率。 OCP原则可以尽可能避免这种风险,如果新行为只能通过新的字段/方法实现,旧代码的行为当然不会改变。

继承可以扩展到Open for extension,但很难关闭。 所以今天要解决o

CP的主要方法是通过Composition-over-inheritance,即通过组合来做到扩展性,而不是通过继承。

多对象行为类似,导致代码重复

当我们有不同的对象,但又有相同或类似的行为时,OOP会不可避免的导致代码的重复。就像上面例子中,我们每增加一个统一的行为,都需要在所有继承了Member类的实现类中重写这个方法,导致每个类都需要去改动。

问题总结

在这个案例里虽然从直觉来看OOP的逻辑很简单,但如果你的业务比较复杂,未来会有大量的业务规则变更时,简单的OOP代码会在后期变成复杂的一团浆糊,逻辑分散在各地,缺少全局视角,各种规则的叠加会触发bug。有没有感觉似曾相识?对的,电商体系里的优惠、交易等链路经常会碰到类似的坑。而这类问题的核心本质在于:

业务规则的归属到底是对象的“行为”还是独立的”规则对象“?业务规则之间的关系如何处理?通用“行为”应该如何复用和维护?Entity-Component-System(ECS)架构 ECS介绍

ECS架构模式是其实是一个很老的游戏架构设计,最早应该能追溯到《地牢围攻》的组件化设计,但最近因为Unity的加入而开始变得流行(比如《守望先锋》就是用的ECS)。要很快的理解ECS架构的价值,我们需要理解一个游戏代码的核心问题:

性能:游戏必须要实现一个高的渲染率(60FPS),也就是说整个游戏世界需要在1/60s(大概16ms)内完整更新一次(包括物理引擎、游戏状态、渲染、AI等)。而在一个游戏中,通常有大量的(万级、十万级)游戏对象需要更新状态,除了渲染可以依赖GPU之外,其他的逻辑都需要由CPU完成,甚至绝大部分只能由单线程完成,导致绝大部分时间复杂场景下CPU(主要是内存到CPU的带宽)会成为瓶颈。在CPU单核速度几乎不再增加的时代,如何能让CPU处理的效率提升,是提升游戏性能的核心。代码组织:如同第一章讲的案例一样,当我们用传统OOP的模式进行游戏开发时,很容易就会陷入代码组织上的问题,最终导致代码难以阅读,维护和优化。可扩展性:这个跟上一条类似,但更多的是游戏的特性导致:需要快速更新,加入新的元素。一个游戏的架构需要能通过低代码、甚至0代码的方式增加游戏元素,从而通过快速更新而留住用户。如果每次变更都需要开发新的代码,测试,然后让用户重新下载客户端,可想而知这种游戏很难在现在的竞争环境下活下来。

而ECS架构能很好的解决上面的几个问题,ECS架构主要分为:

Entity:用来代表任何一个游戏对象,但是在ECS里一个Entity最重要的仅仅是他的EntityID,一个Entity里包含多个ComponentComponent:是真正的数据,ECS架构把一个个的实体对象拆分为更加细化的组件,比如位置、素材、状态等,也就是说一个Entity实际上只是一个Bag of Components。System(或者ComponentSystem,组件系统):是真正的行为,一个游戏里可以有很多个不同的组件系统,每个组件系统都只负责一件事,可以依次处理大量的相同组件,而不需要去理解具体的Entity。所以一个ComponentSystem理论上可以有更加高效的组件处理效率,甚至可以实现并行处理,从而提升CPU利用率。

ECS的一些核心性能优化包括将同类型组件放在同一个Array中,然后Entity仅保留到各自组件的pointer,这样能更好的利用CPU的缓存,减少数据的加载成本,以及SIMD的优化等。

ECS架构分析

组件化

在软件系统里,我们通常将复杂的大系统拆分为独立的组件,来降低复杂度。比如网页里通过前端组件化降低重复开发成本,微服务架构通过服务和数据库的拆分降低服务复杂度和系统影响面等。但是ECS架构把这个走到了极致,即每个对象内部都实现了组件化。通过将一个游戏对象的数据和行为拆分为多个组件和组件系统,能实现组件的高度复用性,降低重复开发成本。

行为抽离

这个在游戏系统里有个比较明显的优势。如果按照OOP的方式,一个游戏对象里可能会包括移动代码、战斗代码、渲染代码、AI代码等,如果都放在一个类里会很长,且很难去维护。通过将通用逻辑抽离出来为单独的System类,可以明显提升代码的可读性。另一个好处则是抽离了一些和对象代码无关的依赖,比如上文的delta,这个delta如果是放在Entity的update方法,则需要作为入参注入,而放在System里则可以统一管理。在第一章的有个问题,到底是应该Player.attack(monster) 还是 Monster.receiveDamage(Weapon, Player)。在ECS里这个问题就变的很简单,放在CombatSystem里就可以了。

数据驱动

即一个对象的行为不是写死的而是通过其参数决定,通过参数的动态修改,就可以快速改变一个对象的具体行为。在ECS的游戏架构里,通过给Entity注册相应的Component,以及改变Component的具体参数的组合,就可以改变一个对象的行为和玩法,比如创建一个水壶+爆炸属性就变成了“爆炸水壶”、给一个自行车加上风魔法就变成了飞车等。在有些Rougelike游戏中,可能有超过1万件不同类型、不同功能的物品,如果这些不同功能的物品都去单独写代码,可能永远都写不完,但是通过数据驱动+组件化架构,所有物品的配置最终就是一张表,修改也极其简单。这个也是组合胜于继承原则的一次体现。

ECS架构改造

定义规则:

public class Rule {private String name;}public class DiscountRule extends Rule {private double discount;public double getDiscount() {return discount;}}

定义entity:

public class Member {private Map<String, Handler> components = new HashMap<>(16); //存储实体规则对应的具体处理器private MemberId memberId;private class MemberId {}public Map<String, Handler> getComponents() {return components;}}

处理器:

public interface Handler {double discountHandle(double sourcePrice, Rule rule);}//折扣处理器public class DiscountHandler implements Handler {@Overridepublic double discountHandle(double sourcePrice, Rule rule) {if (rule instanceof DiscountRule) {return sourcePrice * ((DiscountRule) rule).getDiscount();}return sourcePrice;}}

装配规则器:

public class Assember {public List<Handler> matchHandler(Rule rule, Map<String, Handler> handlerMap) {List<Handler> handlers = new ArrayList<>();rule.getRuleKeys().forEach(e -> {if (handlerMap.get(e) != null) {handlers.add(handlerMap.get(e));}});return handlers;}}

打折系统:

public class DiscountSystem {List<Handler> handlers;public double discount(double sourcePrice, Rule rule) {double targetPrice = sourcePrice;for (Handler h : handlers) {targetPrice = h.discountHandle(targetPrice, rule);}return targetPrice;}}

采用这种方式的架构,我们需要增加积分赠送的需求,现在就只需要增加积分规则和增加积分处理的类就行了。无需改动原来的类,将模型可变都集中在一起,最小化模型的可变部分。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。