5argon
5argon

Reputation: 3863

Hiding implementational detail from parent classes

Suppose I'm designing a robot that can pickup various tools and use them. I would create a Robot class which has Pickup method to pick the tools I wanted to use. For each tools I would create a class for it, say, Knife class which has Cut method. After invoking a Pickup method on Robot now I want to tell my robot to cut. So for OOP concept I have to tell the robot, not knife? And the Cut method is on Knife so how can I invoke that? I have to implement some kind of UseToolCurrentlyHeld() on Robot to propagate my commands to Knife. Or I just directly call the knife (that my robot hold) directly using this: myrobot.ToolCurrentlyInHand.Cut()

I feel it's weird that a parent method must have EVERYTHING to take care of classes they contain. Now I have duplicate methods, like the Knife has Cut() and now Robot must have UseCutOnKnife() only to invoke Cut() on Knife because good OOP practice is to abstract the Knife out and make it feel like ordering a Robot without worrying about internal information like Knife.

Another question, if I composing a music I would create Music class which contains many Measure class to store note information. In one Measure there can be many Note class inside it which Note class will have information like, where in the measure does this note reside, or how long that note plays. Now I want to add one note on measure 45 placed on about middle of the measure. To create the Measure I have to call CreateMeasure(45) on Music and then call CreateNote(0.5f) on Measure? The method to create is on the parent like that? And if now I want to change that note to be at 0.25 on the measure instead, the one responsible to have method to change Note is the Note class itself or Measure class? Or I must implement the method to change notes on the topmost Music class?

This is overview of my classes :

class Music
{
     List<Measure> measures = new List<Measure>();

     //methods...
}

class Measure
{
     int measureNumber;
     List<Note> notes = new List<Note>();

     //methods...
}

class Note
{
     float positionInMeasure; //from 0 to 1
}

As now the Music class is on top of everything I now have to issue everything through Music? And chaining the method to finally call the innermost class?

Upvotes: 4

Views: 136

Answers (7)

Francesco Baruchelli
Francesco Baruchelli

Reputation: 7468

Everybody gave you a nice answer for the Robot part of the question.

For the music part, I'm not sure that I would do it that way. In particular, I wouldn't keep the position of the Measure inside the Measure itself, and the same goes for the Note and its position. Another thing that I don't like too much is the fact that your positions seems always absolute values and this doesn't help when it comes to modifying existing music. Maybe I'm missing something here, but what will happen when you execute CreateMeasure(45) and there are already 90 measures in your music? You'd have to update all the following Measures. For the Note position, I imagine that you are using float in order to be able to represent a sort of "absolute" position, i.e. when to play the note, not just the fact that it comes after another one. I think that I'd prefer to introduce a duration property and a Pause class. Finally, I wouldn't propagate the methods from Note up to Music, but I'd leave the properties public and as you say I'd chain the methods to finally call the innermost class. In the end my classes would look similar to these ones:

public class Music
{
     public List<Measure> measures = new List<Measure>();

     public Measure AddMeasure() 
     {
         Measure newM = new Measure();
         measures.Add(newM);
         return newM;
     }
     public Measure CreateMeasure(int pos) 
     {
         Measure newM = new Measure();
         measures.Insert(pos, newM);
         return newM;
     }
     public Measure CreateMeasureAfter(Measure aMeasure) 
     {
         Measure newM = new Measure();
         int idx = measures.IndexOf(aMeasure);
         measures.Insert(idx + 1, newM);
         return newM;
     }
     public Measure CreateMeasureBefore(Measure aMeasure) 
     {
         Measure newM = new Measure();
         int idx = measures.IndexOf(aMeasure);
         measures.Insert(idx, newM);
         return newM;
     }

     //methods...
}

public class Measure
{
     public List<ANote> notes = new List<ANote>();
     public void AddANote(ANote aNote) 
     {
         notes.Add(aNote);
     }
     public void AddANote(int pos, ANote aNote) 
     {
         notes.Insert(pos, aNote);
     }
     public void AddANoteAfter(ANote aNote, ANote newNote) 
     {
         int idx = notes.IndexOf(aNote);
         notes.Insert(idx + 1, newNote);
     }
     public void AddANoteBefore(ANote aNote, ANote newNote) 
     {
         int idx = notes.IndexOf(aNote);
         notes.Insert(idx, newNote);
     }
     //methods...
}

public abstract class ANote
{
    public int duration;  // something like 4 for a quarter note/pause and so on
    // your stuff
}

public class Note : aNote
{
     float frequency; //or whatever else define a note
    // your stuff
}

public class Pause: aNote
{
    // your stuff
}

Upvotes: 1

davioooh
davioooh

Reputation: 24706

Basically I suggest you to create the Robot class with a Pickup(Tool tool) method. Tool is an interface from which inherit concrete tools classes.

Take a look at the answer of Petar Ivanov. He explain in detail what I mean.

Upvotes: 1

moribvndvs
moribvndvs

Reputation: 42497

It depends on what the focus is, telling the robot to use something, or telling the robot to do something, or perhaps both, as seen below:

public abstract class Task
{
    public abstract void Perform(Robot theRobot);
}

public class Cut : Task
{
    public string What { get; private set; }

    public Cut(string what)
    {
       What = what;
    }

    public override void Perform(Robot theRobot)
    {
        var knife = theRobot.ToolBeingHeld as Knife;
        if (knife == null) throw new InvalidOperationException("Must be holding a Knife.");
        knife.Use(theRobot);
        Console.WriteLine("to cut {0}.", What);
    }
}

public class Stab : Task
{
    public override void Perform(Robot theRobot)
    {
         var knife = theRobot.ToolBeingHeld as Knife;
         if (knife == null) throw new InvalidOperationException("Must be holding a Knife.");

         knife.Use(theRobot);
         Console.WriteLine("to stab.");
    }
}

