假设我正在设计一个可以拾取各种工具并使用它们的机器人。
我会创建一个Robot类,它有Pickup方法来选择我想要使用的工具。
对于每个工具,我会为它创建一个类,比如Knife类,它有Cut方法。
现在在机器人上调用Pickup方法之后,我想告诉我的机器人要切割。所以对于OOP概念我必须告诉机器人,而不是刀?而且Cut方法在Knife上,那我怎么能调用呢?我必须在Robot上实现某种UseToolCurrentlyHeld()
来将我的命令传播给Knife。或者我直接用这个来直接调用刀(我的机器人手持):myrobot.ToolCurrentlyInHand.Cut()
我觉得父方法必须拥有一切来处理它们包含的类是很奇怪的。现在我有了重复的方法,比如刀有Cut()
,现在机器人必须UseCutOnKnife()
才能在刀上调用Cut()
,因为良好的OOP练习是将刀抽出并让它感觉像订购机器人而不必担心像Knife这样的内部信息。
另一个问题,如果我创作音乐,我会创建Music
类,其中包含许多Measure
类来存储音符信息。在一个Measure中,其中可以有许多Note
类,其中Note类将包含信息,例如,此注释驻留在度量中的哪个位置,或该注释播放的时间长度。现在我想在措施中间放置一个关于措施45的注释。要创建小节,我必须在音乐上调用CreateMeasure(45)
,然后在小节上调用CreateNote(0.5f)
?要创建的方法是在父类上吗?如果现在我想将该注释更改为度量上的0.25,那么负责更改注释的方法是Note
类本身或Measure
类?或者我必须实现方法来更改最顶层Music
类的注释?
这是我班级的概述:
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
}
现在音乐课是我现在必须通过音乐发布一切的最重要的一部分?并链接方法最终调用最内层的类?
答案 0 :(得分:5)
这里需要的是所有工具实现的通用界面。
E.g:
interface ITool {
void Use();
}
现在刀可以实现该界面:
class Knife : ITool {
public void Use() {
//do the cutting logic
}
}
现在您将通过机器人上的ITool界面使用该工具,而不知道它是什么工具:
class Robot {
private ITool currentTool = null;
public void Pickup(ITool tool)
{
currentTool = tool;
}
public void UseTool() {
currentTool.Use();
}
}
你可以像这样调用Pickup:
Robot robot = new Robot();
robot.Pickup(new Knife());
robot.UseTool();
答案 1 :(得分:3)
我建议采用不同的方法。让Knife
以及机器人可以从通用类Item
继承的所有其他对象继承方法Use
(也可以是接口):
interface Item
{
void Use();
}
class Knife : Item
{
public void Use()
{
// cut action
}
}
现在,每个实现Item
的类都有自己的Use
方法实现,以实现其特定的操作。
然后Robot
拥有通用Item
并在其上调用Use
,而不知道它实际是什么:
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
答案 2 :(得分:1)
基本上,我建议您使用Robot
方法创建Pickup(Tool tool)
类。
Tool
是继承具体工具类的接口。
看看Petar Ivanov的回答。他详细解释了我的意思。
答案 3 :(得分:1)
对于机器人示例,在C#中,我会从这样的事情开始:
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
}
}
所以Robot
只需要知道它有工具,但它并不一定关心它们是什么,它绝对不关心它们是如何运作的。它只是通过标准接口使用它们。这些工具可以包含其他方法和数据,它们不适用于此示例。
答案 4 :(得分:1)
每个人都为问题的机器人部分提供了一个很好的答案。
对于音乐部分,我不确定我会这样做。特别是,我不会将Measure的位置保持在Measure本身内部,而Note和它的位置也是如此。另一件我不太喜欢的事情是,你的位置似乎总是绝对值,这在修改现有音乐方面没有帮助。也许我在这里遗漏了一些东西,但是当你执行CreateMeasure(45)并且你的音乐中已经有90个小节时会发生什么?您必须更新以下所有度量。对于Note位置,我想你使用float是为了能够表示一种“绝对”位置,即何时播放音符,而不仅仅是它来自另一个音符。我认为我更愿意引入持续时间属性和Pause类。最后,我不会将这些方法从Note up传播到Music,但我会将这些属性保留为public,正如你所说,我将链接方法最终调用最内层的类。最后,我的课程看起来与这些类似:
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
}
答案 5 :(得分:0)
我认为Action<>
可以提供帮助,这里有一些有用的信息Action and Func这有助于我了解Action
和Func
,所以一般来说这是一篇很棒的文章。
答案 6 :(得分:0)
这取决于焦点是什么,告诉机器人使用某些东西,或告诉机器人做某事,或者两者兼而有之,如下所示:
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);
}
}
用法:
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."