【问题标题】:Do split one class hierarchy into several or not?是否将一个类层次结构拆分为多个?
【发布时间】:2020-06-03 12:57:28
【问题描述】:

目前,我们有一个依赖于标准 .NET XML 序列化/反序列化机制的 .NET 应用程序。例子虽然简化了,但意思是一样的。

public abstract class Shape
{
    [XmlAttribute("id")]
    public string Id { get; set; }
    [XmlAttribute("level")]
    public int Level { get; set; }

    public abstract void Draw();
    public abstract void Clear();
    public abstract void Scale(double scale);
}

[XmlType("Circle")]
public class Circle : Shape
{
    public double Radius { get; set; }

    public override void Draw() {}

    public override void Clear() {}

    public override void Scale(double scale) {}
}

[XmlType("Rectangle")]
public class Rectangle: Shape
{
    public double Height { get; set; }
    public double Width { get; set; }

    public override void Draw() {}

    public override void Clear() {}

    public override void Scale(double scale) {}
}

public class Picture
{
    public double Scale { get; set; }
    [XmlArrayAttribute("Shapes")]
    public Collection<Shape> Shapes { get; set; }

    public void Setup()
    {
        foreach (Shape shape in Shapes)
        {
            shape.Draw();
        }

        foreach (Shape shape in Shapes)
        {
            shape.Scale(Scale);
        }
    }

    public void Cleanup()
    {
        foreach (Shape shape in Shapes)
        {
            shape.Clear();
        }
    }

    public static Picture FromXml(XmlReader xmlReader)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(Picture));
        return serializer.Deserialize(xmlReader) as Picture;
    }
}

例如,输入的 XML 文件将如下所示:

<Picture>
    <Scale>0.9</Scale>
    <Shapes>
        <Circle id="1">
            <Radius>1.5</Radius>
        </Circle>
        <Circle id="2">
            <Radius>3</Radius>
        </Circle>
        <Rectangle id="3">
            <Height>300</Height>
            <Width>300</Width>
        </Rectangle>
    </Shapes>
</Picture>

但模型类包含一个逻辑(Draw()、Clear() 和 Scale() 方法),它似乎违反了单一责任原则。因此我们不知道将逻辑分成几个类是否有意义?

如果是,那么如何?因为一旦我们读取 XML 文件,所有对象都只能作为 Shape 对象访问,因此我们必须在将 if 传递给处理程序类之前或在该方法内部显式转换对象,例如:

public abstract class Drawer
{
    public abstract void Draw(Shape shape);
}

public class CircleDrawer : Drawer
{
    public override void Draw(Shape shape)
    {
        Circle circle = shape as Circle;
        if (circle == null)
        {
            throw new ArgumentException("Passed object is not of type Circle");
        }
    }
}

如果该问题已知,请将我重定向到该资源。

提前谢谢你。

【问题讨论】:

  • 您不需要进行强制转换,即shape as Circle。因为Circle 一个Shape,将其视为Shape 非常高兴,但由于Circle 覆盖了Draw 方法,被覆盖的方法就是被调用的方法.考虑以下...dotnetfiddle.net/YQxhph
  • 从:Draw(Shape shape) 更改为:Draw(Circle shape)

标签: c# .net oop design-patterns xml-serialization


【解决方案1】:

你应该分离模型和业务逻辑, 所以将被反序列化的对象将只包含属性。 然后,在创建业务逻辑的工厂或方法中,将其作为圈子(例如)业务逻辑的成员插入。

【讨论】:

    【解决方案2】:

    尝试分离模型和业务逻辑,我得到以下代码。可以接受吗?对我来说,它看起来不灵活:如果我们添加一个新的形状抽屉,我们不应该忘记扩展 if 语句。

    public abstract class Drawer
    {
        public abstract void Draw();
    }
    
    public class CircleDrawer : Drawer
    {
        private readonly Circle _circle;
    
        public CircleDrawer(Circle circle)
        {
            _circle = circle;
        }
    
        public override void Draw() { }
    }
    
    public class RectangleDrawer : Drawer
    {
        private readonly Rectangle _rectangle;
    
        public RectangleDrawer(Rectangle rectangle)
        {
            _rectangle = rectangle;
        }
    
        public override void Draw() { }
    }
    
    public class Picture
    {
        public double Scale { get; set; }
        [XmlArrayAttribute("Shapes")]
        public Collection<Shape> Shapes { get; set; }
    
        public void Setup()
        {
            List<Drawer> drawers = new List<Drawer>();
            foreach (Shape shape in Shapes)
            {
                if (shape is Circle)
                {
                    drawers.Add(new CircleDrawer(shape as Circle));
                }
                else if (shape is Rectangle)
                {
                    drawers.Add(new RectangleDrawer(shape as Rectangle));
                }
                else
                {
                }
            }
    
            foreach (Drawer drawer in drawers)
            {
                drawer.Draw();
            }
        }
    
        public static Picture FromXml(XmlReader xmlReader)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(Picture));
            return serializer.Deserialize(xmlReader) as Picture;
        }
    }
    

    【讨论】:

      【解决方案3】:

      这种情况下的问题是,调用者是否需要知道具体类型,还是所有内容都通过公共基类映射?

      在您的示例中,所有实现都共享一个公共抽象基类。如果所有用户都可以愉快地使用这种常见行为,那么您只需推动所有转换为基本类型的实例。然而,正如您已经提到的,这倾向于将越来越多的业务逻辑放入每个具体类中。取决于多少(或更少),放在那里绝对没问题(这似乎是您当前的解决方案)。

      如果这变得更加复杂或相当复杂,那么将其移至其自己的类和/或方法中将是一个好方法。在这种情况下,您需要某种调度程序,它决定由哪个调度程序负责给定对象。对于这些情况,我开始使用字典作为调度程序,并将给定类型作为键,将 Action 或 Func 作为值,如下所示:

      var dispatcher = new Dictionary<Type, Action<Shape>>
      {
          { typeof(Rectangle), DoSomethingWithRectangle },
          { typeof(Circle), DoSomethingWithCircle }
      }
      
      private void DoSomethingWithRectangle(Shape shape)
      {
          var rectangle = (Rectangle)shape;
          Console.WriteLine($"Rectangle Height: {rectangle.Height} Width: {rectangle.Width}");
      }
      
      private void DoSomethingWithCircle(Shape shape)
      {
          var circle = (Circle)shape;
          Console.WriteLine($"Circle Radius: {circle.Radius}");
      }
      

      这只是一个简单的例子。字典应该存在于调度程序中,您可以在其中使用一些方法来注册不同的类型,而不是方法,您还可以注册可以处理每个特定实例类型的类型或类的实例。如果这种调度仍然不匹配你所有的情况,你可以看看MediatR,它用一堆类型安全的接口来正式化这些东西,你用这些接口来定义你的输入类型、所需的返回值和能够胜任这项工作的处理程序。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-08-07
        • 1970-01-01
        • 1970-01-01
        • 2012-03-05
        • 1970-01-01
        • 1970-01-01
        • 2019-02-28
        相关资源
        最近更新 更多