質問

I've been trying to learn OOP for the last few weeks as much as I can, and I've learned alot, but I'm not certain of this one, what should my class hierarchy look like?

Imagine two classes, here's a Level-class:

class Level(object):
    def __init__(self, level, exp):
        self.level = level
        self.exp = exp

    @property
    def required_exp(self, level=None):
        return 80 + 40 * (level or self.level)

    def add_exp(self, exp):
        self.exp += exp
        while self.exp >= self.required_exp:
            self.exp -= self.required_exp
            self.level += 1

Obviously it has some other methods too, but that's the basic idea.

Now the second class is called Player. It has some attributes, such as: name, health, level, experience points, damage, etc.

What I've learned so far (someone on SO said this) is to ask myself a question when I'm not sure whether to use inheritance (is-a-relationship) or attribute (has-a-relationship). The question would be: Is Player a Level, or does Player have a Level?

Well... It's neither. Player certainly is not a level, that I'm sure of. And Player has it's level, but the level is not equal to Level class. Instead Player has level and experience points, which are both attributes of Level class. You can also add experience points to Player, so it would make sense to inherit it from Level.

As a sidenote, the namings don't really make any sense to me either. It would make sense to have something like: Level.increase(self) to increase a Level by one. However, for player it would make more sense to call Player.level_up(self), as used in most (all?) rpg games. Calling Player.increase(self) doesn't really make any sense, you can't increase Player by one, you're increasing it's level by one. Then again, calling Level.level_up(self) doesn't make that much sense either...

So, I think that I should use Level as if it was an interface and inherit Player (and other subclasses) from it, but I'm not certain so I decided to ask you, since I'm here to learn anyways. What should I do? What would be the most proper way to use Level with Player?

役に立ちましたか?

解決

I believe I'm the one who told you to ask yourself the question: "Is class A a class B, or does class A have a class B?", so I feel like I have to answer this question.

I'm not 100% sure of what your level actually does, but I'm assuming it's one similar to the one used in Pokemon, World of Warcraft, League of Legends, and thousands of other games. If so, you've already gone wrong with your Level-class, it shouldn't be like that.

You're right on one thing for sure; "Player certainly is not a level" As others have already mentioned, you should just go with has-a relationship. Player does have level, we both know that. However, you're telling me player doesn't have an attribute of type Level, but rather an integer level-attribute to hold the numeric value of your Level-class.

This should already ring some bells, but here's a tip: It's the same as having a Name-class that has a string-type attribute called name inside it.

So the answer is: Name class should already be a string itself, it should inherit from string, instead of having a string attribute inside it to hold its value. The same applies for your Level, it's already an integer itself so inherit it from int, then add exp and its methods.

Now you can use player.level to return the integer value of player's level. And to get the exp, you "have to" use player.level.exp. I quoted the "have to", since even though it might sound weird, exp IS an attribute of level's. It's basically the same as a decimal point for a number, makes sense?

Also, calling something like player.level.increase() makes much more sense now. Although, it's already an integer value, why not just do player.level += 1? And if you need some advanced stuff inside the addition, you could just override the Level.__add__(self, value) method. I can't think of a single reason why wouldn't you need this though? (not now that you've already made required_exp a property)

他のヒント

Player has-a level. As you said, "Player certainly is not a level." When you use inheritance, it implies that the player is a subtype of level (which of course doesn't make sense at all).

This is a clear has-a relationship. Interact with the level with methods such as player1.levelup() or player1.addxp() which refer to the level attribute.

If you are already sure Player is not a Level, then inheritance is not the correct mechanism. Inheritance works for relationships like "A car is a vehicle, and a motorbike is a vehicle", so in that situation both Car and Motorbike are subclasses of Vehicle.

However, this relationship doesn't fit either with a real composition, in the sense that Level shouldn't be represented as another class with its own functionality. It is true that a player has a level, but as you point out, player.level_up() and player.add_experience() is more clear than calling these methods with a level object. That is because the interaction with the player's level makes sense through its methods, not with player.level.level. Then you should consider moving the functionality of your Level class to Player:

Class diagram

The player definitely isn't a level but he has a level. I don't see your problem with using the level class defined above in the player class, as it keeps track of all the important information for the player that is a part of a level.

Instead of keeping track of both the level and the additional experience with respect to that level, you could simply keep track of the total experience.

From the total experience you can derive the level and the amount of experience needed to reach the next level, or any other such quantity you might be interested in.

For example,

class Player(object):
    def __init__(self, exp=0):
        self.exp = exp
    @property
    def level(self):
        if self.exp<80:
            return 0
        return (self.exp-80)//40 + 1
    @property
    def required_exp(self):
        return 80 + 40*self.level
    def add_exp(self, exp):
        self.exp += exp


ringo = Player(exp=80)
print(ringo.level)
# 1

print(ringo.required_exp)
# 120
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top