首先說一句:提到封裝,可能有些人想到的是把數據成員設為私有,其實個人覺得應該把封裝看得廣義一些:封裝即隱藏。
大家應該常常遇到這樣一種情況:通過一個類的方法返回一個對象、或對象列表(其實也是對象),比如得到一個部門的員工、獲取一個設備下的子設備等。
一、我們先寫一段示例代碼,其中定義了員工和部門兩個類,通過部門可以得到該部門的員工。
public class Department
{
public List<Employee> Employees;
public Department()
{
Employees = new List<Employee>();
for (int i = 1; i <= 10; i++)
{
Employees.Add(new Employee()
{
Code = i.ToString("0000"),
Name = "Name" + i
}
);
}
}
}
public class Employee
{
public string Code;
public string Name;
}
這是最簡單的寫法,要得到一個部門的員工,直接訪問Department的公有數據成員Employees即可。
但是這段代碼估計大家都不能容忍,主要有兩個問題:
1、沒有任何封裝而言,任何外部代碼可以對其為所欲為,可以department.Employees = null;也可以department.Employees[0] = null;……
2、.NET中數據綁定只支持屬性,而不支持公有數據成員。設置某控件的DataSource為department.Employees將得不到你預期的結果。
二、我們現在做一點優化
public class Department
{
private List<Employee> employees;
public IList<Employee> Employees
{
get { return employees.AsReadOnly(); }
}
public Department()
{
employees = new List<Employee>();
for (int i = 1; i <= 10; i++)
{
employees.Add(new Employee(i.ToString("0000"),"Name" + i));
}
}
}
public class Employee
{
private string code;
private string name;
public string Code
{
get { return code; }
set { code = value; }
}
public string Name
{
get { return name; }
set { name = value; }
}
public Employee()
{
}
public Employee(string code, string name)
{
this.code = code;
this.name = name;
}
}
現在的結果是:
- 當外部代碼寫department.Employees = null時,編譯通不過;
- 當外部代碼寫department.Employees[0]=null時,運行期異常;
- department.Employees也可以作為某控件的DataSource。
雖然有了些進步,但問題依然存在:雖然不能修改Employees ,但是可以修改其中某個Employee的屬性,例如department.Employees[0].Code=XXX。也就是說“封裝尚未成功”……
要解決這個問題,有兩個方法:
1、使用接口
2、使用視圖類(姑且這樣命名吧……)
三、使用接口:
定義一個接口,提供只讀屬性,外部代碼得到的是接口。代碼如下:
public class Department
{
private List<IEmployee> employees;
public IList<IEmployee> Employees
{
get { return employees.AsReadOnly(); }
}
public Department()
{
employees = new List<IEmployee>();
for (int i = 1; i <= 10; i++)
{
employees.Add(new Employee(i.ToString("0000"), "Name" + i));
}
}
}
public interface IEmployee
{
string Code { get; }
string Name { set; }
}
public class Employee : IEmployee
{
private string code;
private string name;
public string Code
{
get { return code; }
set { code = value; }
}
public string Name
{
get { return name; }
set { name = value; }
}
public Employee()
{
}
public Employee(string code, string name)
{
this.code = code;
this.name = name;
}
}
問題似乎解決了,因為department.Employees[0].Code=XXX這樣的代碼已經編譯不通過了。
但是,如果使用者猜到了實現接口的對象類型,依然可以修改,例如:
Department department = new Department();
Employee tmp = null;
tmp = department.Employees[0] as Employee;
tmp.Code = null;
還是沒封好……那我們就用視圖類吧。
四、所謂視圖類,就是定義一個類EmployeeView作為Employee的只讀視圖,外部代碼只能訪問這個只讀視圖。代碼如下:
public class Department
{
private List<EmployeeView> employees;
public IList<EmployeeView> Employees
{
get { return employees.AsReadOnly(); }
}
public Department()
{
employees = new List<EmployeeView>();
for (int i = 1; i <= 10; i++)
{
employees.Add(new EmployeeView(new Employee(i.ToString("0000"), "Name" + i)));
}
}
}
public class EmployeeView
{
private Employee data;
public string Code
{
get { return data.Code; }
}
public string Name
{
get { return data.Name; }
}
public EmployeeView(Employee data)
{
this.data = data;
}
}
public class Employee
{
private string code;
private string name;
public string Code
{
get { return code; }
set { code = value; }
}
public string Name
{
get { return name; }
set { name = value; }
}
public Employee()
{
}
public Employee(string code, string name)
{
this.code = code;
this.name = name;
}
}
這樣,對外部使用者算是做到了真正的封裝,無論有意還是無意都休想破壞里面的數據了☺。
至此算是“隱藏”好了,代價是雙倍的代碼量和類的增加……
這個代價值不值得呢?視具體情況吧!