C#中的封装继承与多态性
封装
封装的概念
在C#中,封装是一种将数据和操作数据的方法绑定在一起,并对外部隐藏数据实现细节的机制。简单来说,它把相关的变量和方法组合成一个单元,也就是类,并且通过访问修饰符来控制对这些成员的访问。这样做的目的是保护数据的完整性和安全性,防止外部代码对数据进行不恰当的访问和修改。
例如,考虑一个简单的“人”类,其中可能包含姓名、年龄等属性。如果不进行封装,外部代码可以随意修改这些属性,可能导致数据的不一致或不合理。通过封装,我们可以限制对这些属性的访问,只允许通过特定的方法来修改,从而保证数据的正确性。
访问修饰符
- public:公共访问修饰符,表示该成员可以被任何其他代码访问。这是最宽松的访问级别。
public class PublicClass
{
public int PublicField;
public void PublicMethod()
{
Console.WriteLine("This is a public method.");
}
}
在上述代码中,PublicField
和PublicMethod
可以在程序的任何地方被访问,只要有PublicClass
的实例。
- private:私有访问修饰符,意味着该成员只能在声明它的类内部被访问。这是最严格的访问级别。
public class PrivateClass
{
private int privateField;
private void privateMethod()
{
Console.WriteLine("This is a private method.");
}
public void AccessPrivateMembers()
{
privateField = 10;
privateMethod();
}
}
在这个例子中,privateField
和privateMethod
只能在PrivateClass
内部被访问。外部代码无法直接访问它们,只能通过AccessPrivateMembers
这样的公共方法间接访问。
- protected:受保护访问修饰符,表明该成员可以被声明它的类及其派生类访问。
public class BaseClass
{
protected int protectedField;
protected void protectedMethod()
{
Console.WriteLine("This is a protected method.");
}
}
public class DerivedClass : BaseClass
{
public void AccessProtectedMembers()
{
protectedField = 20;
protectedMethod();
}
}
这里,DerivedClass
可以访问BaseClass
中的protectedField
和protectedMethod
,但其他无关的类不能访问。
- internal:内部访问修饰符,意味着该成员可以被同一程序集内的任何代码访问,但不能被其他程序集访问。如果我们有一个类库项目,内部成员在该类库内部是可见的,但对于引用该类库的外部项目是不可见的。
internal class InternalClass
{
internal int internalField;
internal void internalMethod()
{
Console.WriteLine("This is an internal method.");
}
}
- protected internal:这是
protected
和internal
的组合,意味着该成员可以被同一程序集内的任何代码访问,也可以被其他程序集中该类的派生类访问。
属性(Properties)实现封装
属性是C#中实现封装的重要方式。属性提供了一种灵活的机制来读取、写入或计算私有字段的值。属性看起来像字段,但实际上是方法,这样可以在访问属性时执行额外的逻辑。
public class Person
{
private string name;
public string Name
{
get { return name; }
set
{
if (!string.IsNullOrEmpty(value))
{
name = value;
}
else
{
throw new ArgumentException("Name cannot be empty.");
}
}
}
private int age;
public int Age
{
get { return age; }
set
{
if (value > 0 && value < 120)
{
age = value;
}
else
{
throw new ArgumentException("Invalid age.");
}
}
}
}
在上述Person
类中,Name
和Age
属性封装了对私有字段name
和age
的访问。在设置属性值时,进行了数据验证,确保数据的合理性。
class Program
{
static void Main()
{
Person person = new Person();
person.Name = "John";
person.Age = 30;
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
}
通过属性,外部代码可以方便地访问和修改对象的状态,同时类内部可以控制数据的完整性。
继承
继承的概念
继承是C#中一个重要的特性,它允许一个类从另一个类获取成员(属性、方法等)。被继承的类称为基类(或父类),继承的类称为派生类(或子类)。通过继承,派生类可以重用基类的代码,并且可以添加新的成员或修改继承的成员。
继承的主要优点是代码复用和层次结构的组织。例如,我们有一个Animal
类,包含一些通用的属性和方法,如Name
和MakeSound
。然后我们可以创建Dog
、Cat
等派生类,这些派生类继承自Animal
类,并且可以根据自身特点实现MakeSound
方法。
基类和派生类的定义
在C#中,使用:
符号来表示一个类继承自另一个类。
public class Animal
{
public string Name { get; set; }
public void MakeSound()
{
Console.WriteLine("The animal makes a sound.");
}
}
public class Dog : Animal
{
public void Bark()
{
Console.WriteLine("The dog barks.");
}
public override void MakeSound()
{
Console.WriteLine("The dog barks woof woof.");
}
}
在上述代码中,Dog
类继承自Animal
类。Dog
类不仅拥有Animal
类的Name
属性和MakeSound
方法,还添加了自己的Bark
方法。并且Dog
类重写了MakeSound
方法,提供了更具体的实现。
访问基类成员
派生类可以访问基类的非私有成员。在派生类中,可以使用base
关键字来访问基类的成员。
public class Vehicle
{
public string Brand { get; set; }
public virtual void Drive()
{
Console.WriteLine("The vehicle is driving.");
}
}
public class Car : Vehicle
{
public int NumberOfSeats { get; set; }
public override void Drive()
{
base.Drive();
Console.WriteLine($"The car {Brand} with {NumberOfSeats} seats is driving.");
}
}
在Car
类的Drive
方法中,通过base.Drive()
调用了基类Vehicle
的Drive
方法,然后添加了自己的逻辑。这样既复用了基类的代码,又进行了扩展。
构造函数和继承
当创建派生类的实例时,会先调用基类的构造函数,然后再调用派生类的构造函数。我们可以在派生类的构造函数中使用base
关键字来调用基类的特定构造函数。
public class Shape
{
public string Color { get; set; }
public Shape(string color)
{
Color = color;
}
}
public class Circle : Shape
{
public double Radius { get; set; }
public Circle(string color, double radius) : base(color)
{
Radius = radius;
}
}
在Circle
类的构造函数中,通过: base(color)
调用了Shape
类的构造函数,传递了颜色参数。这样可以确保基类的初始化逻辑被正确执行。
继承的限制
- C#中只支持单继承,即一个类只能有一个直接基类。这避免了多重继承带来的复杂性和冲突,如菱形继承问题。
- 不能继承
sealed
类。sealed
类表示该类不能被其他类继承,例如System.String
类就是sealed
的,不能有派生类。
多态性
多态性的概念
多态性是C#面向对象编程的核心特性之一,它允许通过基类的引用调用派生类的方法。简单来说,同一操作作用于不同的对象,可以有不同的解释和实现,从而产生不同的执行结果。多态性使得代码更加灵活和可扩展,提高了软件的可维护性。
多态性主要通过方法重写(override)和接口实现来体现。它分为编译时多态(静态多态)和运行时多态(动态多态)。
编译时多态(静态多态)
编译时多态主要通过方法重载(overload)来实现。方法重载是指在同一个类中,有多个方法具有相同的名称,但参数列表不同(参数的个数、类型或顺序不同)。编译器在编译时根据调用方法时传递的参数来决定调用哪个方法。
public class MathOperations
{
public int Add(int a, int b)
{
return a + b;
}
public double Add(double a, double b)
{
return a + b;
}
public int Add(int a, int b, int c)
{
return a + b + c;
}
}
在上述MathOperations
类中,有三个Add
方法,它们的参数列表不同。
class Program
{
static void Main()
{
MathOperations mathOps = new MathOperations();
int result1 = mathOps.Add(2, 3);
double result2 = mathOps.Add(2.5, 3.5);
int result3 = mathOps.Add(2, 3, 4);
Console.WriteLine($"Result1: {result1}, Result2: {result2}, Result3: {result3}");
}
}
编译器根据传递的参数类型和个数,在编译时就能确定调用哪个Add
方法。
运行时多态(动态多态)
运行时多态主要通过方法重写(override)和虚方法(virtual)来实现。当一个方法在基类中被声明为virtual
,派生类可以使用override
关键字来提供该方法的不同实现。在运行时,根据对象的实际类型来决定调用哪个版本的方法。
public class Shape
{
public virtual void Draw()
{
Console.WriteLine("Drawing a shape.");
}
}
public class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle.");
}
}
public class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a rectangle.");
}
}
在上述代码中,Shape
类的Draw
方法被声明为virtual
,Circle
和Rectangle
类重写了这个方法。
class Program
{
static void Main()
{
Shape shape1 = new Circle();
Shape shape2 = new Rectangle();
shape1.Draw();
shape2.Draw();
}
}
这里,shape1
和shape2
虽然都是Shape
类型的引用,但在运行时,根据它们实际指向的对象类型(Circle
和Rectangle
),分别调用了Circle
和Rectangle
类的Draw
方法。
抽象类和抽象方法
抽象类是一种不能被实例化的类,它主要作为其他类的基类,为派生类提供一个通用的框架。抽象类可以包含抽象方法,抽象方法只有声明,没有实现,必须在派生类中被重写。
public abstract class AbstractShape
{
public abstract void Draw();
}
public class Triangle : AbstractShape
{
public override void Draw()
{
Console.WriteLine("Drawing a triangle.");
}
}
在上述代码中,AbstractShape
是抽象类,它的Draw
方法是抽象方法。Triangle
类继承自AbstractShape
类,并实现了Draw
方法。
接口与多态
接口是一种特殊的抽象类型,它只包含方法、属性等的声明,不包含实现。一个类可以实现多个接口,从而实现多态性。接口常用于实现不相关类之间的功能共享。
public interface IDrawable
{
void Draw();
}
public class Square : IDrawable
{
public void Draw()
{
Console.WriteLine("Drawing a square.");
}
}
public class Star : IDrawable
{
public void Draw()
{
Console.WriteLine("Drawing a star.");
}
}
在上述代码中,Square
和Star
类都实现了IDrawable
接口,它们都必须实现Draw
方法。这样,通过IDrawable
接口的引用,可以调用不同类的Draw
方法,实现多态。
class Program
{
static void Main()
{
IDrawable drawable1 = new Square();
IDrawable drawable2 = new Star();
drawable1.Draw();
drawable2.Draw();
}
}
通过封装、继承和多态性,C#提供了强大的面向对象编程能力,使得代码更加模块化、可复用、可维护和可扩展。开发者可以根据具体的需求,灵活运用这些特性来构建高效、健壮的软件系统。无论是小型的控制台应用程序,还是大型的企业级应用,这些特性都起着至关重要的作用。在实际开发中,深入理解和熟练运用封装、继承和多态性,能够显著提高开发效率和代码质量。例如,在开发图形绘制库时,通过继承和多态性可以方便地处理不同形状的绘制,通过封装可以保护图形的内部状态不被随意篡改。在企业级应用开发中,通过合理的继承结构可以复用通用的业务逻辑,通过接口实现多态可以方便地替换不同的实现策略,如数据访问层的不同数据库实现。总之,掌握C#中的封装、继承和多态性是成为优秀C#开发者的必经之路。