我怎样才能确保我实施开放/封闭原则?

时间:2014-06-12 22:32:55

标签: c# solid-principles

假设我有一个名为Shape的基类,我希望每个形状都能够返回自己的区域。我可以使Shape成为接口或抽象类(可能还有其他方式),但最终结果是每个形状都有一个名为Area的函数。

所以我想说我有一个像:

这样的集合
List<Shape> bag = loadShapesFromXML(...)

如果我想将所有区域加在一起,我可以这样做:

int total=0;
foreach (var s in bag)
   total += s.Area();

这遵循开放/封闭原则就好了。我遇到的问题是loadShapesFromXML。假设我的XML看起来像这样:

<bag>
  <circle id="1" radius="5" />
  <square id="2" length="3" />
  <rectangle id="3" length="6" width="7"/>
</bag>

我的loadShapesFromXML方法必须检查“包”中的每个项目,看看它是什么类型的形状。除了使用反射来查看形状的类型是圆形/方形/矩形之外,每当我决定添加新形状时,我还能做些什么来不必修改此函数?

3 个答案:

答案 0 :(得分:2)

您的形状对象已关闭,无法按设计进行修改 - 因此您无法添加ReadFromXXXXWriteToXXXX方法。要实现序列化,您可以使用其他一些组件来理解存储中的数据结构和运行时对象之间的映射 - 由data mapper pattern覆盖。

非常基本&#34; mapper&#34;对于Xml节点,将是由节点名称索引的读取器函数字典:

  var creatorsMap = new Dictionary<string, Func<XElement, Shape>>
     {{"rectangle", node => new Rectangle(node.Element(....) ...)}};
  ...
  shapes.Add(creatorsMap[node.Name](node));

如果您关注的是如何发现所有可能的形状 - 而不是反射是查找所有类型的可能方法,那么在代码或配置中手动添加其他类型也是一种选择。如果您正在使用依赖注入容器,它也可以提供一些方法来构建/帮助这种映射方法。

答案 1 :(得分:1)

好吧,我觉得我找到了一个很好的方法。我从上面对我的XML进行了一些更改。

<bag>
  <circle id="1" radius="5">Circle 1</circle> />
  <square id="2" length="3">Square 1</square> />
  <rectangle id="3" length="6" width="7">Rectangle 1</rectangle>/>
</bag>

这是抽象的形状类。注意它有两个构造函数。第二个是传入&#34; bag&#34;中找到的XElement对象。

public abstract class shape
{
    private readonly string _ID;
    public string id
    {
        get
        {
            return _ID;
        }
    }

    public string Name { get; set; }

    public shape(string id, string name)
    {
        _ID = id;
        this.Name = name;
    }

    public shape(XElement element)
    {
        _ID = element.Attribute("id").Value;
        this.Name = element.Value;
    }

    public abstract XElement GetXElement();

    public abstract double Area();

}

现在这是一个圆圈的实例。请注意,构造函数采用该形状的唯一元素并相应地填充属性。另请注意,如果您希望写回XML文件,GetXElement将正确构建XML节点。

public class circle : shape
{
    public int Radius { get; set; }

    public circle(string id, string name, int radius)
        : base(id, name)
    {
        this.Radius = radius;
    }

    public circle(XElement element)
        : base(element)
    {
        this.Radius = int.Parse(element.Attribute("radius").Value);
    }

    public override XElement GetXElement()
    {
        return new XElement("circle", new XAttribute("id", this.id), new XAttribute("radius", this.Radius), this.Name);
    }

    public override double Area()
    {
        return Math.PI * Radius * Radius;
    }
}

以下是如何使用反射来使用开放/封闭原则来获取区域。请注意,形状都是从XML文件的内容构造的, Console.WriteLine(element)语句生成XML供您使用。在这种情况下,输出将与输入相同,但如果您更改了相关形状的属性,则此XML输出将更改为匹配。

    public void TestMethod1()
    {
        var doc = XDocument.Load(xmlFile);
        double area=0;
        foreach (var shapeItem in doc.Descendants("bag").Descendants())
        {
            var type = Type.GetType("StackOverflowShapes." + shapeItem.Name + ",StackOverflowShapes");
            var myShape = (shape)Activator.CreateInstance(type, shapeItem);
            area += myShape.Area();
            var element = myShape.GetXElement();
            Console.WriteLine(element);
        }

        Assert.Equal(129.5398, area, 4);
    }

现在可以添加新形状而无需修改例程来计算总面积。此外,每个形状都负责构建自己的XML表示,并可以从XML表示创建自己的实例。

答案 2 :(得分:0)

逻辑必须去某个地方,但如果反射已经完成且你需要loadShapesFromXML不改变,你可以将Shape对象类定义为通用而不是特定的,并让它包含所有维度类型。

class Shape
  var radius;
  var length;
  var width;
  var diagonal; //new ways of defining a shape would go here
  var id;
  var name;

反思是一种更好的方法,但至少这样你就可以做到这一点 无需修改loadShapesFromXML方法即可对象形状。但是,您必须修改Shape类以处理新的形状类型,并能够推断它实际上是哪种特定的形状类型(方形,圆形等)。

if(id == 1)
  bag.add(new Circle(this));
else if(id == 2)
  bag.add(new Square(this));
...