用C#进行面向对象的编程

嗨,安华 在本文中,我想分享使用C#的面向对象编程(OOP)。 是的,会有很多技术术语和代码。

面向对象编程

什么是面向对象的编程或OOP? 顾名思义,这是编写代码的原则,其中我们将某些“行为”行为视为对象。 现在,什么是物体? 对象是捆绑状态和行为的软件。 此状态和行为在类中定义。 类基本上是对象的蓝图。 类的主要行为是它具有以下能力:

  • 封装来自外界的不必要的代码,
  • 多态地表现,
  • 通过继承描述基于现有类的新类。

好的,为了说明OOP的实现,我想在这里使用C#作为主要语言。 您可以使用任何其他支持OOP的语言,例如Java,C ++或Python。

让我们写代码

比方说,我们的客户希望我们创建一个具有两个主要功能的程序,即打印并获取三角形的面积(使用Heron公式)。 让我们不使用类或OOP,而仅使用函数。 下面是代码示例:

 私有静态void Main(string [] args) 
{
Console.WriteLine(“ Hello World!”);
 双面A = 3; 
双面B = 4;
双面C = 5;
 双周长= sideA + sideB + sideC; 
double s = 0.5 *周长;
两倍面积= Math.Sqrt(s *(s-sideA)*(s-sideB)*(s-sideC));
  Console.WriteLine($“您好,这是一个面积为{area}的三角形”); 
Console.ReadKey(true);
}

如果我们运行上面的代码,它将运行。 完善!?

或者等待,现在我们的客户想要添加一个矩形 ,该矩形的行为类似于三角形 。 现在,我们需要修改代码来满足他们的要求。

 私有静态void Main(string [] args) 
{
Console.WriteLine(“ Hello World!”);
 双面A = 3; 
双面B = 4;
双面C = 5;
 双周长= sideA + sideB + sideC; 
double s = 0.5 *周长;
两倍面积= Math.Sqrt(s *(s-sideA)*(s-sideB)*(s-sideC));
  Console.WriteLine($“您好,这是一个面积为{area}的三角形”); 
 双倍宽度= 3; 
双高= 4;
double areaRectangle =宽度*高度;
Console.WriteLine($“您好,这是一个矩形,面积为{areaRectangle}”));
Console.ReadKey(true);
}

好的,它仍然有效。 但是,我们的客户希望为我们的程序添加更多形状。 随着添加更多形状,我们的主要方法将变得更大且更难阅读。 当然,我们可以将它们放在单独的方法中。 三角形的例子:

 私有静态double GetArea(双面A,双面B,双面C) 
{
双周长= sideA + sideB + sideC;
double s = 0.5 *周长;
两倍面积= Math.Sqrt(s *(s-sideA)*(s-sideB)*(s-sideC));
返回区域;
}
 私人静态无效打印(双面A,双面B,双面C){ 
双周长= sideA + sideB + sideC;
double s = 0.5 *周长;
两倍面积= Math.Sqrt(s *(s-sideA)*(s-sideB)*(s-sideC));
Console.WriteLine($“您好,这是一个面积为{area}的三角形”);
}

现在,我们的主要方法可以被最小化。 但是,随着更多请求的添加,我们需要添加更多方法。 并且随着我们功能的数量变得庞大,我们将很难维护它。 如果我们的客户想要修改形状的某些行为,那么不影响其他功能就很难更改代码。

但是幸运的是,我们可以利用这个叫做class的小东西。 让我们为三角形矩形创建类。

三角形

 内部类三角形 
{
公共双面A;
公共双面B;
公共双面C;
 公共三角形(双面A,双面B,双面C) 
{
this.sideA = sideA;
this.sideB = sideB;
this.sideC = sideC;
}
 公共双GetArea() 
{
双周长= sideA + sideB + sideC;
double s = 0.5 *周长;
两倍面积= Math.Sqrt(s *(s-sideA)*(s-sideB)*(s-sideC));
返回区域;
}
 公共无效Print() 
{
双周长= sideA + sideB + sideC;
double s = 0.5 *周长;
两倍面积= Math.Sqrt(s *(s-sideA)*(s-sideB)*(s-sideC));
Console.WriteLine($“您好,这是一个面积为{area}的三角形”);
}
}

注意,在上述代码的字段中,我们使用公共访问器。 根据我们的业务需求,我们还可以隐藏这些字段,以便外界无法访问或修改它们(不仅限于字段或状态,还包括方法或行为)。 让我们稍微调整一下代码,然后更改这些字段的访问器:

 内部类三角形 
{
私人双面A;
私人双面B;
私人双面C;
...
}

长方形

 内部类Rectangle 
{
私人双倍宽度;
私人双人身高;
 公共矩形(双倍宽度,双倍高度) 
{
this.width = width;
this.height =高度;
}
 公共双GetArea() 
{
两倍的面积=宽度*高度;
返回区域;
}
 公共无效Print() 
{
两倍的面积=宽度*高度;
Console.WriteLine($“您好,这是一个区域为{area}的矩形”);
}
}

