【问题标题】:Add more behaviour without creating new classes在不创建新类的情况下添加更多行为
【发布时间】:2013-04-12 13:18:40
【问题描述】:

这是面试时被问到的问题。

有一个Label 有一个属性Text
在一个页面中,标签很简单Label,在其他页面中,它可以处理以下任一操作或以下操作的组合
可点击
可调整大小
可拖动

你如何设计这个应用OOP设计原理&设计模式的标签组件?

我说过我会创建以下内容:

public class Label
{
  public string Text{get;set;}
}
public interface IClickable
{
 void Click();
}

public interface IDraggable
{
 void Drag();
}
public interface IResizable
{
 void Resize();
}

这样如果客户想要 Resizable Label

public class ResizableLabel:Label,IResizable
{
  ....
}

同样的方式ClickableLableDraggableLabel

但是,我觉得这是不正确的做法,因为我不想添加那些具体的类。我想避免使用ClickableAndDraggableLabelClickableDraggableResizableLabel

是否有任何设计模式可以在不添加这些具体类的情况下解决这个问题?

【问题讨论】:

  • "不创建新类"...那么您的示例中的ResizableLabel 不是一个新类吗?
  • 你问面试官什么是正确的吗?他/她对您的解决方案有何反应?
  • @MatthewWatson,我想重构我的代码。请参阅伊利亚的答案。看起来不错

标签: c# oop design-patterns design-principles


【解决方案1】:

我会使用Decorator pattern。它在 .net 世界中广泛用于不同类型的流,例如,允许您为字节流编写加密的、压缩的、文本流包装器。类图取自wiki

您的示例在实现中并不是那么微不足道,但使用不需要其他类来实现新的编译行为:

// Define other methods and classes here
public class Label
{
    public string Text{get;set;}

    public virtual void MouseOver(object sender, EventArgs args) { /*some logic*/ }
    public virtual void Click(object sender, EventArgs args) {  /*some logic*/ }

    //other low level events
}

public class ClikableLabel : Label
{
    private Label _label;

    public ClikableLabel(Label label)
    {
        _label = label; 
    }

    public override void Click(object sender, EventArgs args) 
    {   
        //specific logic
        _label.Click(sender, args);
    }
}

public class DraggableLabel : Label
{
    private Label _label;

    public DraggableLabel(Label label)
    {
        _label = label; 
    }

    public override void Click(object sender, EventArgs args) 
    {   
        //specific logic
        _label.Click(sender, args);
    }
}
public class ResizableLabel : Label
{
    private Label _label;

    public ResizableLabel(Label label)
    {
        _label = label; 
    }

    public override void MouseOver(object sender, EventArgs args) 
    {   
        //specific logic
        _label.MouseOver(sender, args);
    }

    public override  void Click(object sender, EventArgs args) 
    {
        //specific logic
        _label.Click(sender, args);
    }
}

现在可以了

var clickableDragableLabel = new ClikableLabel(new DraggableLabel(new Label{Text = "write me!"}));

var makeItResizable = new ResizableLabel(clickableDragableLabel);

【讨论】:

  • 为什么这个标签类需要抽象?在某些地方,他们只想定义不可点击、不可拖动等的简单标签
  • 不要这样做。 ResizableLabel 不应该知道 Clickable。 ResizableLabel 实现 Click 方法没有意义。装饰器模式并不意味着这样做。
  • 如果可点击/点击拖动/可拖动文本框都需要它们的文本为不同的颜色,这将如何工作?
  • 我喜欢这种方法,但是@Billa,您要求这样做而不添加更多具体的类?
  • 我不喜欢这种方法的原因是它似乎有多个属性,每次您进一步嵌套标签时都会复制这些属性,但有趣的是,(例如多个 onPaint 事件! )
【解决方案2】:

我认为对于这种情况,没有必要重新发明轮子。即使问题明确要求 OOP,它也没有明确要求您忽略组件模型编程或基于事件的行为。 这就是为什么我会遵循一种允许责任划分的方法,其中 Label 负责在它被单击或拖动时进行通知,而 SomeOtherComponent 可能会或可能不会监听此类通知(事件)以执行其他逻辑。

请查看以下链接,了解这些用户操作的事件调度方法示例:

Drag and Drop

Label Class

问候,

【讨论】:

  • 您好,请问为什么投反对票。我想了解答案中的错误:) 最好的问候,
【解决方案3】:

我只需要 CanClickdragresize 的布尔属性,它们都默认为 true,并根据需要(或继承)为 false。

构造函数如下

public Label(bool canClick = true, bool canDrag = true, bool canResize = true){}

如果他们曾经扩展过一个类,那么它可能会在以后进一步扩展

