65.9K
CodeProject 正在变化。 阅读更多。
Home

SOLID原则:开闭原则 -> 何为、为何以及如何

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (19投票s)

2013年6月28日

CPOL

3分钟阅读

viewsIcon

73077

SOLID原则:开闭原则,C#中的一个简单示例

引言

本文将解释开闭原则 (OCP),并用 C# 展示一个简单的示例。

背景

什么

1988年,Bertrand Meyer 就已经提到了开闭原则 (OCP)
软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
“面向对象软件构建,Bertrand Meyer”
如果可能,当您的应用程序扩展了新功能时,应该避免修改已正常工作的软件。
相反,应该能够通过添加新功能来扩展现有软件,而无需修改当前代码库,也无需添加重复代码或重复功能。

为什么?

请问您从头开始创建全新应用程序的次数与您在现有代码库上添加新功能的次数相比如何?
很有可能您花费更多时间在现有代码库上添加新功能,对吧?
那么请问自己:编写全新代码更容易还是修改现有代码更容易?
OCP 处理您希望构建代码的方式,以获得面向对象技术所宣称的最大优势之一:可重用性和可维护性。

如何

实现开闭原则的最佳方法是首先开始实现单一职责原则:一个类应该只有一个变化的原因。
这将分离代码中的不同关注点。
下一步是用抽象来表示这些单独的关注点,并让这些关注点的使用者与这些抽象进行交互。

用非常直接的方式来陈述开闭原则,可以说:

  • 您应该设计永不改变的模块。
  • 当需求改变时,您可以通过添加新代码来扩展此类模块的行为,而不是更改已正常工作的旧代码。

抽象是实现这一原则的方法。
来自抽象的派生类对修改是关闭的,因为抽象是固定的,但可以通过创建抽象的新派生类来扩展行为。

Using the Code

以下示例展示了一个计算形状数组面积总和的类。
当引入新的形状时,必须实现两件事:

  • shape 类本身。
  • AreaCalculator 必须更改为计算此新 shape 的面积。

AreaCalculator 违反了开闭原则,因为对这段代码的新需求总是包括更改某个现有形状的计算或引入新的形状。
在这两种情况下,都必须更改 AreaCalculator,因此它并没有对修改关闭。
它也没有对扩展开放,因为我们无法在不修改它的情况下扩展 AreaCalulator 的功能。

问题

/***************** Calculates the sum of all shape area's *****************/

public class AreaCalculator
{ 
 public double Area(object[] shapes)
 {
  double area = 0;

  foreach (var shape in shapes)
  {  
   if (shape is Square)
   {
     Square square = (Square)shape;
     area += Math.Sqrt(square.Height);
   }

   if (shape is Triangle)
   {
     Triangle triangle = (Triangle)shape;
     double TotalHalf = (triangle.FirstSide + triangle.SecondSide + triangle.ThirdSide) / 2;
     area += Math.Sqrt(TotalHalf * (TotalHalf - triangle.FirstSide) * 
     (TotalHalf - triangle.SecondSide) * (TotalHalf - triangle.ThirdSide));
   }

   if (shape is Circle)
   {
     Circle circle = (Circle)shape;
     area += circle.Radius * circle.Radius * Math.PI;
   }

  }
  return area;
 }
}
public class Square
{
  public double Height { get; set; }
}
public class Circle
{
  public double Radius { get; set; }
}
public class Triangle
{
  public double FirstSide { get; set; }
  public double SecondSide { get; set; }
  public double ThirdSide { get; set; }
}

以下是解决方案。

步骤 1:通过将面积计算移动到相关的 shape 类来分离职责。
步骤 2:引入相关功能的抽象,并让使用者遵守这些抽象。

现在,当引入新的 shape 或例如某个 shape 的面积计算发生变化时,AreaCalculator 不需要更改,并且可以轻松地扩展新的形状而无需更改。

需要注意的是,真正的封闭性很多时候只可以在理论上实现,因为总有可能发生违反封闭性的变化。例如,如果必须更改面积计算的顺序,那么我们就必须更改 AreaCalculator。这就是为什么必须根据感觉和经验明智地选择封闭性。

解决方案

开放封闭 AreaCalculator

/***************** Calculates the sum of all shape area's *****************/

public class AreaCalculator
{
  public double Area(Shape[] shapes)
  {
    double area = 0;

    foreach (var shape in shapes)
    {
     area += shape.Area();
    }

     return area;
   }
}

引入抽象

public abstract class Shape
{
  public abstract double Area();
}

Square 类实现抽象及其面积计算

public class Square : Shape
{
 public double Height { get { return _height; } }
 private double _height; 

 public Square(double Height)
 {
   _height = Height;
 }

 public override double Area()
 {
   return Math.Sqrt(_height);
 }
}

Circle 类实现抽象及其面积计算

public class Circle : Shape
{
  public double Radius{get{return _radius;}}
 
  private double _radius;

  public Circle(double Radius)
  {
    _radius = Radius;
  }

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

Triangle 类实现抽象及其面积计算

public class Triangle : Shape
{ 
 public double FirstSide {get {return _firstSide;}}
 public double SecondSide { get { return _secondSide; } }
 public double ThirdSide { get { return _thirdSide; } } 
 
 private double _firstSide;
 private double _secondSide;
 private double _thirdSide; 
 
 public Triangle(double FirstSide, double SecondSide, double ThirdSide)
 {
   _firstSide = FirstSide;
   _secondSide = SecondSide;
   _thirdSide = ThirdSide;
 }

 public override double Area()
 {
   double TotalHalf = (_firstSide + _secondSide + _thirdSide) / 2;
   return Math.Sqrt(TotalHalf * (TotalHalf - _firstSide) * 
                   (TotalHalf - _secondSide) * (TotalHalf - _thirdSide));
 }
}

玩得开心!

© . All rights reserved.