[基础] AHK函数对象系列-对象属性与数据域保护v2

news/2024/7/1 5:51:07

AB009-[基础] AHK函数对象系列-对象属性与数据域保护

活见鬼:明明变量改了,为什么显示不出来?

在上文 [基础] AHK函数对象系列-绑定函数对象v3 中我们通过“绑定函数对象”,实现了在命令(诸如hotkey/GUI事件/SeTimer)中使用函数。

不过细心的小伙伴们可能会发现,在示例1:用HotKey给带有参数的函数注册热键中,msgbox的参数s更改之后,对话框中的文字并没有更改。

原因很简单,因为更改参数s,函数对象并没有改变。

权宜之计:手动重新生成对象并绑定

所以说,参数s更改之后,我们需要重新生成一个函数对象,并且绑定到HotKey。

思考:如何让数据和对象的更改联动?

比如示例-AB009-1中,新增了一个方法UpData(),放在快捷键^r事件中,这样用户每次更改完成都会执行一次,函数对象也就重新绑定好了。

不过,稍微想想就会发现,这显然是一个笨办法,之后每次想要修改数据的时候,都必须考虑到UpData(),一旦漏掉的话,那就完蛋了。极大的加重了编程的负担,降低了程序可读性。

那么问题来了,如何让数据和对象的更改联动?(不动脑筋就继续看,是小狗U·ェ·U哦)

;示例-AB009-1
DefaultHotkey:="^t"

;# 请问用户创建热键
InputBox,UserOption,热键设置,请为软件的功能设置一个您喜欢的热键,,,,,,,,%DefaultHotkey%
if (ErrorLevel=1){
    TrayTip,热键设置,您未输入热键,故程序将自动设置热键为默认值Ctrl+t
    UserOption:=DefaultHotkey
}
obj_0:=new Test(UserOption)
return ;# 自动执行结束

;# 用户手动修改数据
^r::
InputBox,UserData,数据设置,请重新设置统计数据,,,,,,,,%ClipBoard%
TrayTip,数据再次确认,% "您输入的数据是 " obj_0.TheNumber_2:=UserData
obj_0.UpData() ;权宜之计:每次数据更改之后,重新生成对象,并且绑定到HotKey
return

class Test{

;# 成员属性
todo_0:=""
TheNumber_2:="2"


UserOption:=""
;# 构造方法
__New(UserOption){
    todo_0:=ObjBindMethod(this,"ThePrint",this.TheNumber_2)
    this.UserOption:=UserOption
    Hotkey,%UserOption%,% todo_0
TrayTip,热键设置,已为您成功设置热键%UserOption% `r`nO(∩_∩)O  愿您使用愉快
    return this
}

;# 示例功能
ThePrint(Data){
    MsgBox,% "数据打印如下`r`n" Data
    return
}
;# todo更新
UpData(){
    todo_0:=ObjBindMethod(this,"ThePrint",this.TheNumber_2)
    Hotkey,% this.UserOption,% todo_0
    return
}
} 

面向对象的哲学:面向对象就是学习真实世界

get()和set()方法

比较容易想到的一个方案就是,数据的修改不直接进行,而是通过一个特定的方法进行。

比如示例-AB009-2中,我们使用方法setTheNumber_2来修改数据。

;示例-AB009-2
DefaultHotkey:="^t"

;# 请问用户创建热键
InputBox,UserOption,热键设置,请为软件的功能设置一个您喜欢的热键,,,,,,,,%DefaultHotkey%
if (ErrorLevel=1){
    TrayTip,热键设置,您未输入热键,故程序将自动设置热键为默认值Ctrl+t
    UserOption:=DefaultHotkey
}
obj_0:=new Test(UserOption)
return ;# 自动执行结束

;# 用户手动修改数据
^r::
InputBox,UserData,数据设置,请重新设置统计数据,,,,,,,,%ClipBoard%
TrayTip,数据再次确认,% "您输入的数据是 " obj_0.setTheNumber_2(UserData)
;~ TrayTip,数据再次确认,% "您输入的数据是 " obj_0.TheNumber_2:=UserData
;~ obj_0.UpData() ;权宜之计:每次数据更改之后,重新生成对象,并且绑定到HotKey
return

