我如何确保我在执行开放/封闭原则?

本文关键字:原则 何确保 确保 执行 | 更新日期: 2023-09-27 18:08:28

假设我有一个叫做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方法必须检查"袋子"中的每个项目,以查看它的形状类型。除了使用反射来查看形状类型是否为圆形/正方形/矩形之外,我还可以做些什么来避免每次决定添加新形状时都修改此函数?

我如何确保我在执行开放/封闭原则?

你的形状对象是封闭的修改设计-所以你不能添加ReadFromXXXXWriteToXXXX方法。要实现序列化,你可以使用一些其他组件来理解存储数据结构和运行时对象之间的映射——由数据映射器模式覆盖。

Xml节点的最基本的"mapper"将是按节点名称索引的阅读器函数字典:

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

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

好的,我想我找到了一个很好的方法。我对上面的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>

这是抽象的shape类。注意它有两个构造函数。第二种方法是传入一个在"bag"中找到的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表示创建自身的实例。

逻辑将不得不去某个地方,但如果反射出来,你需要loadShapesFromXML不改变,你可以定义你的形状对象类是一般的,而不是特定的,并有它包括所有的维度类型。

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));
...