Class hierarchy or not ?

Hello everyone,


I got a question and here is a little example to demonstrate it.


Lets suppose I got a class Soldier which has some typical attributes.


class Soldier {

var healthPoints: Int

var attack: Int

var defense: Int

var type: SoldierType


...

}


enum SoldierType {

case ARCHER

case INFANTRY

case CAVALARY

}


Now I want to use several soldier types with different default values therefore I have two ideas:


1. Subclassing the Soldier class and then providing an initializer for each type of soldier I want.

e.g.

class Archer: Soldier {

init() {

super.init()

healtPoints = 100

attack = 10

defense = 2

type = .ARCHER

}

}

...


2. My second approach would be to initialize the Soldier with a type and then switch the type.


init(type: SoldierType) {

self.type = type

switch type {

case .ARCHER:

healtPoints = 100

attack = 10

defense = 2

...

}



I hope Iam not on a completly wrong path.


If not, I would prefer the first option,

but since Swift has no access modifier for the class and subclasses only, I would have to make everything private and provide setter, which I really do not like.


If there are other better ways, I would be glad if you let me know.


Thanks in advance

Chris

Replies

Which is better is a quenstion which may depend on the context, but I prefer the first.

You may need to add some methods (or computed properties) common to Soldier which may vary between its subclasses.

Having many conditionals makes your app difficult to maintain.


But I do not understand this part fully.

since Swift has no access modifier for the class and subclasses only, I would have to make everything private and provide setter

You mean that Swift has no `protected`-like modifiers, or something else?

Unless you have tens of different soldiers, I would prefer the explicit naming


class Archer: Soldier {
     init() {
          super.init()
          healtPoints = 100
          attack = 10
          defense = 2  
          type = .ARCHER
     }
}


You could also define a convenience initializer in Soldier, to make it simpler to initialize.

Thanks,

yes I meant the protected access modifier.


Is there any solution without setters, here is just a little example I have in mind.


protocol SoldierProtocol {

func attack()

func move()

...

}


class Soldier: SoldierProtocol {

var healthPoints: Int

var attack: Int

var defense: Int

var type: SoldierType


func attack {

...

}


...

}


class Archer: Soldier {

init() {

super.init()

healtPoints = 100

attack = 10

defense = 2

type = .ARCHER

}

}


let soldier: SoldierProtocol = Archer()

...


So I still have an internal initializer for the Soldier class, but if I dont use it and write a doc for it,

it may be ok?


Is this a valid alternative or should I go with setters ?

WHy do you need Protocol definition and not just func definition in Soldier ?


You could override those methods in Archer if needed.

Thanks,


suppose there is another enum Country and the default value for the Soldier depends on the SoldierType and Country,

I got like 50+ possible combinations, would it be good to combine both approaches,

for example like this?


enum Country {

GERMANY

FRANCE

NORWAY

...

}


class Archer: Soldier {

init(country: Country) {

super.init()

self.country = country

type = .ARCHER

switch country {

case .GERMANY:

healtPoints = 10

attack = 10

defense = 2

...

}

}

}

I do not want to my make my variables internal, but private so I need setters to provide my default values for different Soldies.

when subclassing Soldier. I do not want to change any function in a subclass it is all about the default values and not having internal variables.


So I wanna be able to write:

let protArcher: SoldierProtocol = Archer()

protArcher.move()

but something like this should not be possible and is not with if I do it like above.

protArcher.defense //Error


and if I dont use the protocol and put the functions in the Soldier class I could write this.

If I dont use private + setters.

let archer: Archer = Archer()

archer.defense


Maybe Iam just unable to state what my problem is or it is not even one.

Either way thanks.

You could mark var or func as final.


It will not be possible to override but they will remain public

A convenience initializer in Soldier would be useful there.


enum SoldierType {
     case ARCHER
     case INFANTRY
     case CAVALARY
}

enum Country {
     case GERMANY
     case FRANCE
     case NORWAY
}

class Soldier {
     var healthPoints: Int
     var attack: Int
     var defense: Int
     var type: SoldierType

     init(healthPoints: Int, attack: Int, defense: Int, type: SoldierType) {
          self.healthPoints = healthPoints
          self.attack = attack
          self.defense = defense
         self.type = type
     }

    func attacking() {
        print("Can override this")
    }

}

class Archer: Soldier {
    init() {
        super.init(healthPoints: 100, attack: 10, defense: 2, type: .ARCHER)
    }
   
    convenience init(country: Country) {
        self.init()
        switch country {
             case .FRANCE:
                 healthPoints = 20
             case .GERMANY:
                  healthPoints = 10
                  attack = 10
                  defense = 2
        default: return
        }
    }

    final override func attacking() {
        print("Cannot override this")
    }

}


You can call the func:

l

let archer = Archer()
print("archer.healthPoints", archer.healthPoints)
archer.attacking()

let germanArcher = Archer(country: .GERMANY)
print("germanArcher.healthPoints", germanArcher.healthPoints)
germanArcher.attacking()

gives

archer.healthPoints 100

Cannot override this

germanArcher.healthPoints 10

Cannot override this


But if you try to subclass and override:


class SecondaryArcher: Archer {
    override func attacking() {
        print("Tried override this")
    }
}


You get compiler error:

Instance method overrides a 'final' instance method

Maybe you already have found, that even if you make some properties private, you need to make something accessible for the subclasses, getters, setters or initializers. Do you want to make them private and define some other methods to hide them? That's a meaningless repetition.


Using protocols may be a good abstraction, but you cannot hide the actual initializers or methods in the class.


If you believe `protected`-like abstraction would be very valuable and important, you can make such members `fileprivate`, and put all the classes which needs access to them into the same file. Though, this is not recommended unless your app is very small.


If your app is very large, you should better consider splitting your project into main project and sub framework projects, with having private frameworks inside your app, you can distinguish `internal` and `public`.


Generally, for an app with volume that one person can keep watch an all classes in it, `protected`-like modifiers does not have much effect.

You should better concentrate on designing the classes or protocols to represent the data hirearchy/structure properly.

Some hacky workarounds for making `protected`-like abstraction would make your app more difficult to maintain and develop.