10.1.5 为TDate类添加属性
在第7章,我创建了TDate类,我们现在使用属性来扩展这个类。本章DateProperties这个新的例子,基本上是第七章ViewDate这个例子的扩展。这里是这个类新的定义,有了一些新的方法(用于设置和获取属性的值)以及四个属性。
type
TDate = class
private
FDate: TDateTime;
function GetYear: Integer;
procedure SetYear(const Value: Integer);
function GetDay: Integer;
procedure SetDay(const Value: Integer);
function GetMonth: Integer;
procedure SetMonth(const Value: Integer);
public
constructor Create; overload;
constructor Create(Y, M, D: Integer); overload;
procedure SetValue(Y, M, D: Integer); overload;
procedure SetValue(NewDate: TDateTime); overload;
function LeapYear: Boolean;
procedure Increase(NumberOfDays: Integer = 1);
procedure Decrease(NumberOfDays: Integer = 1);
function GetText: string; virtual;
property Day: Integer read GetDay write SetDay;
property Month: Integer read GetMonth write SetMonth;
property Year: Integer read GetYear write SetYear;
property Text: string read GetText;
end;
Year,Day和Month属性使用其对应的方法读取和写入各自的值。这里是支持Month属性所需要方法的实现:
function TDate.GetMonth: Integer;
var
Y, M, D: Word;
begin
DecodeDate(FDate, Y, M, D);
Result := M;
end;
procedure TDate.SetMonth(const Value: Integer);
begin
if (Value < 1) or (Value > 12) then
raise EDateOutOfRange.Create('Invalid month');
SetValue (Value, Day, Year);
end;
调用SetValue执行实际的日期编码,在出错的情况下抛出一个自定义异常。我定义的这个异常类对象在日期超出范围时抛出:
type
EDateOutOfRange = class(Exception);
第四个属性Text仅映射到一个read方法。这个函数声明为虚函数,因为它将在TNewDate这个子类中被取代。一个属性的Get或者Set方法没有理由不使用延迟绑定(第八章大篇幅解释过的功能)。
注意: 在这个例子中,重要的是要知道属性并没有直接映射到数据。属性是对存储在不同类型的信息和不同类型的结构进行简单的计算得来。
在用属性更新了类的实现以后,我们现在是时候更新使用属性的例子了。例如,我们能够直接使用Text属性,并且我们能够提供三个编辑框让用户读取或者写入三个主要属性的值。这实际上是btnRead按钮按下时所要做的:
procedure TDateForm.BtnReadClick(Sender: TObject);
begin
EditYear.Text := TheDay.Year.ToString;
EditMonth.Text := TheDay.Month.ToString;
EditDay.Text := TheDay.Day.ToString;
end;
btnWrite按钮做相反的操作,你能够按照两种之一编写代码:
// Direct use of properties
TheDay.Year := EditYear.Text.ToInteger;
TheDay.Month := EditMonth.Text.ToInteger;
TheDay.Day := EditDay.Text.ToInteger;
// Update all values at once
TheDay.SetValue(EditMonth.Text.ToInteger,
EditDay.Text.ToInteger,
EditYear.Text.ToInteger);
这两种方式的不同取决于输入值无效时发生了什么。当我们单独设置每个值的时候,程序可能刚改变了年的值然后就发生了异常,后续的代码被跳过,导致日期仅仅是被部分修改。当我们一次设定所有的值时,要么这些值是正确的并都被设置,要么这些值是无效的,data对象依然是保留了原来的值。实际上,这就是为什么这三个属性应该设置成只读属性的原因,也就是说要保持数据的完整性。