隐藏实现细节,不让父类知道

4
假设我正在设计一个可以拾取各种工具并使用它们的机器人。 我会创建一个Robot类,其中包含Pickup方法来选择我想要使用的工具。 对于每个工具,我都会创建一个类,比如Knife类,其中包含Cut方法。 在对Robot调用Pickup方法之后,现在我想告诉我的机器人进行切割。因此,根据OOP概念,我必须告诉机器人而不是刀子?而Cut方法在Knife上,那么我该如何调用它呢?我必须在Robot上实现某种UseToolCurrentlyHeld()来将我的命令传播到Knife。或者我直接调用机器人(持有的刀)直接使用这个:myrobot.ToolCurrentlyInHand.Cut()
我觉得父方法必须处理它们包含的类的所有内容很奇怪。现在我有重复的方法,比如Knife有Cut(),现在Robot必须有UseCutOnKnife()只为了调用Knife上的Cut(),因为良好的OOP实践是将Knife抽象出来,让它感觉像是在不用担心内部信息(如Knife)的情况下订购机器人。
另一个问题,如果我创作音乐,我会创建Music类,其中包含许多Measure类来存储音符信息。在一个Measure中,可能会有许多Note类,其中Note类将具有信息,例如,这个音符在哪个节拍中,或者这个音符演奏的时间有多长。现在我想在第45个小节的中间添加一个音符。要创建Measure,我必须在Music上调用CreateMeasure(45),然后在Measure上调用CreateNote(0.5f)?像这样创建方法的父级吗?如果现在我想将该音符更改为在小节的0.25处,那么负责更改Note的方法是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
}

现在Music类已经处于最高位置,我现在必须通过Music发出所有内容吗?并且链接方法以最终调用最内层的类?


+1 对嗅到问题并表达出来表示赞同。 - razlebe
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();

那么在这种情况下,就像刀具上的Use()与机器人上的UseTool()配对一样?我的音乐案例怎么样?在机器人场景中,刀具是在直接调用机器人实例时创建的。现在,如果我想在知道音乐封装它的情况下,在一个小节实例上进行新音符的直接调用,该怎么办? - 5argon
关于第一个问题:您不必拥有UseTool - 您可以直接在currentTool对象上调用Use。我不确定我理解您的第二个问题。 - Petar Ivanov

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

1

基本上我建议您创建具有Pickup(Tool tool)方法的Robot类。 Tool是一个从中继承具体工具类的接口。

看一下Petar Ivanov的答案。他详细解释了我的意思。


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
    }
}

所以,机器人只需要知道它有工具,但并不一定关心这些工具是什么,也绝对不关心它们如何操作。它只是通过标准接口使用它们。这些工具可以包含其他方法和数据,但在本例中并没有。

1

关于机器人部分的问题,大家都给出了不错的答案。

至于音乐部分,我不确定我会这样做。特别是,我不会将小节的位置保留在小节本身内部,对于音符及其位置也是如此。另一件我不太喜欢的事情是,你的位置似乎总是绝对值,这在修改现有音乐时并不方便。也许我在这里漏掉了什么,但如果你执行CreateMeasure(45),而你的音乐中已经有90个小节了,会发生什么?你必须更新所有后面的小节。对于音符位置,我想你正在使用浮点数来表示某种“绝对”位置,即何时播放音符,而不仅仅是它在另一个音符之后。我认为我更喜欢引入持续时间属性和暂停类。最后,我不会将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
}

0

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


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接