如何讓返回的對象為只讀——一步步封裝起來


首先說一句:提到封裝,可能有些人想到的是把數據成員設為私有,其實個人覺得應該把封裝看得廣義一些:封裝即隱藏。

大家應該常常遇到這樣一種情況:通過一個類的方法返回一個對象、或對象列表(其實也是對象),比如得到一個部門的員工、獲取一個設備下的子設備等。

 

一、我們先寫一段示例代碼,其中定義了員工和部門兩個類,通過部門可以得到該部門的員工。

    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;
}
}

這樣,對外部使用者算是做到了真正的封裝,無論有意還是無意都休想破壞里面的數據了☺。

至此算是“隱藏”好了,代價是雙倍的代碼量和類的增加……

這個代價值不值得呢?視具體情況吧!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM