访问者模式 vs 服务员模式 vs 命令模式

4

这里讨论了命令模式和服务模式的相似之处。但是另一方面,我发现服务模式与访问者模式非常相似,甚至相似到我完全不知道它们的区别在哪里?两者都通过添加功能为其他类对象提供服务。但是命令模式不会添加功能,而是将其包装起来,对吗?请解释一下我的困惑所在。


关于访问者模式和服务员模式,请参考此问题:http://stackoverflow.com/questions/27939046/design-patterns-for-php-visitor-pattern-vs-servant-pattern - Ravindra babu
访问者模式可以避免层次结构限制,使我们能够在不改变合同的情况下修改功能。命令模式基于实现合同。 - Ravindra babu
1个回答

13
I'll try to describe my opinion & understanding over the matter and perhaps we can discuss further over it.
Command:如你所写,它包装了一个功能。除了功能外,它还保存要操作的数据和应用方法时要传递的参数。
Command 的 Execute 方法知道如何将所有组件组合在一起完成工作。
因此,我认为 Command 是一个自主的工作容器,可以存储并稍后执行。
Servant:这是一种简单的模式,专注于通过将责任分配给 Servant 或 Helper 类来减轻 Master 类(或客户端类)的责任。
Command 和 Servant 的区别:
时间分离 - 命令作为一个自主的容器,可以存储/排队/序列化或调度,并且可以在以后的时间点上执行。此外,命令模式遵循更黑盒子编程模型,因为命令的调用者只需要调用“Execute”函数。
因此,一个命令可以由一个类创建并由另一个类调用。
Visitor 模式及其区别
我将举一个例子来解释区别 -
假设我有三种类型的移动设备 - iPhone,Android和Windows Mobile。
这三个设备都安装了蓝牙无线电。
假设蓝牙无线电可以来自两个不同的OEM - Intel和Broadcom。
为了使示例与我们的讨论相关,请还假设Intel电台公开的API与Broadcom电台公开的API不同。
这是我的类的外观 -

enter image description here

enter image description here

现在,我想介绍一个操作 - 在移动设备上打开蓝牙。
它的功能签名应该像这样 -
 void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)

因此,根据正确类型的设备正确类型的蓝牙收发器,可以通过调用适当的步骤或算法来打开它。

原则上,它变成了一个3×2矩阵,我试图根据涉及到的对象的正确类型向量化正确的操作。

根据两个参数的类型而异的多态行为。enter image description here

正如维基页面在“动机”部分所述,解决这种问题的天真方式将遭受很多问题。

现在,我将介绍访问者模式给这个问题。灵感来自于维基百科页面所述-“实质上,访问者允许向一组类添加新的虚拟函数,而无需修改类本身;相反,创建一个访问者类,实现所有适当的虚拟函数的特殊化。访问者将实例引用作为输入,并通过双重派遣实现目标。”

由于3x2矩阵,双重派遣在这里是必要的

介绍代码中的访问者模式 -

首先,我需要做出一个决定,哪个类层次结构更稳定(更不容易改变) - 设备类层次结构还是蓝牙类层次结构。 更稳定的那个将成为可访问类,较不稳定的那个将成为访问者类。对于这个示例,我会说设备类更加稳定。

以下是设置:

enter image description here

这里是客户端代码和测试代码

 class Client
  {
      public void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothVisitor blueToothRadio) 
      {
          mobileDevice.TurnOn(blueToothRadio);        
      }
  }


 [TestClass]
public class VisitorPattern
{

    Client mClient = new Client();

    [TestMethod]
    public void AndroidOverBroadCom()
    {
        IMobileDevice device = new Android();
        IBlueToothVisitor btVisitor = new BroadComBlueToothVisitor();

        mClient.SwitchOnBlueTooth(device, btVisitor);
    }

    [TestMethod]
    public void AndroidOverIntel()
    {
        IMobileDevice device = new Android();
        IBlueToothVisitor btVisitor = new IntelBlueToothVisitor();

        mClient.SwitchOnBlueTooth(device, btVisitor);
    }

