1. 入门
想象一下你在设计一辆现代汽车。里面有几百个复杂零件、敏感的电子设备、各种精细的设置。很明显,没人应该随便就能摸到发动机控制单元然后开始拧螺丝。如果谁都能乱动,车可能就会变得很奇怪,到时候你还得检查到底是谁把啥给弄坏了。
编程也是一样的。当你写一个类的时候,肯定想保护它的内部——不让别人随便改重要的数据,或者乱动那些外部根本不该知道的方法。这就是封装的本质——面向对象编程三大核心原则之一。
封装让你能把实现细节藏起来,明确规定哪些东西能“暴露”给外部,哪些必须留在内部。就像你只让用户打开手套箱,但把引擎盖锁得死死的。类自己决定哪些数据可以给外部代码看,哪些要保密。这样代码就更靠谱、更有逻辑,也更容易维护。
严格点说,封装——就是把数据(字段)和跟它相关的行为(方法)合成一个整体,同时限制外部直接访问对象内部组件的方式。
2. 访问修饰符:守护自己的地盘
在C#(还有其他OOP语言)里,封装是靠访问修饰符实现的。这些“标签”决定了类的哪些成员(字段、方法、属性等等)能被外部访问,哪些只能内部用。
我们之前其实已经见过它们了,不过还是来回顾一下,并且补充下常见的修饰符:
| 修饰符 | 可访问范围... |
|---|---|
|
所有人!项目里的任何代码(如果是库,外部也能访问) |
|
只有这个类内部能访问 |
|
这个类和它所有的子类都能访问 |
|
只在当前程序集(项目)里能访问 |
|
要么在当前程序集,要么在子类里能访问 |
|
只有当前程序集里的子类能访问 |
这节课我们主要关注最常用的:public、private,顺带提一下protected,别让脑子太累。
区分内部和外部
来写个Dog类:
public class Dog
{
public string Name;
public int Age;
public void Bark()
{
Console.WriteLine($"{Name} 说: 汪!");
}
}
这里所有字段和方法都是public。也就是说,任何人都能改狗的名字或者年龄:
Dog rex = new Dog("雷克斯", 5);
rex.Name = "小花"; // 突然换了个身份!
rex.Age = -999; // 年龄出大问题了
其实Name和Age这俩字段挺重要的,肯定不想让谁都能乱改。
这样写不太行
如果所有字段都public,就很容易破坏类的逻辑:外部随便一改,对象就不靠谱了。比如给狗设个负年龄,或者名字写成"%$#!??"。
3. 隐藏字段:用private
一般来说,类的字段都私有(用private修饰)。这样只有类内部才能改它们,外部根本动不了。
public class Dog
{
private string name;
private int age;
public void Bark()
{
Console.WriteLine($"{name} 说: 汪!");
}
}
现在如果外部想直接访问字段:
Dog rex = new Dog("雷克斯", 5);
rex.name = "小花"; // 编译错误
编译器会直接说:“没权限!”
为啥要这么严格?灵活性去哪了?
所有的“灵活性”其实是靠专门的方法或者属性(下节课会详细讲)来实现的,这样你可以按规则控制数据的访问和修改。
4. 封装实战:加点控制
比如说,我们想让狗的年龄不能设成负数:
public class Dog
{
private string name;
private int age;
public Dog(string name, int age)
{
this.name = name;
if (age >= 0)
this.age = age;
else
this.age = 0; // 不让设“奇怪”的年龄
}
public void Bark()
{
Console.WriteLine($"{name} 说: 汪!");
}
// 安全修改年龄的方法
public void SetAge(int newAge)
{
if (newAge >= 0)
age = newAge;
// 也可以加个else:提示值不对
}
}
现在外部谁也不能直接搞坏字段了。要改年龄只能用专门的方法,而且会先检查一遍。
5. 字段 vs 访问方法(getter/setter)
这种做法——把字段设成private,然后提供方法来操作——就叫数据封装(data encapsulation)。想读字段值就写getter方法,想改就写setter方法。
public class Dog
{
private string name;
public Dog(string name)
{
this.name = name;
}
public string GetName()
{
return name;
}
public void SetName(string newName)
{
// 这里可以加名字合法性检查
name = newName;
}
}
不过! 自从C#有了属性(properties),这种方法就很少用了——属性让代码更简洁更好用(下节课会详细讲)。
6. 方法和类的访问修饰符
字段不是全部!访问修饰符也能用在方法(类成员函数)甚至类本身上。
只在类内部用的方法(比如内部逻辑的辅助函数),一般都设成private。
公开方法——就是类的接口(别和interface关键字搞混),也就是用户能用的那些东西。
7. 封装常见错误
错误1:啥都设成public。
觉得越多能访问越方便,其实这样会让外部能乱改类的内部,结果代码变得脆弱又难预测,尤其是大项目里。
错误2:改了访问修饰符导致外部代码崩了。
重构的时候不小心改了方法或字段的修饰符,结果其他用到它们的代码突然编译不过或者行为不对。尤其是公开API时更要小心。
错误3:把局部变量和类字段搞混。
有时候开发者忘了方法里声明的变量只在这个方法里活着,而类字段在所有方法里都能用。如果名字还一样就更容易出错。
错误4:忽视private和protected。
很多人怕用受限访问,担心以后用不到。其实封装就是要把多余的东西藏起来,只暴露真正需要的部分。
GO TO FULL VERSION