Home > OS >  Instance vs. Inheritance
Instance vs. Inheritance

Time:11-09

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?

CodePudding user response:

Comparing instances to inheritance is akin to comparing apples and oranges. They are more or less unrelated. Inheritance is used to define the heirachy of class definitions, whereas instances are when you create an actual instance of one of those classes.

One (but by far not the only) question you need to ask yourself when considering if inheritance is the right choice is do the subclass and superclass share an is a relationship? If the answer is yes, you might want to use inheritance. If the answer is maybe or no then you almost certainly don't.

To give you some examples.

q: Does a class Mercedes share an is a relationship with a class Car.
a: Maybe! It's much more likely you just simply have a class Car which has a property which is it's Make (and Model etc).

q: Does a MyWebPage share an is a relationship with class BaseWebPage
a: Probably, yes. This is a common way that inheritance is used. MyWebPage overrides some functionality of the base class to control rendering (as one example)

Back to your example. Does class Mage Share an is a relationship with Npc.... answer is another "Maybe". For it to make sense to use inheritance your Mage must override some of the base functionality of Npc - you might have something like the Car example above.

CodePudding user response:

All classes inherit, by default, from Object

In order to use a non-abstract class (or any inherited ones), you need to create an instance.

Instances hold fields and define functions that can act as wrappers/accessors to other instances stored within that instance (referred to as composition).


For the given example, let's say NPC's have levels. All subclasses then extend from that, therefore inheriting the attributes from it. Mages (and other types) have their own properties

class NPC {
  int Level { get; set; }
  
  public NPC(int level) {
    Level = level;
  }
}

class Mage : NPC {
  String PrimaryMagic {get; set; }
 
  public Mage(int level, string magic) : base(level) {
    PrimaryMagic = magic;
  } 
}
var lowMage = new Mage(1, "fire");
var highMage = new Mage(50, "ultima");

But you can also make a generic leveled character with no specific attributes

var nobody = new NPC(0);

You could also do-away with the Mage class and give all NPC's some Spells list attribute, for example that defaults to empty/null.

var magicStudent = new NPC(1); // starts with no magic
// .. Game progresses
if (magicStudent.Level >= 5) {
  magicStudent.Spells.Add("cure"); // Based on some condition, update a list of spells. 
}

CodePudding user response:

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 does something, 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;
  }

  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 " ServiceNumber " and I ain't telling you anything else and I'm dead in the console. If it's a medic theyre a bit more forthcoming with info :)

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

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..

  • Related