【问题标题】:Design pattern for a user interface with extensible functionality具有可扩展功能的用户界面设计模式
【发布时间】:2012-09-04 17:48:06
【问题描述】:

假设我正在为硬件配件编写用户界面。该配件有两个版本 - 比如说 Widget Lite 和 Widget Pro。

Widget Pro 可以做 Widget Lite 可以做的所有事情,但有更多选项,可以做一些 Widget Lite 不能做的事情。更详细地说,Widget Lite 有一个通道,Widget Pro 有两个,所以当涉及到类似于音量控制的东西时,Lite 只需要一个控制,而 Pro 需要两个允许独立控制。

在我第一次尝试构建一个应用程序来处理这个问题时,我让代表 Widget Pro 的类扩展了 Widget Lite,但后来我得到了各种条件案例来处理看起来很难看的差异。有谁知道合适的设计模式来帮助解决这种情况?在想出可能对我的搜索有帮助的同义词时,我的想象力是一片空白。

【问题讨论】:

  • LSP 违规在您的示例中至关重要。如果你能找到 Lite 不能只用 Pro 代替的地方,你就不能从 Lite 派生出 Pro。

标签: oop design-patterns


【解决方案1】:

我将从查看plug in 模式(依赖倒置的一种形式)开始。

尝试抽象一个 Lite 和 Pro 版本通用的接口,例如

interface IBaseWidget
{
   IControl CreateVolumeControl();
   // ... etc
}

在单独的程序集/dll 中,实现您的 Lite 和 Pro 小部件:

class LiteWidget : IBaseWidget
{
   int _countCreated = 0;
   IControl CreateVolumeControl()
   {
       _countCreated++;
       if (_countCreated > 1)
       {
          throw new PleaseBuyTheProVersionException();
       }
   }
}

由于您不想通过 Lite 部署分发 Pro 版本,因此您需要在运行时加载 dll,例如通过约定(例如,您的基础应用程序四处寻找名为 *Widget.dll 的 DLL),或通过配置,找到适用的 IBaseWidget 的具体实现。根据@Bartek 的评论,理想情况下,您不希望您的基础引擎类工厂“了解”IBaseWidget 的特定具体类。

【讨论】:

  • 为什么会违反 LSP(classfactory contra 实现)?
  • @jgauffin 同意这不是对 LSP 的技术违规
【解决方案2】:

访客模式可能对您有用。检查dofactory

访客类...

...为每个类的 ConcreteElement 声明一个访问操作 对象结构。操作的名称和签名标识 向访问者发送访问请求的类。这让 访问者确定被访问元素的具体类。 然后访问者可以直接通过其访问元素 特定界面

这类似于 Vikdor 所说的抽象类实现。

Here 是一个 wiki 链接。

访问者模式需要一种支持 单次调度和方法重载。

我使用访问者模式提供了一个非常简单的实现,以满足您对 WidgetLite 和 Pro 的不同频道和音量设置的要求。我在 cmets 中提到了访问者模式将极大地帮助您减少 if-else 调用。

