1、考虑这样一个场景。
我们的程序中有一个“选项”窗口,这个窗口包含很多选项。其中有一个选项是单选类型的,用户可以从N个选项值中选择一个。
我们需要在用户单击“确定”按钮后把用户选择的值保存到文件中,程序下次启动时再读取到内存中。
2、不好的解决方案
通常情况下,我们会在按钮单击事件中写类似下面的代码:
procedure TfrmOption.btnOKClick(Sender: TObject); begin if rb1.Checked then // save value 1 else if rb2.Checked then // save value 2 else if rb3.Checked then // save value 3 end;
或者这样:
procedure TfrmOption.btnOK2Click(Sender: TObject); var value: Integer; begin if rb1.Checked then value := 1 else if rb2.Checked then value := 2 else if rb3.Checked then value := 3; // save value end;
这样的代码有什么问题呢?
首先,如果这样的选项一多,那 btnOKClick 中的代码将会非常长,影响了可读性。
其次,btnOKClick 的职责只是负责保存选项,而在上面的代码中还多出了判断选项值的职责。这在以后的维护中将会带来逻辑上的混乱。
3、利用属性封装的方案
从逻辑上来讲,“选项”这个功能可以分为三个部分。
一是选项值的保存与读取,这部分可以单独写一个类,具体怎么做超出了本文讲解范围,所以暂不做详细解释。
二是选项值的显示与修改,只保存和读取的选项值是没有意义的,它们必须展示给用户,让用户修改和查看才有意义。
第三部分也就是接下来我们要讲的部分。因为保存下来的值一般是经过摘要的,利于存储的。而展示给用户的是经过扩展的,修饰的。
这一转换过程已经体现在上面两段代码中:将单选按钮(或者其他控件)的状态转换为数字进行保存。或者反过来,根据数字的值设置不同单选按钮的选中状态。
前面说过了,btnOKClick 的职责是保存选项值,那剩下的职责给谁?给“选项”窗口。
我们可以这样想。“选项”窗口除了负责展示和接受用户对选项的修改,还担负了将用户的输入转换成方便存储的值,同时还担负了反向过程的职责。
而这个职责可以用属性来实现,利用属性将这个职责变为“选项”窗口的一部分,同时这也屏蔽了一些业务逻辑,有利于以后这个选项的扩展升级。
下面是具体的代码:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls;
type
TfrmOption = class(TForm)
btnOK: TButton;
rb1: TRadioButton;
rb2: TRadioButton;
rb3: TRadioButton;
btnOK2: TButton;
procedure btnOKClick(Sender: TObject);
private
function GetProp1: Integer;
procedure SetProp1(const Value: Integer);
{ Private declarations }
public
{ Public declarations }
procedure SaveOption(Name: string; Value: Integer);
property Prop1: Integer read GetProp1 write SetProp1;
end;
var
frmOption: TfrmOption;
implementation
{$R *.dfm}
procedure TfrmOption.btnOKClick(Sender: TObject);
begin
SaveOption('Prop1', Prop1);
end;
function TfrmOption.GetProp1: Integer;
begin
if rb1.Checked then
Result := 1
else if rb2.Checked then
Result := 2
else
Result := 3;
end;
procedure TfrmOption.SaveOption(Name: string; Value: Integer);
begin
end;
procedure TfrmOption.SetProp1(const Value: Integer);
begin
case Value of
1: rb1.Checked := True;
2: rb2.Checked := True;
else rb3.Checked := True;
end;
end;
end.
后记:为了方便讲解,没有把文中提到的那个选项设计为枚举。更进一步的,可以用枚举结合TRadioGroup控件增强可维护性和可读性。