class Test{

;# 成员属性
todo_0:=""
TheNumber_2:="2"

setTheNumber_2(value){
this.TheNumber_2:=value
this.UpData()
return this.TheNumber_2
}

UserOption:=""
;# 构造方法
__New(UserOption){
    todo_0:=ObjBindMethod(this,"ThePrint",this.TheNumber_2)
    this.UserOption:=UserOption
    Hotkey,%UserOption%,% todo_0
TrayTip,热键设置,已为您成功设置热键%UserOption% `r`nO(∩_∩)O  愿您使用愉快
    return this
}

;# 示例功能
ThePrint(Data){
    MsgBox,% "数据打印如下`r`n" Data
    return
}
;# todo更新
UpData(){
    todo_0:=ObjBindMethod(this,"ThePrint",this.TheNumber_2)
    Hotkey,% this.UserOption,% todo_0
    return
}
} 

其实,这就是Java中的解决方法,不过Java中需要配合访问权限的控制,如果在Java中会这么写。

//示例-AB009-3:Java中的数据域保护
public class Test_01 {
    private int TheNumber_2=2;
    void setTheNumber_2(){
        TheNumber_2=2;
        UpData();
        return;
    }
    void  UpData(){
        return; //只是举例子,所以这是空的
    }
}
private是权限修饰符,意思就是,TheNumber_2这个数据只能在类中进行修改(私有)。

这么一搞之后,数据域就被保护了,只要修改数据就只能通过setTheNumber_2()来进行,不会出现例外,那么也就不会出现由于“UpData()没有写”,而导致的“数据出错”/“程序逻辑错误”。

其实本该如此

经常可以看见论坛上来拿“面向对象”调侃程序员的婚姻问题,因为在某些地方“对象”指的是“配偶”的意思,但你有没有想过,“对象”,究竟是什么意思?

其实对象的英语是“Object”,意译过来就是“物体”,“面向对象编程”其实可以叫做“现实物体模拟编程”。

因为我们的编程是为了解决现实中的问题,所以我们带着现实世界的法则来进行编程,必然会优美而简洁。

举个例子,在现实生活中,我们有一个东西叫做“抽屉”,现在我们在计算机中来模拟它,所以建立了一个"虚拟抽屉"类。

抽屉类做好之后,要把这个虚拟抽屉打开。如果直接修改抽屉的状态,就相当于抽屉没有动,但抽屉被打开了。这样就会产生逻辑上的错误,导致之后的操作也都出错;比如,当你发现抽屉已经被打开之后,往里面放一个钥匙,但却发现放不进去,因为虽然抽屉已经被打开了,但是抽屉的上方是桌子。

举个栗子

思考:你一定玩过游戏吧,现在你可以设想一下游戏中的面向对象设计,想象一个典型的游戏场景,然后想象直接修改某数据造成的逻辑错误是什么样的。

在现实生活中,“拉开抽屉”和“抽屉处于拉开的状态”,这两种现象是紧密联系的,所以根本就不会出现“抽屉没有动过”但是“抽屉被打开了”的情况。所以说直接修改“抽屉处于拉开的状态”是有问题的。

所以我们需要对数据进行保护,要让数据的修改完全可控,以保证数据符合程序设计逻辑。

数据域保护

对象属性:保护AHK中的数据域

前面提到,Java中解决这个问题采用的是get()和set()方法,但是采用这个方法,最好配合数据域访问权限控制,否则的话,一旦忘掉就形同虚设了。

实际上在Java中这两个东西是几乎天天要用的,以至于IDEA中可以用快捷键一键创立

AHK中没有权限控制,所以他采用了另一种方法,就是对于“属性”的内部进行自定义。

这里我们再举个简单例子

再举个栗子

;示例-AB009-4:对象属性简单例子

;实例化Player
P1:=new Player
;调节音量
P1.VolumeLevel:=120
;显示音量
MsgBox,% P1.VolumeLevel

;Player类
Class Player{
B_VolumeLevel:=0

;Volume属性 
    VolumeLevel[]{
get {
return this.B_VolumeLevel
}
set {
if (value>100)
return this.B_VolumeLevel:=100
else if (value<0)
return this.B_VolumeLevel:=0
else
return this.B_VolumeLevel:=value ;[1] 
}
    }
    

}

可以看到我们建立了一个播放器类,为了符合设计要求,用户是不能把音量调整到一百以上的,通过对于定义属性,我们保护中的数据,简洁优美地解决了这个问题。

[1] value是什么?value是调用set时传入的参数。P1.VolumeLevel:=120中的“120”就是参数“value”。
[2] return this.B_VolumeLevel:=value 这种用法是什么意思?return默认是接受表达式的。this.B_VolumeLevel:=value,这个表达式的意思是调用了“赋值(set)”方法,默认的set方法是返回“value本身”的。所以这个也可以写成。

this.B_VolumeLevel:=value
return value ;(或者“return this.B_VolumeLevel”)

这两个理解起来有困难,没关系,后期可能会继续讲“对象协议”,到时候这篇文章的论述会更加连贯,如有需要,请关注本简书专辑的更新。

