Im learning c# and have troubles understanding what use cases inheritance has in comparison to instances?
To put it into a context, i am making a console rpg, and my current understanding is that I should make a superclass/baseclass npc
from which I inherit 3 subclasses mage
, paladin
and thief
, from which i each instance High level mage
, low level mage
.
Does it make sense or is there a problem in my logic? Would this also work the other way around with inheritance/instance swapped?
i do not understand use cases of inheritance. Thats what bugged me
You made this comment on another answer; that answer talks about when to identify candidates for inheritance, and OneCricketeer's answer talks about an example inheritance hierarchy but you might still be wondering "what's the point?"
In essence the point is that it allows you to treat things in a common way when you don't care to know the detail about what a thing is. You don't need to know if the thing in your hand is a knife, a spatula, a credit card etc in order to be able to use it to spread mayo on a sandwich; all those things I mentioned could reasonably be used to scoop up mayo and spread it out. They can be treated in a generic way relying on some basic properties that makes them good as spreaders (straight edge, fairly rigid body, water and oil proof)
So lets say you have your character types and they all descend from NPC. Suppose every kind of player has Health
, and when it hits 0 they're dead. Because all characters have Health, it might make sense to put it on NPC. Maybe different kinds of characters have different starting healths:
class Npc{
public int Health{get;set;}
}
class Medic:Npc{
Medic(){
Health = 30;
}
}
class Soldier:Npc{
Soldier(){
Health = 80;
}
}
Suppose your player has organised a clan/posse/team, so your program has e.g.:
var team = new Npc[10];
You can have up to 10 kinds of character in your team, and it can be any mix. This thing about inheritance is that a Soldier is-a Npc, and a Medic is-a Npc, so now you made an array of Npc, you can stuff soldiers and medics in there:
team[0] = new Soldier();
team[1] = new Medic();
Suppose someone drops a bomb on the team, it inflicts 55 damage on everyone's health. It'll kill a fresh Medic outright. We don't need to know what kind of player they are; all Npc have Health, and both medics and soldiers can be treated as Npc in this generic "take some Health off them" scenario:
void DropBombOn(Npc[] team){
foreach(var npc in team) {
npc.Health -= 25;
if(npc.Health <= 0)
Console.WriteLine("Character is dead");
}
}
But which character is dead? Let's modify the classes:
class Npc{
public int Health{get;set;}
public string Name{get;set;}
}
Set them up:
team[0] = new Soldier("john");
team[1] = new Medic("fred");
class Medic:Npc{
Medic(string name){
Health = 30;
Name = name;
}
}
class Soldier:Npc{
Soldier(string name){
Health = 80;
Name = name;
}
}
void DropBombOn(Npc[] team){
foreach(var npc in team) {
npc.Health -= 55;
if(npc.Health <= 0)
Console.WriteLine("Character called " + npc.Name + " is dead");
}
}
You can add another type of character, and they're still also an Npc, so they can be bombed just like all the rest..
Inheritance allows us to treat very specific types as something more general if there are operations that can take place in a general sense, on the general properties that all those kinds of things have.
Maybe all players have a bitmap image within their class that represents them. You can draw the team on screen by visiting each one and saying "give me your pixels"; you don't need to visit each one and say "if this npc is a medic, draw a medic. If this npc is a soldier draw a soldier.." - you just draw whatever pixels they give you and every different player gives different pixels. That, in essence is what we did with name - all characters have a name, we didn't care about which name we got; we just asked and printed out what we got
Another useful thing is that classes can have methods that override (replace) those on the base classes. Here we make a method that describes the character. We put it on the Npc, which means it will be available for anything that inherits from an Npc. We mark it abstract
which means "anything that inherits from Npc must provide a method that returns a string, and is called DescribeYourself":
public class Npc{
public int Health{get;set;}
public string Name{get;set;}
public abstract void DescribeYourself();
}
public class Medic:Npc{
public Medic(string name){
Health = 30;
Name = name;
}
public override string DescribeYourself(){
return "I'm a medic called " + Name;
}
}
public class Soldier:Npc{
public string ServiceNumber {get; set;}
public Soldier(string name,string serviceNumber){
Health = 80;
Name = name;
ServiceNumber = serviceNumber;
}
public override string DescribeYourself(){
return "I'm a soldier with service number " + ServiceNumber + " and I ain't telling you anything else";
}
}
void DropBombOn(Npc[] team){
foreach(var npc in team) {
npc.Health -= 55;
if(npc.Health <= 0)
Console.WriteLine(npc.DescribeYourself() + " and I'm dead");
}
}
This time we didn't pull the name/print a common message - we asked the npc to describe itself. If it's a Soldier, the soldier version of the code runs and we see I'm a soldier with service number 12345 and I ain't telling you anything else and I'm dead in the console. If it's a medic they're a bit more forthcoming with personal info, saying I'm a medic called John and I'm dead :)
We also have to provide a service number when we make a Soldier. This outlines that different kinds of classes can have different data requirements/more or less data during setup and use, but we can still treat them in a common way:
team[0] = new Soldier("john", "12345");
team[1] = new Medic("fred");
The term for this "treat in a common way" is polymorphism and it's perhaps one of the most powerful things about C#.. In a practical sense, for example, Microsoft can provide a Stream class that, basically, writes bytes to somewhere, and then loads of inherited classes can write bytes to different places. We now have streams that can write bytes to.. the console, to a file, to some dropbox storage, to memory, to a string... And they're all used in the same way and the person calling write doesn't care how many hoops the code has to jump through to write to console vs file vs FTP server.. They just call Write with the expectation that the bytes will end up in the place promised.
You can write you own streams, and hand them to Microsoft's classes - Microsoft's classes knew nothing about your classes, but you can still ask a Bitmap to Save itself to your stream, and maybe your stream class takes it and adds the bytes to an email and sends it..
Suddenly you just made it possible to have a bitmap write itself to an email and a bitmap can remain completely ignorant of what an email is, but it can still write to it.
You can remain completely ignorant of what a feeler gauge is, but you can use it to spread mayo..