4

假设我正在设计一个可以拾取各种工具并使用它们的机器人。我将创建一个具有 Pickup 方法的 Robot 类来选择我想使用的工具。对于每个工具,我都会为它创建一个类,比如具有 Cut 方法的 Knife 类。现在在 Robot 上调用 Pickup 方法后,我想告诉我的机器人切割。所以对于 OOP 概念我必须告诉机器人,而不是刀?Cut 方法在 Knife 上,那么我该如何调用它呢?我必须在 Robot 上实现某种方式UseToolCurrentlyHeld()来将我的命令传播到 Knife。或者我只是直接使用这个直接调用刀(我的机器人拿着):myrobot.ToolCurrentlyInHand.Cut()

我觉得父方法必须拥有一切来处理它们包含的类,这很奇怪。现在我有重复的方法,就像 Knife 一样Cut(),现在 Robot 必须UseCutOnKnife()只在 Knife 上调用Cut(),因为好的 OOP 实践是将 Knife 抽象出来,让它感觉就像订购 Robot 而不必担心像 Knife 这样的内部信息。

另一个问题,如果我创作一首音乐,我会创建Music一个包含许多Measure类来存储音符信息的类。在一个 Measure 中可以有许多Note类,其中 Note 类将包含信息,例如,该音符在该小节中的位置,或者该音符播放多长时间。现在我想在小节 45 上添加一个音符,放在小节的中间。要创建 Measure,我必须先调用CreateMeasure(45)Music,然后再调用CreateNote(0.5f)Measure?创建的方法是这样在父级上的吗?如果现在我想将该注释更改为度量上的 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
}

就像现在音乐课是我现在必须通过音乐发布所有内容的最重要的事情?并链接方法最终调用最里面的类?

4

7 回答 7

5

你需要的是一个所有工具都实现的通用接口。

例如:

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();
于 2012-06-13T08:04:15.537 回答
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
于 2012-06-13T08:02:06.957 回答
1

基本上我建议您使用方法创建RobotPickup(Tool tool)Tool是从中继承具体工具类的接口。

看看 Petar Ivanov 的回答。他详细解释了我的意思。

于 2012-06-13T08:03:39.577 回答
1

对于问题的机器人部分,每个人都给了你一个很好的答案。

对于音乐部分,我不确定我会那样做。特别是,我不会将 Measure 的位置保留在 Measure 本身内,Note 及其位置也是如此。我不太喜欢的另一件事是您的位置似乎总是绝对值,这在修改现有音乐时无济于事。也许我在这里遗漏了一些东西,但是当你执行 CreateMeasure(45) 并且你的音乐中已经有 90 个小节时会发生什么?您必须更新以下所有措施。对于音符位置,我想您使用浮点数是为了能够表示一种“绝对”位置,即何时演奏音符,而不仅仅是它在另一个音符之后出现的事实。我认为我' d 更喜欢引入持续时间属性和暂停类。最后,我不会将方法从 Note 传播到 Music,但我会将属性公开,正如你所说,我会将方法链接起来最终调用最里面的类。最后,我的课程看起来与这些课程相似:

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
}
于 2012-06-13T14:08:07.670 回答
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只需要知道它有工具,但它不一定关心它们是什么,它绝对不关心它们如何操作。它只是通过标准接口使用它们。这些工具可以包含额外的方法和数据,只是在这个例子中没有。

于 2012-06-13T08:10:28.143 回答
0

我认为Action<>可以提供帮助,这里有一些关于它的有用信息Action 和 Func它帮助我理解ActionFunc所以总的来说这是一篇很棒的帖子。

于 2012-06-13T08:01:55.160 回答
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."
于 2012-06-13T08:10:06.383 回答