封装
封装是面向对象oop的四大基本原则之一(其他三个是继承,多态,抽象)
封装的核心思想
- 将数据(字段)和操作数据的方法捆绑在一起,形成一个类
- 控制对内部实现细节的访问,将对象的内部状态(字段)隐藏起来只通过一个公开的,受控的接口(通常是属性和方法)与外部进行交互
为什么需要封装?
- 数据保护
- 防止非法修改:直接暴露类的内部字段是非常危险的,外部代码可以随意不受限制的修改这个字段的值,可能导致对象处于无效或不一致的状态(如年龄为负数,账户余额被随意扣除)
- 验证和控制:通过封装,你可以在设置值之前进行验证是否在合理范围内。如果无效值可以抛出异常,忽略请求或进行修正确保对象状态始终有效
- 隐藏实现细节
- 外部代码不需要知道对象是如何存储数据或执行操作的,它只需要知道对象提供了哪些方法和设置/获取哪些信息
-
提高灵活性和可维护性
-
增强安全性
- 敏感数据可标记为private防止外部代码直接访问和篡改
访问修饰符
访问修饰符是实现封装的关键工具,它控制类成员的可访问范围
- public 访问不受限。用于定义公共接口
- private 访问仅限于包含的它的类或结构体内部。这是类成员的默认访问级别。用于隐藏内部实现细节
- protected 访问仅限于包含它的类以及从该类派生的类
- internal 访问限于当前程序集。
- protected internal 访问限于当前程序集或从包含类派生的类型
- private protected 访问仅限于包含它的类或当前程序续集中从该类派生的类型
属性
属性是c#中实现分封装的一种强大机制,它提供了一种访问类或对象数据成员的替代方式,看起来像访问字段,实际上是通过方法(访问器)来执行
它的基本语法
csharp
namespace ConsoleApp1
{
class Myclass
{
//类成员默认是私有字段 存储实际数据
string _name;
int _age;
//我们可以通过定义一个(公共的接口)Name成员来对_name进行访问和修改,这样我们可以保证私有字段始终有效
public string Name
{
get { return _name; } //get访问器 读取值
set { _name = value; }//set访问器 设置值 value是隐式参数 隐式参数是指那些没有在方法签名中显示声明,但可以在方法内部访问的参数
}
public int Age
{
get { return _age; }
set
{
//我们可以在set属性中判断值是否有效 无效抛出异常,有效直接给私有字段_age赋值
if (value >= 0 && value <= 150)
_age = value;
else
//无效抛出异常的代码逻辑
Console.WriteLine("抛出异常代码");
}
}
}
internal class Program
{
static void Main(string[] args)
{
Myclass class1 = new Myclass();
class1.Name = "张三";
class1.Age = 100;
Console.WriteLine($"姓名{class1.Name}今年{class1.Age}");
}
}
}
关键组成部分
- get访问器:
- 当读取属性值时调用
- 必须返回一个与属性值类型匹配的的值
- 可以包含逻辑(如计算,返回默认值等)
- 语法:get{...return...;}
- set访问器:
- 当为属性赋值时调用
- 有一个隐式参数value 其类型与属性类型相同 代表要赋值的值
- 通常用于将值存储到私有字段中
- 可以包含验证逻辑,通知逻辑,日志记录等
- 语法set{...}
- 访问器可见性
- 默认情况下 get和set访问器具有与属性本身相同的访问级别
- 也可以单独修改访问器的可见性(通常用于创建只读或只写属性)
csharp
public string ReadOnlyName { get; private set; } // 外部只读,类内部可写
public int WriteOnlyId { private get; set; } // 外部只写,类内部可读 (较少见)
属性的优势
- 访问控制:提供对字段读写操作的完全控制
- 添加逻辑:可以在get/set中添加验证计算等逻辑代码
- 数据绑定友好:属性是数据绑定机制的标准方式
- 语法简洁
自动属性
对于不需要在get/set中添加额外逻辑的简单属性,我们可以用自动属性
csharp
public class Person
{
// 自动属性 - 编译器会自动生成一个隐藏的私有后备字段
public string Name { get; set; }
public int Age { get; set; }
}
- 工作原理:编译器会在编译时自动为你生成一个私有的匿名的后备字段,并实现基于get和set访问器来操作这个字段
- 初始胡:可以在声明时初始化
public int a {get;set;}=1;
- 只读自动属性 在构造函数中初始化
csharp
public class Person
{
public string Id {get;}//只读自动属性(没有set 如果想要初始化需要在构造函数中初始化)
public Person(int id)
{
Id = id;
}
}
- 使用init访问器允许在对象初始化器中赋值
csharp
namespace ConsoleApp1
{
class Myclass
{
//可以在构造函数或对象初始化器中赋值
public int b { get; init; }
}
}
internal class Program
{
static void Main(string[] args)
{
Myclass class1 = new Myclass { b=1 };//对象初始化器
}
}
}
总结:
- 封装是oop的核心原则,旨在保护数据,隐藏代码细节提高代码安全性和可维护性
- 访问修饰符是实现封装的基础控制成员的可见性
- 属性是c#中实现数据封装的首选方式。通过get和set访问器提供对私有字段的受控访问,允许添加验证和逻辑
- 自动属性简化了不需要的额外逻辑属性的声明,编译器自动处理后备字段和基本访问器
- 优先使用属性而不是公共字段来暴露类的数据成员