现在这是主要方法:

 私有静态void Main(string [] args) 
{
Console.WriteLine(“ Hello World!”);
  Triangle triangle = new Triangle(3,4,5); 
double areaOfTriangle = triangle.GetArea();
triangle.Print();
 矩形矩形=新矩形(3,4); 
double areaOfRectangle =矩形.GetArea();
矩形。打印();
  Console.ReadKey(true); 
}

正如我们可以看到上面的代码,我们的main方法变得更容易阅读。 我们有两个对象,分别是TriangleRectangle 。 我们可以区分哪个对象调用了自己的方法。 即使我们的客户希望为某个类修改甚至添加功能,我们也可以直接更改该类而不影响其他类。

但是,等等,有一些奇怪的事情。 如果客户希望我们更改面积的计算方式,则需要同时更改GetArea()Print()方法。 (注意Print()方法中有计算区域)。 如果我们在代码中的多个位置实现了如此多的相似功能,这将是一个问题。 因为我们需要跟踪这些实现,并确保将它们更改为具有相同的行为。

很高兴地有另一种原则,叫做“ 不要重复自己干” 。 简而言之。 该原则基本上告诉我们分解功能并将它们组合在一起。 这样,如果功能有任何更改,我们只需要在一个地方进行更改。 现在,让我们调整代码以遵守此原则:

三角形

 内部类三角形 
{
私人双面A;
私人双面B;
私人双面C;
 公共三角形(双面A,双面B,双面C) 
{
this.sideA = sideA;
this.sideB = sideB;
this.sideC = sideC;
}
 公共双GetArea() 
{
双周长= sideA + sideB + sideC;
double s = 0.5 *周长;
两倍面积= Math.Sqrt(s *(s-sideA)*(s-sideB)*(s-sideC));
返回区域;
}
 公共无效Print() 
{
Console.WriteLine($“您好,这是一个三角形,面积为{GetArea()}”));
}
}

长方形

 内部类Rectangle 
{
私人双倍宽度;
私人双人身高;
 公共矩形(双倍宽度,双倍高度) 
{
this.width = width;
this.height =高度;
}
 公共双GetArea() 
{
两倍的面积=宽度*高度;
返回区域;
}
 公共无效Print() 
{
Console.WriteLine($“您好,这是一个矩形,面积为{GetArea()}”));
}
}

凉! 现在,如果我们的客户希望修改我们计算面积的方式,我们可以专注于一个地方进行修改。

可是等等。 现在,我们的客户希望打印功能不仅可以通过常规字符串,而且可以通过XMLHTML格式。 好吧,当然,我们需要在两个类中的Print()方法中都更改实现,并添加另外两个方法:

三角形

 公共无效PrintString() 
{
...
}
 公共无效PrintXML() 
{
...
}
 公共无效PrintHTML() 
{
...
}

长方形

 公共无效PrintString() 
{
...
}
 公共无效PrintXML() 
{
...
}
 公共无效PrintHTML() 
{
...
}

等等! 这只是两个类。 如果我们还有更多的课程怎么办? 我们还必须对所有这些方法进行更改或添加,对吗? 容易做到吗? 我不这么认为。 如果我们的客户想要一次打印一组形状该怎么办?

很高兴,还有另一种我们可以使用的原则,即单一责任原则SRP 。 这个原则从根本上告诉我们,班级应该只承担一个责任或一个改变的理由。 在我们的示例中, Print()方法除了具有形状外,基本上还有其他责任。 也就是说,当我们的客户想要修改和添加print时 ,我们需要更改类的行为。 现在,让我们创建一个新的打印机类:

TrianglePrinter

 类TrianglePrinter 
{
私人List listOfTriangle;
 公共TrianglePrinter(){ 
listOfTriangle = new List ();
}
 公共无效AddTriangle(Triangle triangle) 
{
listOfTriangle.Add(triangle);
}
 公共无效PrintAllInString() 
{
...
}
 公共无效PrintAllInXML() 
{
...
}
 公共无效PrintAllInHTML() 
{
...
}
}

RectanglePrinter:

 内部类RectanglePrinter 
{
私有List listOfRectangle;
 公共RectanglePrinter() 
{
listOfRectangle =新列表();
}
  public void AddRectangle(矩形矩形) 
{
listOfRectangle.Add(rectangle);
}
 公共无效PrintAllInString() 
{
...
}
 公共无效PrintAllInXML() 
{
...
}
 公共无效PrintAllInHTML() 
{
...
}
}

嗯,很好。 现在,由于三角形矩形都省略了Print()方法,因此我们需要提供要从打印机类中调用的行为。 让我们更改两个类,并添加一个名为ToString()的函数:

三角形

 公共重写字符串ToString() 
{
返回$“你好,这是面积为{GetArea()}的三角形”“;
}

长方形

 公共重写字符串ToString() 
{
返回$“你好,这是一个面积为{GetArea()}的矩形”;
}

好的。 现在,如果我们的客户想要更改甚至添加其他打印行为,则无需担心更改形状类的行为。 现在好。

