Reputation: 3863
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
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
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
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
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
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
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
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