隐藏父类的实现细节

时间:2012-06-13 07:56:53

标签: c# oop

假设我正在设计一个可以拾取各种工具并使用它们的机器人。 我会创建一个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
}

现在音乐课是我现在必须通过音乐发布一切的最重要的一部分?并链接方法最终调用最内层的类?

7 个答案:

答案 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这有助于我了解ActionFunc,所以一般来说这是一篇很棒的文章。

答案 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."