封装依赖对象组合行为的优雅方式及多形状碰撞检测策略设计
这两个问题刚好是图形/游戏开发里非常经典的设计场景,我来分享下我在实际项目中用过的优雅解决方案:
一、封装依赖关联对象组合行为的优雅方式
首先,这类场景核心要解决的是别让主对象变成“大杂烩”,同时让行为可以灵活组合、动态替换。我最推荐的是基于接口抽象的委托模式,完全贴合开闭原则:
- 第一步:把需要组合的行为抽成独立接口。比如你的物体有碰撞、移动两种核心行为,就定义
CollisionBehavior(包含碰撞检测方法)和MovementBehavior(包含位置更新方法)。 - 第二步:主对象(比如
GameObject)只持有这些接口的实例,而不是具体实现类。比如在GameObject里声明private CollisionBehavior collisionBehavior;,完全不关心它是圆形碰撞还是多边形碰撞。 - 第三步:主对象的相关方法直接“甩锅”给行为实例。比如
GameObject的checkCollision(GameObject other)方法里,直接返回collisionBehavior.checkCollision(this, other);,自己只做协调工作。 - 第四步:具体行为实现独立编写,比如
ShapeCollisionBehavior、BoundingBoxCollisionBehavior,需要时直接注入到GameObject中就行。
这种设计的好处太明显了:
- 行为和主对象彻底解耦,新增行为根本不需要改
GameObject的代码,完全符合开闭原则。 - 同一个对象可以动态切换行为(比如某个物体从圆形碰撞切换到多边形碰撞),只需要替换对应的行为实例。
- 代码职责清晰,每个行为类只干自己的事,维护和测试都方便。
举个简化的Java代码片段(其他语言思路完全一致):
// 抽象碰撞行为接口 public interface CollisionBehavior { boolean checkCollision(GameObject self, GameObject other); } // 主游戏对象类 public class GameObject { private CollisionBehavior collisionBehavior; private Shape shape; public GameObject(CollisionBehavior behavior, Shape shape) { this.collisionBehavior = behavior; this.shape = shape; } public boolean checkCollision(GameObject other) { return collisionBehavior.checkCollision(this, other); } // 提供形状的访问方法 public Shape getShape() { return shape; } } // 基于形状的碰撞行为实现 public class ShapeBasedCollisionBehavior implements CollisionBehavior { private final CollisionDetector detector = new CollisionDetector(); @Override public boolean checkCollision(GameObject self, GameObject other) { return detector.check(self.getShape(), other.getShape()); } }
二、多形状碰撞检测的实现策略(低重复、易扩展)
这个场景的核心痛点是避免写一堆重复的if-else或者O(n²)的代码,同时新增形状时不用动现有代码。我用过最优雅的方案是双重分派(Double Dispatch)+ 抽象形状基类,本质是访问者模式的实战应用:
具体实现步骤:
定义抽象形状基类:
所有形状都继承这个基类,只需要一个接受“碰撞访问者”的方法:public abstract class Shape { public abstract <T> T accept(ShapeVisitor<T> visitor); }定义形状访问者接口:
这个接口为每个具体形状定义一个访问方法,返回值可以是碰撞检测的结果(boolean):public interface ShapeVisitor<T> { T visitCircle(Circle circle); T visitPolygon(Polygon polygon); T visitLine(Line line); // 以后新增形状,就在这里加对应的visit方法就行 }实现具体形状类:
每个形状实现accept方法,调用访问者对应的visit方法,把自己传进去:public class Circle extends Shape { public float x, y, radius; @Override public <T> T accept(ShapeVisitor<T> visitor) { return visitor.visitCircle(this); } } public class Polygon extends Shape { public List<Point> vertices; @Override public <T> T accept(ShapeVisitor<T> visitor) { return visitor.visitPolygon(this); } }实现核心碰撞检测器:
这是整个逻辑的核心,通过双重分派来处理任意形状对的碰撞:public class CollisionDetector { public boolean check(Shape a, Shape b) { // 第一次分派:让a自己决定怎么处理和b的碰撞 return a.accept(new ShapeVisitor<Boolean>() { @Override public Boolean visitCircle(Circle circleA) { // 第二次分派:让b自己决定怎么和circleA碰撞 return b.accept(new ShapeVisitor<Boolean>() { @Override public Boolean visitCircle(Circle circleB) { return checkCircleCircle(circleA, circleB); } @Override public Boolean visitPolygon(Polygon polygon) { return checkCirclePolygon(circleA, polygon); } @Override public Boolean visitLine(Line line) { return checkCircleLine(circleA, line); } }); } @Override public Boolean visitPolygon(Polygon polygonA) { return b.accept(new ShapeVisitor<Boolean>() { @Override public Boolean visitCircle(Circle circle) { // 复用圆和多边形的碰撞逻辑,只需要调换参数 return checkCirclePolygon(circle, polygonA); } @Override public Boolean visitPolygon(Polygon polygonB) { return checkPolygonPolygon(polygonA, polygonB); } @Override public Boolean visitLine(Line line) { return checkPolygonLine(polygonA, line); } }); } @Override public Boolean visitLine(Line lineA) { return b.accept(new ShapeVisitor<Boolean>() { @Override public Boolean visitCircle(Circle circle) { return checkCircleLine(circle, lineA); } @Override public Boolean visitPolygon(Polygon polygon) { return checkPolygonLine(polygon, lineA); } @Override public Boolean visitLine(Line lineB) { return checkLineLine(lineA, lineB); } }); } }); } // 具体的碰撞算法,每个形状对只写一次 private boolean checkCircleCircle(Circle a, Circle b) { float dx = a.x - b.x; float dy = a.y - b.y; float distanceSquared = dx*dx + dy*dy; float radiusSum = a.radius + b.radius; return distanceSquared <= radiusSum * radiusSum; } private boolean checkCirclePolygon(Circle circle, Polygon polygon) { // 这里实现圆和多边形的碰撞逻辑,比如用分离轴定理 // ... } // 其他形状对的碰撞方法同理... }
这个方案的优势:
- 极低的代码重复:每个形状对的碰撞逻辑只需要实现一次,比如圆和多边形的逻辑,在圆访问多边形和多边形访问圆时可以直接复用,完全不用重复写。
- 超级容易扩展:新增一个形状(比如
Rectangle)时,只需要做3件事:- 新增
Rectangle类继承Shape,实现accept方法。 - 在
ShapeVisitor接口里加visitRectangle(Rectangle rectangle)方法。 - 在
CollisionDetector的各个内部访问者里加对应的处理逻辑,再实现checkRectangleX的具体算法。
完全不需要修改现有形状的代码,完美符合开闭原则。
- 新增
- 逻辑清晰易维护:所有碰撞逻辑都集中在
CollisionDetector里,形状类只负责存储自身数据,不需要关心碰撞逻辑,后期维护和测试都非常方便。
最后补个小技巧:可以把通用的碰撞算法(比如分离轴定理)抽成独立的工具类,让CollisionDetector的代码更简洁,也方便在其他地方复用。
内容的提问来源于stack exchange,提问作者MonkeyKhan
相关产品推荐
相关产品推荐