    [TestMethod]
    public void iPhoneOverBroadCom()
    {
        IMobileDevice device = new iPhone();
        IBlueToothVisitor btVisitor = new BroadComBlueToothVisitor();

        mClient.SwitchOnBlueTooth(device, btVisitor);
    }

    [TestMethod]
    public void iPhoneOverIntel()
    {
        IMobileDevice device = new iPhone();
        IBlueToothVisitor btVisitor = new IntelBlueToothVisitor();

        mClient.SwitchOnBlueTooth(device, btVisitor);
    }
}

这里是类的层次结构。
     /// <summary>
        /// Visitable class interface 
        /// </summary>
       interface IMobileDevice
        {
           /// <summary>
           /// It is the 'Accept' method of visitable class
           /// </summary>
            /// <param name="blueToothVisitor">Visitor Visiting the class</param>
           void TurnOn(IBlueToothVisitor blueToothVisitor);
        }

       class iPhone : IMobileDevice
       {
           public void TurnOn(IBlueToothVisitor blueToothVisitor)
           {
               blueToothVisitor.SwitchOn(this);
           }
       }

       class Android : IMobileDevice
       {
           public void TurnOn(IBlueToothVisitor blueToothVisitor)
           {
               blueToothVisitor.SwitchOn(this);
           }
       }

       class WindowsMobile : IMobileDevice
       {
           public void TurnOn(IBlueToothVisitor blueToothVisitor)
           {
               blueToothVisitor.SwitchOn(this);
           }
       }

        interface IBlueToothRadio
        {

        }

        class BroadComBlueToothRadio : IBlueToothRadio
        {

        }

        class IntelBlueToothRadio : IBlueToothRadio
        {

        }

访问者跟随 -
/// <summary>
/// Wiki Page - The Visitor pattern encodes a logical operation on the whole hierarchy into a single class containing one method per type. 
/// </summary>
interface IBlueToothVisitor
{
    void SwitchOn(iPhone device);
    void SwitchOn(WindowsMobile device);
    void SwitchOn(Android device);
}


class IntelBlueToothVisitor : IBlueToothVisitor
{
    IBlueToothRadio intelRadio = new IntelBlueToothRadio();

    public void SwitchOn(iPhone device)
    {
        Console.WriteLine("Swithing On intel radio on iPhone");
    }

    public void SwitchOn(WindowsMobile device)
    {
        Console.WriteLine("Swithing On intel radio on Windows Mobile");
    }

    public void SwitchOn(Android device)
    {
        Console.WriteLine("Swithing On intel radio on Android");
    }
}

class BroadComBlueToothVisitor : IBlueToothVisitor
{
    IBlueToothRadio broadCom = new BroadComBlueToothRadio();

    public void SwitchOn(iPhone device)
    {
        Console.WriteLine("Swithing On BroadCom radio on iPhone");
    }

    public void SwitchOn(WindowsMobile device)
    {
        Console.WriteLine("Swithing On BroadCom radio on Windows Mobile");
    }

    public void SwitchOn(Android device)
    {
        Console.WriteLine("Swithing On BroadCom radio on Android");
    }
}

在讲解仆人模式之前,让我简单介绍一下这个结构:

  1. 我有两个 Bluetooth 访问者,它们包含打开各种移动设备上蓝牙的算法。
  2. 我将 BluetoothVistor 和 BluetoothRadio 分开以遵循访问者哲学 - “添加操作而不修改类本身”。也许其他人想将其合并到 BluetoothRadio 类中。
  3. 每个访问者都定义了3个函数-一个用于每种类型的移动设备。(这是与仆人模式的一个重要区别- 仆人模式只应向所有受服务类提供单个算法。)
  4. 此外,由于存在6种算法变体(取决于对象类型),所以双重分派是必要的。在仆人模式中,我们只谈论算法的单个变体。
  5. 正如我上面写的,我的原始需求是像这样的一个函数- void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio),现在为了让双重分派工作,我更改了签名-使用 IBlueToothVisitor 替代了 IBlueToothRadio