还是? 可以看到,对于每个形状类,我们都需要一台专用的打印机。 因此,如果我们需要创建10个形状类别,我们还需要准备10个专用的打印机类别。 如果我们的客户现在想打印多种形状的图形,该怎么办? 哦,不,不是很好。

很高兴,我们可以在OOP中遵循这种模式,称为继承。 基本上,我们将一些类归为一类,它们通常具有相似的行为。 从那里,我们将这些行为扩展或继承到每个特定的类。

好的,由于这两个类都有两个相似的方法,因此我们将其用作名为Shape的新类。

形状

 内部抽象类Shape 
{
公共抽象双GetArea();
 公共重写字符串ToString() 
{
return $“你好,该区域是{GetArea()}”;
}
}

在上面的类中,请注意GetArea()方法为空,因为每个继承的类都有自己的计算面积的方法。 现在,让我们修改三角形矩形类以从shape类继承。

三角形

 内部类Triangle:形状 
{
私人双面A;
私人双面B;
私人双面C;
 公共三角形(双面A,双面B,双面C) 
{
this.sideA = sideA;
this.sideB = sideB;
this.sideC = sideC;
}
 公共重写double GetArea() 
{
双周长= sideA + sideB + sideC;
double s = 0.5 *周长;
两倍面积= Math.Sqrt(s *(s-sideA)*(s-sideB)*(s-sideC));
返回区域;
}
}

长方形

 内部类Rectangle:形状 
{
私人双倍宽度;
私人双人身高;
 公共矩形(双倍宽度,双倍高度) 
{
this.width = width;
this.height =高度;
}
 公共重写double GetArea() 
{
两倍的面积=宽度*高度;
返回区域;
}
}

请注意,从上面的代码中,我们没有实现ToString()方法,因为它已经在父类( 形状类)中实现了。 现在,让我们将打印机类更改为:

 内部类ShapePrinter 
{
私有列表 listOfShape;
 公共ShapePrinter() 
{
listOfShape =新列表();
}
 公共无效AddShape(形状) 
{
listOfShape.Add(shape);
}
 公共无效PrintAllInString() 
{
foreach(listOfShape中的形状)
{
Console.WriteLine(shape);
}
}
 公共无效PrintAllInXML() 
{
...
}
 公共无效PrintAllInHTML() 
{
...
}
}

现在,我们可以将main方法修改为:

 私有静态void Main(string [] args) 
{
Console.WriteLine(“ Hello World!”);
 形状三角形=新的Triangle(3,4,5); 
形状矩形=新的Rectangle(3,4);
  ShapePrinter打印机= new ShapePrinter(); 
printer.AddShape(triangle);
printer.AddShape(rectangle);
printer.PrintAllInString();
  Console.ReadKey(true); 
}

如我们所见,阅读main方法变得更加容易。 我们初始化两个形状,分别是三角形矩形 。 然后,我们创建打印机并将已创建的形状传递到其中。 然后以常规字符串打印所有内容。 因此,如果我们运行上面的代码,输出将是:

 你好,面积是6 
你好,面积是12

到目前为止,已经足够了。 但是,我们仍然可以改善它。 我们可以在代码中实现多态,因此每种形状的打印结果都可以是唯一的。

让我们首先更改Shape类。 我们可以省略ToString()方法的实现。 而且由于省略此方法将导致抽象类没有任何实现,因此我们可以将抽象类简化为接口。 让我们命名为IShape的接口:

  IShape界面 
{
双GetArea();
}

让我们修改三角形矩形类:

三角形

 内部类Triangle:IShape 
{
...
公共重写字符串ToString()
{
返回$“你好,这是三角形,面积为{GetArea()}”“;
}
}

长方形

 内部类Rectangle:IShape 
{
...
公共重写字符串ToString()
{
返回$“你好,这是矩形,面积为{GetArea()}”。
}
}

让我们修改打印机类,使其仅接受实现IShape的类。

 内部类ShapePrinter { 
私有List listOfShape;
 公共ShapePrinter(){ 
listOfShape =新列表();
}
  public void AddShape(IShape shape){ 
listOfShape.Add(shape);
}
 公共无效PrintAllInString(){ 
foreach(listOfShape中的IShape形状){
Console.WriteLine(shape);
}
}
 公共无效PrintAllInXML(){ 
...
}
 公共无效PrintAllInHTML(){ 
...
}
}

现在让我们在主要方法中使用它们。

 私人静态void Main(string [] args){ 
Console.WriteLine(“ Hello World!”);
  IShape三角形=新的Triangle(3,4,5); 
IShape矩形=新Rectangle(3,4);
  ShapePrinter打印机= new ShapePrinter(); 
printer.AddShape(triangle);
printer.AddShape(rectangle);
printer.PrintAllInString();
  Console.ReadKey(true); 
}

如果运行上面的代码,则将得到:

 你好,这是三角形,面积是6 
您好,这是矩形,面积为12

大! 现在,如果我们的客户要求在各处添加更多形状或转换器,则无需感到困惑。 🙂

 公共抽象双GetArea(); 
 公共重写字符串ToString() 
  { 
 返回$“你好,该区域是{GetArea()}”; 
  } 
  }