I'm making a simple game to learn some kotlin, and I'm a little confused on the best way to change this around using OOP.
I have a class for backpack setup so that it has another class called items that will be different types of items. Below I've recreated a simple version of what I have.
class backPack {
private val item: Item
init { item = Item() }
fun display() {
item.display()
}
}
class Item {
fun display() { println("Your item!") }
}
fun main() {
val examplePack = backPack()
examplePack.display()
}
I want to change the backPack class to allow for different types of items. For example, health potions and mana potions. I considered making the item an open class, then having something like this:
class healthPotion : Item() {
override fun display() {
println("health potion!")
}
}
class manaPotion : Item() {
override fun display() {
println("mana potion!")
}
}
which seems correct, but I'm a little stuck on how to refactor the backpack class to allow different types of items and I want to make sure this seems like a proper way to do this. Any assistance is very appreciated, thank you!
CodePudding user response:
That's basically the idea! If your BackPack
class (it should start with a capital by convention) handles Item
s, then any class that has that type will work. You have two options - inheritance, and composition.
Inheritance is where you build a hierarchy of classes, e.g.:
open class Item {
val weight: Int
fun Describe()
}
open class Potion : Item() {
fun drink() {}
}
// this is a Potion, and a Potion is an Item, meaning this is an Item
class ManaPotion : Potion() {
override fun drink() {
println("whoa!")
}
}
// etc
The problem there is you're locked into a rigid hierarchy - what if you had a StaleBread
that's a Food
, but you also want it to be a Weapon
? You can use interfaces to compose your object by giving it multiple types, not just a parent:
open class Item
interface Food {
fun eat() {
println("yum")
}
}
interface Weapon {
fun attack() {
println("RARGH")
}
}
class StaleBread : Item(), Food, Weapon
Because StaleBread
has all of those types, you can treat it as any of them, because it is all of them. Because it's a Weapon
, it's guaranteed to have an attack()
method, etc. You can add it to a List<Food>
, because it is
a Food
. Being able to treat objects as different types is called polymorphism. So you're able to compose an object from different types, to give it certain properties, certain behaviours, etc
Your example should work as-is because you're handling Item
s in your backpack, and your two potion classes are both Item
s (descendants of that class, specifically, since you're inheriting from the Item
class). There are lots of ways to organise it - if you get into some tutorials about inheritance, composition and polymorphism (this is only a simple overview that skips over a bunch of things) you'll start to get some ideas about how to move forward
oh yeah, for the actual backpack, you probably want something like this:
class BackPack {
private val items = mutableListOf<Item>()
fun addItem(item: Item) {
// here you could do things like check it doesn't exceed the allowed weight
// or capacity of the backpack
items.add(item)
}
}
This way, your backpack can contain multiple Item
s, and you can control how those are accessed through the class's functions and properties. Anything which is an Item
type can go in there!
CodePudding user response:
If you want to be able to change which item is in the bag, the property should be a var
. If you want the bag to be able to be empty, then it should be a nullable property (declared with a ?
after the type so it can hold null).
By the way, class names should always start with a capital letter so your code will be easier to read. And you can initialize properties at the declaration site instead of using a separate init
block.
class Backpack {
var item: Item? = null
fun display() {
item?.display()
}
}
Your generic Item()
doesn't seem like it would be useful in practice. Therefore, Item should probably be either an abstract class
or an interface
. A general OOP principle is that you should avoid deep class hierarchies. If your superclass doesn't contain logic that must be shared by all its children, it should probably be an interface instead.