public class Bore : Task
{
    public override void Perform(Robot theRobot)
    {
         var drill = theRobot.ToolBeingHeld as Drill;
         if (drill == null) throw new InvalidOperationException("Must be holding a Drill.");

         drill.Use(theRobot);
         Console.WriteLine("to bore a hole.");
    }
}

public abstract class Tool
{
    public abstract void Use(Robot theRobot);
    public abstract void PickUp(Robot theRobot);
    public abstract void PutDown(Robot theRobot);
}

public class Knife : Tool
{
    public Knife(string kind)
    {
        Kind = kind;
    }

    public string Kind { get; private set; }

    public override void Use(Robot theRobot)
    {
       Console.Write("{0} used a {1} knife ", theRobot.Name, Kind);
    }

    public override void PickUp(Robot theRobot)
    {
       Console.WriteLine("{0} wielded a {1} knife.", theRobot.Name, Kind);
    }

    public override void PutDown(Robot theRobot)
    {
       Console.WriteLine("{0} put down a {1} knife.", theRobot.Name, Kind);
    }
}

public class Drill : Tool
{    
    public override void Use(Robot theRobot)
    {
       Console.Write("{0} used a drill ", theRobot.Name);
    }

    public override void PickUp(Robot theRobot)
    {
       Console.WriteLine("{0} picked up a drill.", theRobot.Name);
    }

    public override void PutDown(Robot theRobot)
    {
       Console.WriteLine("{0} put down a drill.", theRobot.Name);
    }
}

public class Robot
{
    public Robot(string name)
    {
        Name = name;
    }

    public string Name { get; private set; }

    public Tool ToolBeingHeld { get; private set; }

    public void PickUp(Tool tool)
    {
        if (ToolBeingHeld != null) ToolBeingHeld.PutDown(this);

        ToolBeingHeld = tool;

        ToolBeingHeld.PickUp(this);
    }

    public void PutDown()
    {
        if (ToolBeingHeld != null) ToolBeingHeld.PutDown(this);
        ToolBeingHeld = null;
    }

    public void Perform(Task task)
    {
        task.Perform(this);
    }
}

Usage:

var robot = new Robot("Fred the Robot");
robot.PickUp(new Knife("butcher")); // output is "Fred the Robot wielded a butcher knife."

robot.Perform(new Cut("a leg")); // output is "Fred the Robot used a butcher knife to cut a leg."

robot.Perform(new Stab()); // output is "Fred the Robot used a butcher knife to stab."

try { robot.Perform(new Bore()); } // InvalidOperationException: Must be holding a drill.
catch(InvalidOperationException) {}

robot.PutDown(); // output is "Fred the Robot put down a butcher knife."

Upvotes: 0

David
David

Reputation: 218930

For just the robot example, in C#, I would start with something like this:

public class Robot
{
    private IList<Tool> tools = new List<Tool>();

    public void PickUpTool(Tool newTool)
    {
        // you might check here if he already has the tool being added
        tools.Add(newTool);
    }

    public void DropTool(Tool oldTool)
    {
        // you should check here if he's holding the tool he's being told to drop
        tools.Remove(newTool);
    }

    public void UseTool(Tool toolToUse)
    {
        // you might check here if he's holding the tool,
        // or automatically add the tool if he's not holding it, etc.
        toolToUse.Use();
    }
}

public interface Tool
{
    void Use();
}

public class Knife : Tool
{
    public void Use()
    {
        // do some cutting
    }
}

public class Hammer : Tool
{
    public void Use()
    {
        // do some hammering
    }
}

So the Robot only needs to know that it has tools, but it doesn't necessarily care what told they are and it definitely doesn't care how they operate. It just uses them through a standard interface. The tools can contain additional methods and data, they just don't for this example.

Upvotes: 1

Petar Ivanov
Petar Ivanov

Reputation: 93050

What you need here is a common interface that all the tools implement.

E.g.:

interface ITool {
    void Use();
}

Now the knife can implement that interface:

class Knife : ITool {
    public void Use() {
        //do the cutting logic
    }
}

Now you would use the tool through the ITool interface on the robot, not knowing what tool it is:

class Robot {
    private ITool currentTool = null;

    public void Pickup(ITool tool)
    {
        currentTool = tool;
    }

    public void UseTool() {
        currentTool.Use();
    }
} 

And you can invoke Pickup like that:

 Robot robot = new Robot();
 robot.Pickup(new Knife());
 robot.UseTool();

Upvotes: 5

Tudor
Tudor

Reputation: 62449

I instead suggest a different approach. Have Knife and all other objects the robot can pick inherit from a generic class Item with a method Use (can also be an interface):

interface Item
{
    void Use();
}

class Knife : Item
{
    public void Use()
    {
        // cut action
    }
}

Now each class implementing Item will have its own implementation of the Use method for its specific action.

Then the Robot holds a generic Item and calls Use on it without knowing what it actually is:

class Robot
{
     public Item CurrentItem { get; private set; }

     public void PickUpItem(Item i)
     {
         CurrentItem = i;
     }

     public void UseItem()
     {
         CurrentItem.Use(); // will call Use generically on whatever item you're holding
     }         
}

...

Robot r = new Robot();
r.PickUpItem(new Knife());
r.UseItem(); // uses knife

r.PickUpItem(new Hammer());
r.UseItem(); // uses hammer

Upvotes: 3

TimVK
TimVK

Reputation: 1146

I think Action<> could help and here is some useful information about it Action and Func It helped me to understand Action and Func so in general it is a great post.

Upvotes: 0

Related Questions