基本理念是您将控制权(例如音量)传递给小部件,它会知道如何根据需要使用它。因此,控制对象本身具有非常简化的实现。每个小部件的代码保持在一起!!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WidgetVisitor
{
    //This is the widget interface. It ensures that each widget type
    //implements a visit functionality for each control. The visit function
    //is overloaded here.
    //The appropriate method is called by checking the parameter and this 
    //avoids the if-then logic elegantly
    public interface Widget
    {
        void visit(Volume vol);
        void visit(Channel chan);
        void Display(AllControls ac);
    }

    //This is the interface which defines the controls. Each control that 
    //inherits this interface needs to define an "accept" method which 
    //calls the appropriate visit function of the right visitor,
    //with the right control parameter passed through its call!
    //This is how the double dispatch works.
    //Double dispatch: A mechanism that dispatches a function call to different concrete 
    //functions depending on the runtime types of two objects involved in the call.
    public interface WidgetControls
    {
        void accept(Widget visitor); 

    }

    //I have implemented the volume control and channel control
    //Notice how the code for defining each control is the SAME
    //in visitor pattern! This is double dispatch in action
    public class Volume : WidgetControls
    {
        public int volLevel { get; set; }
        public int volJazz { get; set; }
        public int volPop { get; set; }
        public void accept(Widget wc)
        {
            wc.visit(this);
        }
    }

    public class Channel : WidgetControls
    {
        public int channelsProvided { get; set; }
        public int premiumChannels { get; set; }
        public void accept(Widget wc)
        {
            wc.visit(this);
        }
    }

    //Widget lite implementation. Notice the accept control implementation
    //in lite and pro.
    //Display function is an illustration on an entry point which calls the
    //other visit functions. This can be replaced by any suitable function(s)
    //of your choice
    public class WidgetLite : Widget
    {
        public void visit(Volume vol)
        {
            Console.WriteLine("Widget Lite: volume level " + vol.volLevel);
        }

        public void visit(Channel cha)
        {
            Console.WriteLine("Widget Lite: Channels provided " + cha.channelsProvided);
        }

        public void Display(AllControls ac)
        {
            foreach (var control in ac.controls)
            {
                control.accept(this);
            }

            Console.ReadKey(true);
        }
    }

    //Widget pro implementation
    public class WidgetPro : Widget
    {
        public void visit(Volume vol)
        {
            Console.WriteLine("Widget Pro: rock volume " + vol.volLevel);
            Console.WriteLine("Widget Pro: jazz volume  " + vol.volJazz);
            Console.WriteLine("Widget Pro: jazz volume  " + vol.volPop);
        }

        public void visit(Channel cha)
        {
            Console.WriteLine("Widget Pro: Channels provided " + cha.channelsProvided);
            Console.WriteLine("Widget Pro: Premium Channels provided " + cha.premiumChannels);
        }

        public void Display(AllControls ac)
        {
            foreach (var control in ac.controls)
            {
                control.accept(this);
            }

            Console.ReadKey(true);
        }
    }

    //This is a public class that holds and defines all the 
    //controls you want to define or operate on for your widgets
    public class AllControls
    {
        public WidgetControls [] controls { get; set; }

        public AllControls(int volTot, int volJazz, int volPop, int channels, int chanPrem)
        {
            controls = new WidgetControls []
            {
                new Volume{volLevel = volTot, volJazz = volJazz, volPop = volPop},
                new Channel{channelsProvided = channels, premiumChannels = chanPrem}
            };
        }
    }

    //finally, main function call
    public class Program
    {
        static void Main(string[] args)
        {
            AllControls centralControl = new AllControls(3, 4, 2, 5, 10);

            WidgetLite wl = new WidgetLite();
            WidgetPro wp = new WidgetPro();

            wl.Display(centralControl);
            wp.Display(centralControl);

        }
    }
}

【讨论】:

  • -1 你应该解释为什么它会帮助他。举个例子,而不仅仅是引用任意模式。 (如果你解决这个问题,我会投票)
  • @jgauffin,有时间我会更新合适的解释!
  • @jgauffin,这是一个粗略的解释。我指的是我在最初的答案中提供的 wiki 链接。我还提到了 [this] (stackoverflow.com/questions/2604169/…) 写得非常好的解释。请检查此问题的第二个答案。这里,WidgetLiteWidgetPro 是两个实现 visit 函数的访问者类。 accept这些访问者的音量和频道等控件。
  • @Darran,访问者模式不仅是 if-then-else 循环的强大杀手,它还允许人们以最少的代码洗牌添加更多的访问者和更多的接受者。
  • 静止。你必须解释为什么你认为this问题是一个不错的选择。您可能还想阅读我对您提到的答案的评论。
【解决方案3】:

我会这样做:

              AbstractWidget (Abstract class)
                   /\
                  /  \
                 /    \
                /      \
               /        \
         WidgetLite   WidgetPro

公共代码将进入 AbstractWidget(抽象,因为它不应该被实例化),而这两个类之间不同的行为将进入具体类。

【讨论】:

    【解决方案4】:

    我强烈建议您有一个基础 Widget 类,Widget LiteWidget Pro 都派生自该类。

    public class Widget {}
    
    public class WidgetLite : Widget {}
    
    public class WidgetPro : Widget {}
    

    在基类中拥有ProLite 共享的属性/方法。这对你来说是一个更干净的设计。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-15
      相关资源
      最近更新 更多