现在让我们看一下同样的情况,并实现 Servant 模式。
Servant 模式是一种更简单的模式,它旨在从一个类层次结构中取出公共功能,以便不会在所有类中重复。
为此,让我们假设所有 3 个设备需要完全相同的算法来打开蓝牙。 同时,我们假设只存在一种类型的收音机。
现在,我们可以在所有 3 个设备类中编写相同的算法,或者根据维基百科所说应用 servant 模式 - “servant 用于为一组类提供某些行为。而不是在每个类中定义该行为 - 或者当我们无法将此行为因素化为通用父类时 - 在 servant 中定义它。”

enter image description here

我已经用红圈标出了区别。
  1. 不需要双重调度,客户端可以直接调用服务类的servant。
  2. 适用于所有三种设备的单一算法。

这是处理调度的唯一位置——客户端和测试代码。

class Client
 {
    public void SwitchOnBlueTooth(IMobileDevice mobileDevice,    IBlueToothServant blueToothRadio)
    {
        //there is just one BT servant & all the serviced types get the same service (No There is no specificity). 
        // Wiki page - User knows the servant (in which case he doesn’t need to know the serviced classes) and sends messages with his requests to the servant instances, passing the serviced objects as parameters.
        blueToothRadio.SwitchOn(mobileDevice);
    }
}


[TestClass]
public class ServantPattern
{

    Client mClient = new Client();

    [TestMethod]
    public void AndroidBlueToothOn()
    {
        IMobileDevice device = new Android();
        IBlueToothServant btServant = new BlueToothServant();

        mClient.SwitchOnBlueTooth(device, btServant);
    }

    [TestMethod]
    public void iPhoneOverBroadCom()
    {
        IMobileDevice device = new iPhone();
        IBlueToothServant btServant = new BlueToothServant();

        mClient.SwitchOnBlueTooth(device, btServant);
    }

    [TestMethod]
    public void WMBlueToothOn()
    {
        IMobileDevice device = new WindowsMobile();
        IBlueToothServant btServant = new BlueToothServant();

        mClient.SwitchOnBlueTooth(device, btServant);
    }
}

在这里,服务类层次结构并不那么有趣。

/// <summary>
/// Serviced class interface 
/// </summary>
interface IMobileDevice
{

}

class iPhone : IMobileDevice
{

}

class Android : IMobileDevice
{
}

class WindowsMobile : IMobileDevice
{
}

这里是服务员类及其接口(维基链接没有显示其接口)。
 /// <summary>
 /// The sevant interface
 /// </summary>
 /// <remarks>Not present in Wiki article but I have added so its easy to          mock it</remarks>
 interface IBlueToothServant
 {
     void SwitchOn(IMobileDevice device);
 }


class BlueToothServant : IBlueToothServant
{
    IBlueToothRadio intelRadio = new BlueToothRadio();

    public void SwitchOn(IMobileDevice device)
    {
        Console.WriteLine("Switching On blue tooth radio on IMobileDevice");
    }

}

我没有粘贴 IBlueToothRadioBlueToothRadio 的代码,因为这对于讨论服务模式并不太相关。

如果有任何不清楚的地方,请告诉我,我们可以进一步讨论。


嗨@Narek, 不,那不是我的意思。让服务员模式编程到一个接口。 事实上,编程到一个接口将有助于模拟服务员类,以防我们希望进行自动化单元测试。 - Kapoor
我的观点是,在服务者模式中,只有一个服务者类实现,为所有接受服务的类提供相同的服务。在提供给接受服务的类的服务行为上没有特定性。因此,当所有接受服务的类需要单一的服务行为时,这种模式是合适的。 - Kapoor
当然,@Narek,写一段简短的代码是个好主意。我还会为这段代码创建一个UML图。 但目前我不在,我将于26日或27日回到我的工作站。 - Kapoor
你好@Narek,很抱歉让你等待了。这是我的生日,我放了一个长假。我已经提供了一个例子,在我的例子中,我首先解释了我如何看待访问者模式,也许我有点过于冗长,但这帮助我解释了与服务员模式的区别。如果你明白我的意思,请告诉我...我们可以进一步讨论,现在有了这个例子,我们可以在讨论中有一个参考点。 - Kapoor
卡普尔,我认为这是最好的答案,可能永远都不会发生!另外,生日快乐,虽然有点晚了! :) - Narek
显示剩余4条评论

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