小心递归

如果你仔细的看了这个例子,你可能会奇怪,为什么要拐弯抹角的用一个“B_VolumeLevel”?直接使用“VolumeLevel”不行吗?实际上是不行的。

还是回到“示例-AB009-3:Java中的数据域保护”,通过对比,你可以发现区别主要有两点。

1,在Java中成员方法调用成员变量是不需要this的,在Java中的逻辑是“不冲突就简化”,只要在类内进行引用,那么就默认是this.,但是AHK中不是这样的,引用必须使用this.,我不明白为什么AHK要设计成这样,如果您知道的话可以还请您不吝赐教。

2,由于Java中使用的是set方法,所以我们可以直接使用private数据本身,但是在AHK中数据本身就是“方法”,所以如果还是引用本身,那么就会出现递归(无限循环到死),出现这种情况AHK就会直接ExitAPP。所以必须要找一个变量来存储数据,通常我习惯在属性名前面加“B_”代表“基变量”,因为“B”是“Base”的首字母。当然,我也不知道有没有更简洁的解决方案,大概的翻了一下AHK的官方例子,也没有看到其他的方法,如果您知道的话可以还请您不吝赐教。

递归大法

不再闹鬼:让快键键功能跟随变量发生改变

估计到这里你已经搞懂了吧。哈哈。

你可以先试着自己改一改。

我这里放上一个例子,但是我把文字都倒过来了。

写完之后你,可以对比对比咱俩写的有什么不一样,谁的实现更简洁和优美。

