代码改变世界

如何让返回的对象为只读——一步步封装起来

2012-01-05 13:25  Higel  阅读(2255)  评论(9编辑  收藏  举报

首先说一句:提到封装,可能有些人想到的是把数据成员设为私有,其实个人觉得应该把封装看得广义一些:封装即隐藏。

大家应该常常遇到这样一种情况:通过一个类的方法返回一个对象、或对象列表(其实也是对象),比如得到一个部门的员工、获取一个设备下的子设备等。

 

一、我们先写一段示例代码,其中定义了员工和部门两个类,通过部门可以得到该部门的员工。

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

这样,对外部使用者算是做到了真正的封装,无论有意还是无意都休想破坏里面的数据了☺。

至此算是“隐藏”好了,代价是双倍的代码量和类的增加……

这个代价值不值得呢?视具体情况吧!