关于oop:管理对象间关系


Managing inter-object relationships

如何为对象编写特殊情况代码?

例如,假设我正在编码一个RPG——有n=5个类。矩阵中有n^2个关系,用于确定字符A是否可以攻击(或使用能力m on)字符B(暂时忽略其他因素)。

我该如何在OOP中对其进行编码,而不将特殊情况放在各处?

另一种说法是,我可能有些东西

1
2
3
4
ClassA CharacterA;
ClassB CharacterB;

if ( CharacterA can do things to CharacterB )

我不知道if语句里面是什么,而是

1
if ( CharacterA.CanDo( CharacterB ) )

或者一个元类

1
if ( Board.CanDo( CharacterA, CharacterB ) )

Cando函数何时应该依赖ClassA和ClassB,或者属性/修饰符与ClassA/ClassB?


我将从一个能看见(怪物怪物)或能看见(怪物怪物)的方法开始,看看会发生什么。你最终可能会得到一个可见性类,或者使用http://en.wikipedia.org/wiki/visitor_模式。一个极端的例子是Bobs叔叔的三重调度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// visitor with triple dispatch. from a post to comp.object by robert martin http://www.oma.com
/*
In this case, we are actually using a triple dispatch, because we have two
types to resolve.  The first dispatch is the virtual Collides function which
resolves the type of the object upon which Collides is called.  The second
dispatch is the virtual Accept function which resolves the type of the
object passed into Collides.  Now that we know the type of both objects, we
can call the appropriate global function to calculate the collision.  This
is done by the third and final dispatch to the Visit function.
*/
interface AbstractShape
    {
    boolean Collides(final AbstractShape shape);
    void Accept(ShapeVisitor visitor);
    }
interface ShapeVisitor
    {
    abstract public void Visit(Rectangle rectangle);
    abstract public void Visit(Triangle triangle);
    }
class Rectangle implements AbstractShape
    {
    public boolean Collides(final AbstractShape shape)
        {
        RectangleVisitor visitor=new RectangleVisitor(this);
        shape.Accept(visitor);
        return visitor.result();
        }
    public void Accept(ShapeVisitor visitor)
        { visitor.Visit(this); } // visit Rectangle
    }
class Triangle implements AbstractShape
    {
    public boolean Collides(final AbstractShape shape)
        {
        TriangleVisitor visitor=new TriangleVisitor(this);
        shape.Accept(visitor);
        return visitor.result();
        }
    public void Accept(ShapeVisitor visitor)
        { visitor.Visit(this); } // visit Triangle
    }

class collision
    { // first dispatch
    static boolean Collides(final Triangle t,final Triangle t2) { return true; }
    static boolean Collides(final Rectangle r,final Triangle t) { return true; }
    static boolean Collides(final Rectangle r,final Rectangle r2) { return true; }
    }
// visitors.
class TriangleVisitor implements ShapeVisitor
    {
    TriangleVisitor(final Triangle triangle)
        { this.triangle=triangle; }
    public void Visit(Rectangle rectangle)
        { result=collision.Collides(rectangle,triangle); }
    public void Visit(Triangle triangle)
        { result=collision.Collides(triangle,this.triangle); }
    boolean result() {return result; }
    private boolean result=false;
    private final Triangle triangle;
    }
class RectangleVisitor implements ShapeVisitor
    {
    RectangleVisitor(final Rectangle rectangle)
        { this.rectangle=rectangle; }
    public void Visit(Rectangle rectangle)
        { result=collision.Collides(rectangle,this.rectangle); }
    public void Visit(Triangle triangle)
        { result=collision.Collides(rectangle,triangle); }
    boolean result() {return result; }
    private boolean result=false;
    private final Rectangle rectangle;
    }
public class MartinsVisitor
    {
    public static void main (String[] args)
        {
        Rectangle rectangle=new Rectangle();
        ShapeVisitor visitor=new RectangleVisitor(rectangle);
        AbstractShape shape=new Triangle();
        shape.Accept(visitor);
        }
    }


我建议你看看双重派遣模式。

http://c2.com/cgi/wiki?双补丁示例

上面的示例解释了一组打印机如何打印一组形状。

http://en.wikipedia.org/wiki/double_调度

维基百科的例子特别提到了用这个模式解决自适应碰撞问题。


SteveYegge有一篇很棒的关于属性模式的博客文章,你可以用它来处理这个问题。事实上,他用它写了一个RPG!

http://steve-yegge.blogspot.com/2008/10/universal-design-pattern.html

你可能会说player1是type1,type1可以攻击type2,player2是type2,所以除非在特定的player1上有一些"覆盖",player1可以攻击player2。

这使得非常健壮和可扩展的行为成为可能。


为了回答您对问题的编辑,您需要研究多态性。我个人会让cando()函数作为董事会的一部分,然后,根据传入的两个类,董事会将调用适当的函数并返回结果(战斗、观察等)。

如果你在Java中这样做,一个EnUM/界面可以和你的游戏板一起是一个非常干净的方法来解决这个问题。


我会(假设C++)给每个类提供一个EDCOX1×0的标识符,由类的实例上的吸收器方法返回,并使用EDCOX1×1编码来编码类之间的关系的特殊情况,所有的类在一个地方都是好的和有序的。我还可以通过getter函数专门访问该映射,这样修改一些或所有特殊情况的编码策略就变得像pie一样简单。例如:如果25个类中只有几对具有"不可见"属性,那么在这种情况下,映射可能只包含具有该属性的少数异常(对于像这样的布尔属性,std::set实际上可能是C++中的一个更好的实现)。

一些OO语言有多个分派(通用的Lisp和Dylan是现在想到的两种),但是对于所有(绝大多数)缺少它的语言来说,这是一种很好的方法(在某些情况下,您会发现集中化的map/dict是限制性的,并且重构为动态的访问者设计模式等等,但这要归功于"getter"函数"这样的重构对于其余的代码都是非常透明的)。


我会说使用设计模式,通常我认为观察者、中介者和访问者模式对于管理复杂的对象间关系是相当好的。


"看见"的定义是什么?如果他们占据同一个广场?如果是这样,这将在如何实现冲突检测(或者在本例中是什么)中得到回答,而不是在字符之间实现OOP关系。在不知道更多信息的情况下,我将以这种方式处理问题(用C++ /伪代码进行说明):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Character {
private:
    matrixSquare placement;
public:
    Character() {};
    ~Character {};
    matrixSquare getLocation() { return placement;};
};

class GameBoard {
private:
    //your 5 x 5 matrix here
public:
    GameBoard() {};
    ~GameBoard() {};
    boolean isOccupied(matrixSquare)
    {
        if (occupied)
        {
            //do something
            return true;
        }
        else
        {
            return false;
        }
    }
};

这里的技巧是定义角色片段和游戏场实现之间的关系。建立之后,您可以澄清如何确定两个字符是否在同一个方块、相邻方块等中…希望有帮助。