;示例-AB009-5:不再闹鬼;
}
}
nruter    
0_odot %,noitpOresU.siht %,yektoH    
)2_rebmuNehT.siht,"tnirPehT",siht(dohteMdniBjbO=:0_odot    
{)(ataDpU
新更odot #;
}
nruter    
ataD "n`r`下如印打据数" %,xoBgsM    
{)ataD(tnirPehT
能功例示 #;

}
siht nruter    
快愉用使您愿  O)∩_∩(On`r` %noitpOresU%键热置设功成您为已,置设键热,piTyarT
0_odot %,%noitpOresU%,yektoH    
noitpOresU=:noitpOresU.siht    
)2_rebmuNehT.siht,"tnirPehT",siht(dohteMdniBjbO=:0_odot    
{)noitpOresU(weN__
法方造构 #;
""=:noitpOresU

 }

}
eulav nruter
nruter ~;
eulav是就值回返的tes来本来原 #;
)(ataDpU.siht
eulav=:B_2_rebmuNehT.siht
的入输 eulav=:B_2_rebmuNehT 户用是就也,数参个一后最的法方tes是eulav #;
{tes
}
B_2_rebmuNehT.siht nruter
{teg
{][2_rebmuNehT
"2"=:B_2_rebmuNehT
""=:0_odot
性属员成 #;

{tseT ssalc

nruter
ataDresU=:2_rebmuNehT.0_jbo " 是据数的入输您" %,认确次再据数,piTyarT
%draoBpilC%,,,,,,,,据数计统置设新重请,置设据数,ataDresU,xoBtupnI
::r^
据数改修动手户用 #;

束结行执动自 #; nruter
)noitpOresU(tseT wen=:0_jbo
}
yektoHtluafeD=:noitpOresU    
t+lrtC值认默为键热置设动自将序程故,键热入输未您,置设键热,piTyarT    
{)1=leveLrorrE( fi
%yektoHtluafeD%,,,,,,,,键热的欢喜您个一置设能功的件软为请,置设键热,noitpOresU,xoBtupnI
键热建创户用问请 #;

"t^"=:yektoHtluafeD

;鬼闹再不:5-900BA-例示;

全文技术总结

  1. 在面向对象的编程中,尽量让“数据域”私有,以达到“数据域保护”的目的。
  2. 编程为现实所需,从现实中借鉴经验。“面向对象”本质上可以理解为“向现实世界学习”。[1]
  3. AHK中可以通过自定义属性的方法实现数据域的保护。[2]

[1] 这句话是一种比喻,而并非要求程序和现实世界是一一对应的,如果要细讲的话,那就是“面向对象的设计” 的专业内容,并非本文重点。
[2] @天马行空 和我讨论起了“伪属性”的问题,这个问题涉及到base,我也没有学习和使用过,最后如果出“继承和多态”可能会顺便提一提。大致看了一眼感觉是一个比较简单的实现“属性”效果的方法,主要就是利用“函数对象”直接修改base。
[3] 有群友问,AHK中的“属性”能够实现重载和传递参数的特性吗?首先,重载在AHK中是没有的,如果非要用,可以模拟一下,具体可以参考我这篇文章。其次,“属性”但本质也是一种对象的使用方法,是可以传递参数的,这一部分等到后期的“对象协议”再说。

面向对象编程的特性

End

我是Java/AHK持续学习者,欢迎您来和我探讨Java/AHK问题 ^_^

更多文章:

[专栏] AHK程序设计 - 简书(优先持续更新)

[基础] AHK函数对象系列-绑定函数对象v3

[基础] AHK函数对象系列-绑定方法对象

[基础] 在AHK中实现函数重载的效果

[基础] [GIF动图] 绕过中文输入法发送文本的3种方法

问题解答:

[问题解答] 示例不能运行吗? - 关于AHK程序设计系列文章示例问题的解释

版权声明:

该文章版权系“心如止水”所有,欢迎分享、转发,但如需转载,请联系QQ:2531574300,得到许可并标明出处和原链接后方可转载。未经授权,禁止转载。

文章版本:

v1
v2 增加了五条注释,增加了部分图片。修复了部分用词错误。

AHK版本:1.1.30.00

心如止水


http://www.niftyadmin.cn/n/3528585.html

相关文章

python怎么搭建接口测试框架_接口测试自动化基本框架搭建

接口测试基本框架搭建(common部分)项目基本结构1.common&#xff1a;封装的各种处理的方法2.conf&#xff1a;配置文件3.data&#xff1a;用例数据4.logs&#xff1a;日志文件5.reports&#xff1a;测试报告6.testcases&#xff1a;封装测试用例类7.runner.py&#xff1a;启动文…

mysql数据库还原myl_MySQL帐户管理

MySQL用户帐户管理这一部分我们将会讨论如何为我们的MySQL服务器客户端设置帐户。我们将会讨论下面的一些问题&#xff1a;1在MySQL中使用的帐户的用户以及密码的含义并与我们在操作系统中使用的用户以及密码进行比较2如何设置新的帐户以及删除已经存在的帐户3如何更改密码4安全…

mysql innodbdatahomedir_mysql homedir迁移

随着数据库的增长&#xff0c;innodb文件和日志文件会越来越大&#xff0c;如果是默认安装的mysql&#xff0c;这些文件一般是放在 /usr/lib/mysql下面进行转移&#xff1a;1&#xff0c;安全关闭mysqlmysqladmin -u root -p shutdown2&#xff0c;复制mysql文件夹&#xff0c;…

python分类器实现_Python实现基于SVM的分类器的方法

本文代码来之《数据分析与挖掘实战》&#xff0c;在此基础上补充完善了一下~代码是基于SVM的分类器Python实现&#xff0c;原文章节题目和code关系不大&#xff0c;或者说给出已处理好数据的方法缺失、源是图像数据更是不见踪影&#xff0c;一句话就是练习分类器(?㉨?メ)源代…

最全的mysql 5.7.13_MySQL_最全的mysql 5.7.13 安装配置方法图文教程(linux) 强烈推荐!,linux环境Mysql 5.7.13安装教程分 - phpStudy...

最全的mysql 5.7.13 安装配置方法图文教程(linux) 强烈推荐!linux环境Mysql 5.7.13安装教程分享给大家&#xff0c;供大家参考&#xff0c;具体内容如下1系统约定安装文件下载目录&#xff1a;/data/softwareMysql目录安装位置&#xff1a;/usr/local/mysql数据库保存位置&…

java滑块_Java Swing JSlider滑块的实现示例

1. 概述JSlider&#xff0c;滑块。以图形方式在有界区间内通过移动滑块来选择值的组件。滑块可以显示主刻度标记以及主刻度之间的次刻度标记。刻度标记之间的值的个数由 setMajorTickSpacing(int) 和 setMinorTickSpacing(int) 来控制。刻度标记的绘制由 setPaintTicks(boolean…

java 本地事务_java事务(二)——本地事务

本地事务事务类型事务可以分为本地事务和分布式事务两种类型。这两种事务类型是根据访问并更新的数据资源的多少来进行区分的。本地事务是在单个数据源上进行数据的访问和更新&#xff0c;而分布式事务是跨越多个数据源来进行数据的访问和更新。在这里要说的事务是基于数据库这…

fitnesse java_从Fitnesse中学习Java单元测试

从第一次知道Fitnesse这个集成测试工具到现在也已经差不多有2年多的时间了。在这个期间把Fitnesse的源码也算是反反复复阅读了很多遍&#xff0c;算是对其实现的原理和方法有所了解。在最近一次对Fitnesse最新版本代码的研究中我发现&#xff0c;Fitnesse除了是一个很好的开源集…