【讨论】:

    【解决方案4】:

    我认为Interface不能解决您的问题。
    我会做一些更像这样的东西:

    首先,定义一个列出所有操作的枚举:

    public Enum LabelAction{ None = 0, Clickable = 1, Resizable = 2, Draggable = 4 }
    

    要定义多个 Enum,您可以查看以下链接:

    然后在你的类Label中定义一个成员,采取行动:

    public class Label
    {
        private readonly LabelAction _action;
        private string Text { get; set; }
    
        public class Label(string text)
            : Label(text, LabelAction.None) { } 
    
        public class Label(string text, LabelAction action)
        {
            this.Text = text;
            this._action = action; 
        }
    
        public bool CanClick 
        { 
            get
            {
                return this._action & LabelAction.Clickable == LabelAction.Clickable;
            }
        }
    
        public bool CanResize { get { return this._action & LabelAction.Resizable == LabelAction.Resizable ;} }
        public bool CanDrag { get { return this._action & LabelAction.Draggable == LabelAction.Draggable ;} }
    
        public Click()
        {
           if(this.CanClick) { /* click */ }
           else { throw new Exception("Not clickable");}
        }
        public Drag()
        {
           if(this.CanDrag) { /* drag */ }
           else { throw new Exception("Not draggable");}
        }
        public Resize()
        {
           if(this.CanResize) { /* resize */}
           else { throw new Exception("Not resizable");}
        }
    }
    

    用法:

    var simpleLabel = new Label("simple");
    var clickable = new Label("clickable", LabelAction.Clickable);
    var clickableDraggable = new Label("clickable and draggable", LabelAction.Clickable | LabelAction.Draggable);
    
    public void DoEvent(Label label)
    {
        if(label.CanClick) label.Click();
        if(label.CanDrag) label.Drag();
        if(label.CanResize) label.Resize();
    }
    

    如果你需要添加一个动作,你必须添加一项到枚举LabelAction,一个方法CanDo()和一个方法Do()Label类。没有那么多。

    【讨论】:

      【解决方案5】:

      我认为你想多了面试官的想法。如果案例一样简单实用,避免过度抽象的复杂性,那么这样就足够了:

      public class Label
      {
          public string Text{get;set;}
      }
      
      public class ComlexLabel : Label
      {
          Click();
          Drag();
          Resize();
      }
      

      您可以对其进行任何操作。现在,如果对于一个挑战,您只需要一个具体实例并且需要单独类型的对象才能仅完成这些事情的组合,那么它再次简单 - 只是这次您必须创建类似的原型/接口:

      public class Label
      {
          public string Text{get;set;}
      }
      
      public interface Clickable
      {
          Click();
      }
      
      public interface Resizable
      {
          Resize();
      }
      
      public interface Dragable
      {
          Drag();
      }
      
      public interface ClickableDragable : Clickable, Draggable
      {
      
      }
      
      public interface ClickableResizable : Clickable, Resizable
      {
      
      }
      
      public interface ResizableDragable : Resizable, Draggable
      {
      
      }
      
      public interface ClickableDragableResizeable : Resizable, Clickable, Draggable
      {
      
      }
      
      public class ComlexLabel : Lable, ClickableDragableResizeable
      {
          Click();
          Drag();
          Resize();
      }
      

      现在您可以通过创建提供所需功能的类型来拥有ComlexLabel 的实例。喜欢:

      ResizableDragable rd = new ComlexLabel();
      ClickableResizable cr = new ComlexLabel();
      ClickableDragableResizeable cdr = new ComlexLabel();
      

      现在rdcrcdr 具有不同的功能。他们背后只有一个具体的例子。防止客户端通过这样做获得完全权限

      var cdr = new ComplexLabel();

      您应该将ComplexLabel 构造函数设为私有并将任务分配给某个工厂。喜欢

      var rd = Factory.GetResizableDragableLabel();
      

      现在rd 必须只是ResizableDragable 没有Click 功能..

      【讨论】:

      • 此代码不可维护。需要添加尽可能多的类,如果我明天添加 Movable 功能:(
      • @Billa 不,您只添加接口/合同。但是,是的,组合成为问题。但后来我认为面试官是在用一个不太真实的例子进行测试。只是对你的“只有一个具体类”的需求有点僵化
      【解决方案6】:

      你可以有一个基类来实现所有接口并将它们的行为委托给具体的策略类。 然后你会有一个 NullDraggable, nulResizeable,NullClickable 默认情况下什么都不做(所以你的基本标签不可点击、调整大小和可拖动) 然后你创建不同的策略,如 Clickable、DoubleClickable、WidthResizeable 等... 然后,您将所需的组合传递给您的班级。 通过这种方式,您可以获得许多易于在具有相同界面的其他组件中重用的小策略。 您可以通过使用复合模式来实现多种行为(例如,您可以同时拥有可单击和可双击的行为)

      这可能有点太ingeneered了

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-04-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-25
        • 2014-06-09
        • 1970-01-01
        相关资源
        最近更新 更多