[关闭]
@hainingwyx 2017-07-27T16:20:54.000000Z 字数 34556 阅读 15078

MATLAB面向对象编程模式

matlab 面向对象


第1章面向过程和面向对象程序设计

1.1什么是面向过程的编程

定义:一种以过程为核心的编程算法,把问题的过程按照步骤分解出来,然后用函数形式加以实现。
面向过程编程方法的优点是简单快捷,缺点是面对复杂的程序难以修改和维护。

1.2什么是面向对象的编程

面向对象编程(Object Oriented Programming,OOP)把任务分解成一个个相互独立的对象,通过各对象的组合和通信来模拟实际问题。

1.2.1什么是对象(Object)

真实世界中具体的东西。
特点:具有各种属性;具有相关的行为。

1.2.2什么是类(Class)

对各个具体、相似对象的共性的抽象。

1.2.3什么是统一建模语言(UML)

Unified Modeling Language:对程序的一种图形表达方式
Matlab要求每个类的定义保存为一个同名的文件。
在matlab中建立实体对象的方式是:调用类的构造函数(Constructor)。构造函数和类同名;构造函数的返回值是构造出来的新的对象。

1.3面向过程编程有哪些局限性

不容易维护和扩张。大多数情况下,如果已有了面向过程的程序,可以用面向对象的思想去包装这些已有的程序,并且在此基础上继续维护和扩张已有的程序。

1.4面向对象编程有哪些优点

把大问题分解成小的对象,通过组合和信息传递完成任务,通过继承达到代码的复用,修改或添加模块不会影响到其他模块

第2章MATLAB面向对象程序入门

2.1如何定义一个类

  1. classdef Point2D<handle
  2. properties
  3. end
  4. methods
  5. end
  6. end

类定义中包含一个属性block和一个方法block。
constructor构造方法:负责产生并且返回类的对象,通常还可以用来初始化对象的属性。

2.2如何创建一个对象

创建对象的方式是直接调用类的constructor

2.3 类的属性(Property)

2.3.1 如何访问对象的属性

面向对象编程中,使用Dot运算符来访问对象的属性。

2.3.2 什么是属性的默认值(DefaultValue)

在MATLAB类的property Block定义中,可以为属性直接赋一个值,此时为默认值。
如果默认值的赋值使用表达式,该表达式仅在类定义被MATLAB装载时执行一次,所以表达式计算的结果最好是固定。
除了在property block中给属性赋默认值,还可以在constructor对属性变量做初始化。

2.3.3 什么是常量(Constant)属性

定义:在对象生存周期中值保持不变的属性,如果对该属性进行修改都将报错。
定义constant property需要用constant关键词。如

  1. classdef A<handle
  2. properties(Constant)
  3. R=pi/180
  4. end
  5. end

如果不显示给定常量属性一个特定的值,则默认为empty double。另外constant property不用创建对象就可以直接使用类中的常量。

  1. A. R %A是类名而不是对象名

2.3.4 什么是非独立(Dependent)属性

定义:其值依赖于其他的属性,一旦其他的属性改变,该属性也做响应的变化,在概念上可以理解为数学中的因变量。为了不需要每次都在自变量变化之后更新因变量,可以把该值设为dependent(依赖)属性。
dependent属性特点:对象内部没有给属性分配物理的存储空间,每次该属性被访问时,其值将被动态地计算出来。计算该属性的方法由get提供。

  1. classdef Point2D<handle
  2. properties
  3. x
  4. y
  5. end
  6. properties(Dependent)
  7. r
  8. end
  9. methods
  10. function obj=Point2D(x0,y0)
  11. obj.x=x0;
  12. obj.y=y0;
  13. end
  14. function r=get.r(obj)%dependent属性要放在get方法中
  15. r=sqrt(obj.x^2+obj.y^2);
  16. disp('get.r called');
  17. end
  18. end
  19. end

验证程序:

  1. p1=Point2D(1.0,2.0);
  2. p1.r
  3. p1.y=1;
  4. p1.r

设置为dependent还有一个好处:支持dot和向量化操作,如果r是一个矩阵或者矢量,在类的外部,可以直接进行适量操作(obj.r(1:2));如果r是一个结构体,可以直接使用dot继续访问r内部的其他fields(obj.r.otherfields)
另一个例子:

  1. classdef View<handle
  2. properties
  3. hFig
  4. hEdit
  5. end
  6. properties(Dependent)
  7. text
  8. end
  9. methods
  10. function obj=View()
  11. obj.hFig=figure();
  12. obj.hEdit=uicontrol('style','edit','parent',obj.hFig);
  13. end
  14. function str=get.text(obj)
  15. str=get(obj.hEdit,'String');
  16. end
  17. end
  18. end

什么是matlab的解释器?
回答:MATLAB解释器将MATLAB命令行、脚本、函数中的MATLAB的代码翻译成内部指令,并且执行。MATLAB作为一种解释型语言,从写代码到执行代码的转换是立即完成的,并且源代码总是存在,一旦出现错误,MATLAB解释器就能很容易找出错误的位置。

2.3.5 什么是隐藏(Hidden)属性

隐藏的效果是在命令行中查看对象的信息时,该属性不会被显示出来。

  1. classdef A<handle
  2. properties(Hidden)
  3. var
  4. end
  5. methods(Hidden)
  6. function internalFunc(obj)
  7. disp(‘I am a hidden function’);
  8. end
  9. end
  10. end

用户如果知道属性的名字仍然可以正常地访问该属性。
Hidden关键词用处是隐藏类的内部的细节。

2.4 类的方法(Method)

2.4.1 如何定义类的方法

  1. methods
  2. function [returnValue]=functionName(arguments)
  3. …….
  4. end
  5. end

如果成员方法只有几行可以放在类定义中;否则可以在类中仅给出方法的声明,把实现放到一个独立的文件中去。

2.4.2 如何调用类的方法

  1. 使用OOP的点(Dot)语法调用成员方法obj.normalize()
  2. 使用传统的函数语法调用成员normalize(obj)

2.4.3 点调用和函数式调用类方法的区别

  1. p1.normalize符合面向对象风格,程序的可读性高,一目了然。
  2. 使用Dot语法清楚地告诉MATLAB要调用的是成员方法还是函数。确定用户到底是在调用函数还是类方法,是dispatcher的工作,唯有在执行时,dispatcher才会参与工作。
  3. 使用Dot语法,MATLAB的语法检查器会及时帮助用户检查语法错误。

2.4.4 什么是方法的签名

签名:对象连同方法的名称构成了该方法在matlab中独一无二的签名。
函数签名=函数名+所属类
每次调用对象的方法时,MATLAB的dispatcher都会动态地判断该方法的签名。虽然从表面看两个属于不同类的对象可能调用了方法名称相同的方法,但是MATLAB还是可以通过判断该函数所属类来找到匹配对象的。
脚本中的clear classes命令是必须的吗?
回答:是的,作为一个良好的编程习惯,在每个程序开头使用clear清除残存的变量和旧的定义是必要的。特别是在类的定义被秀海之后。我们要清楚内存中旧的定义,这样才能使新的修改生效。

2.4.5 类、对象、属性、方法之间的关系

类是一种抽象的定义,包括property和method,占用内存。对象是一个物理实体,根据类模板创作出来的,也有物理内存,构成对象本身的只有数据。方法是类具有的一种操作,被所有类共享。

2.4.6 如何用disp方法定制对象的显示

在MATLAB面向对象编程中,可以通过定义类的disp方法(也叫覆盖override)来定制对象在命令行上的输出内容,包含定制disp方法的类的对象,在命令行被用户查询时,会优先调用类的disp方法。

  1. function disp(obj)
  2. s=sprintf('%-17s(%s:%s)\n',obj.name,obj.exchange,obj.symbol);
  3. s=[s,sprintf('--------------------------------------------\n')];
  4. s=[s,sprintf('Last Trade: %6.2f',obj.last_price)];
  5. s=[s,sprintf(' (%s %s)\n',obj.last_time,obj.last_date)];
  6. disp(s);
  7. end

2.5 类的构造函数(Constructor)

2.5.1 什么是Constructor

定义:是一种特殊的成员方法。和类的名称相同,用来创造类的实例。类定义中只能有一个constructor。constructor只能有一个返回值,且必须是新创建的对象。

2.5.2 如何在Constructor中给property赋值

MATLAB中声明一个对象时,工作顺序是:先装载类的定义,然后调用constructor。

2.5.3 如何让Constructor接受不同数目的参数

MATLAB是弱解释性语言,不能通过参数的数目的不同来决定调用哪个参数,类似的功能只能放到函数体中,通过判断参数的个数(nargin)来实现,根据nargin的不同,选择不同的代码。

  1. function obj=Point2D(x0,y0)
  2. if nargin==0 %没有提供参数,default constructor
  3. %.....
  4. elseif nargin==2
  5. %......
  6. else
  7. %......

2.5.4 什么是Default Constructor

定义:不带任何参数的构造函数

2.5.5 用户一定要定义Constructor吗

如果用户没有提供任何形式的constructor,MATLAB会在内部提供一个default Constructor。看似一个空函数,但后台还是有工作的,如给对象、属性分配空间等。该自动提供的constructor不解释任何参数,如果尝试提供任何参数,将会报错。

2.6 类的继承

2.6.1 什么是继承

继承关系也叫做泛化关系,被继承的类叫做父类或者基类,继承的类叫做子类或者派生类。在MATLAB中可以用isa查询一个对象是否属于一个特定的类。如:isa (p2,’Point2D’);
UML中继承用空心三角箭头表示

2.6.2 为什么子类Constructor需要先调用父类Constructor

子类先继承了父类的属性和方法,然后在父类的基础上增加自己的属性和方法。代码中的@表示调用父类的constructor,返回一个对象。

  1. obj=obj@Super();

2.6.3 在子类方法中如何调用父类同名方法

句柄:句柄是一种抽象的思想:隐藏了内核实现的细节,同时为调用提供了方便,保证了内核的安全。
参考文章:http://blog.chinaunix.net/uid-26285146-id-3262293.html
子类和父类成员方法可以有相同的名字,并且在子类方法内部可以调用父类的同名方法,在子类中,除同名方法外的其他地方不能调用父类的同名方法。
调用语法:superMethod@superClass(this,otherArguments)
superMethod:父类同名函数
superClass:父类类名
this:子类对象的Handle传递给父类,obj
otherArguments:向父类同名函数传递其他参数

2.6.4 什么是多态

概念:建立在继承的基础上。同名的方法被不同的对象调用,能产生不同的行为(形态)。
例子:

  1. obj1=Print2D();
  2. obj1.print();
  3. obj2=Print2D();
  4. obj2.print();

2.7 类之间的基本关系:继承、组合和聚集

2.7.1 如何判断B能否继承A

使用继承可以提高程序的复用性,但是盲目继承会造成逻辑混乱和程序适用性降低。所如果A和B不相关,不能为了B的功能更多些继承A的功能和属性。

2.7.2 企鹅和鸟之间是不是继承关系

共有继承可以用is a“是一个”等价

2.7.3 如何把类组合起来

如果在逻辑上A是B的一部分,则不要为了让A得到B的功能去继承B,而是要用A和其他东西组合出B。如眼鼻口耳、头的组合关系。组合通过Head对象的constructor来保证。组合关系要求Head对象一定在内部拥有Nose、Eye、Mouth、Ear。
UML图上,实心菱形箭头表示组合关系。

2.7.4 什么是组合聚集关系

组合:整体和部分,
聚集:松散的整体和部分,自行车由架子、轮子、坐垫组成。反映在程序上就是,Wheel和Seat对象可以独立创建。
UML图上,空心菱形箭头表示聚集。

2.8 Handle类的set和get方法

2.8.1 什么是set方法

set方法给对象属性的赋值提供了一个中间层,可以检查赋值是否符合要求或者检查赋值的类型。还可以在set方法中做一些其他工作,比如做一个LOG记录每次属性被赋的值。

  1. classdef A<handle
  2. properties
  3. a
  4. end
  5. methods
  6. function set.a(obj,val)
  7. if val>=0; %检查赋值要求
  8. obj.a=val;
  9. else
  10. error('a must be positive');
  11. end
  12. end
  13. end
  14. end

main:

  1. obj=A();
  2. obj.a=-10 %试图给a赋负数

load一个对象时,属性set方法会被MATLAB调用,如果这时对象的某些属性值仍是默认值,这些属性会经过set方法,被验证有效性。可以认为是数据有效性的最后一层屏障。

2.8.2 什么是get方法

get方法提供了对成员属性查询操作的一个中间层。可以使程序变得向后兼容。比如一个大型程序的升级,需要把date的名字改成timeStamp。

  1. classdef Record <handle
  2. properties(Dependent,Hidden)
  3. date%设置成dependent不占用内存,Hidden属性使得不会被命令行显示出这个旧的属性
  4. end
  5. properties
  6. timestamp
  7. end
  8. methods
  9. function set.date(obj,val)
  10. obj.timeStamp=val;
  11. end
  12. function val=get.date(obj)
  13. val=obj.timeStamp
  14. end
  15. end
  16. end

set和get方法还是有时间成本的。

2.9 如何设置属性和方法的访问权限

2.9.1 什么是public,protected,private权限

  1. classdef SomeClass<handle
  2. properties(Access=private)%私有属性
  3. prop_private
  4. end
  5. properties(Access=protected)%保护属性
  6. prop_protected
  7. end
  8. properties(Access=public)%默认是共有的
  9. prop_public
  10. end
  11. methods(Access=privated)%同上
  12. function result=someFunction(obj)
  13. %...
  14. end
  15. end
  16. end

UML图中,+表示public,#表示protected,-表示private。
属性的访问权限可以被细分为赋值SetAccess和查询GetAccess的权限。如:

  1. properties(SetAccess=private, GetAccess=public)
  2. var
  3. end

表明该属性可以被外界程序查询值,但不能被外部程序赋值,赋值只能在类的内部进行。

2.9.2 如何决定对类的属性和方法设置何种访问权限

  1. classdef BankAccount<handle
  2. properties(SetAccess=private)
  3. balance
  4. accountNumber
  5. end
  6. methods
  7. function obj=BankAccount(balance,num)
  8. obj.balance=balance
  9. obj.accountNumber=num
  10. end
  11. function deposit(obj,val)
  12. obj.balance=obj.balance+val
  13. end
  14. function withdraw(obj,val)
  15. obj.balance=obj.valance-val
  16. end
  17. end
  18. end

2.9.3 MATLAB对属性访问的控制与C++和Java有什么不同

public属性,在C++和Java中,如果把一个成员属性定义成public,意味着该属性可以被外部直接访问和赋值,这相当于该属性直接暴露给外部。matlab在外部直接访问和public属性之间可能还存在一个set和get的中间层,即使是public属性,Matlab面向对象语言提供了检查措施。
private属性,在C++和Java中,如果把一个成员定义成private,意味着不可以被外部直接访问和赋值,但是习惯上会提供一个public的set和get方法。两者有差别,前者只是普通方法。C++和Java是现实调用set方法,matlab是隐式调用set方法。

2.10 ClearClasses到底清除了什么

matlab 2014b引入了新的自动更新类定义的功能。
类被首次使用,Matlab会将类定义一次性装载进内存中之后再声明类的对象时,不需要重新读入类定义。面向对象编程时,如果定义了一个类,声明了一个对象,然后改变了该类的定义,再尝试声明一个对象时,会出现Warning。只要有旧的instance存在,新的类定义就无法生效。通常可以
1:clear obj
如果工作空间中还有其他重要变量存在,用户不希望全部清除他们,可以有选择地清除对象。如:工作空间中有A类的对象obj1和obj2,我们修改A类的定义,只要执行,就可以在下次声明A的对象时,使用新的定义。
clear obj1 obj2
2:clear classes
清除工作空间所有变量、所有之前被装载的类定义、类中的constant属性

第3章 MATLAB的句柄类和实体值类

3.1 引子:参数是如何传递到函数空间中去的

如果参数的值在函数内部没有改变,函数的工作空间知识复制了参数的必要数据,而实际数据仍然在函数工作空间之外。这些必要数据提供了访问实际数据的一种渠道(指针)。

  1. function result=myAdd(a,b)
  2. result=a+b;
  3. end

如果函数体内修改了参数的值,在参数的值改变的前一刻,Matlab会在函数工作空间中复制一个局部拷贝,以保证所有对a矩阵的修改都是局部的,即函数体内对a的修改不会影响到Main工作空间中的a矩阵。这种技术也叫Lazy Copy。不到万不得已不构造局部拷贝。

  1. function result=myAdd(a,b)
  2. a=round(a);
  3. result=a+b;
  4. end

3.2 MATLAB的ValueClass和HandleClass

3.2.1 什么是ValueClass和HandleClass

两者的区别是:用户定义的类是否继承了Matlab内部提供的一个Handle基类。
ValueClass:实例化后是一个类的对象,它复制时是拷贝,得到不同的独立对象;
HandleClass:实例化后是类的句柄,复制时是引用,都指向同一个类。
clear all;clc;
mValue=ImageValue(10000);
mHandle=ImageHandle(10000);
Matlab依次在内存中对mValue和mHandle各分配了一个对象,每个对象大小都为763M。用Whos检查对象大小,mValue为763M,mHandle为112字节。这是因为Matlab处理实体类对象的方式是:直接在内存中开辟一块区域,用以存放实体类对象;Matlab处理句柄类对象的方式稍微多了一层,在内存中不但有一部分区域用来存放实际有用的数据,还有一个句柄对象,指向这块内存。句柄对象为112字节。

3.2.2 Value类对象和Handle类对象拷贝有什么区别

nValue=mValue;
nHandle=mHandle;
Value类对象在内存中进行了完全拷贝,也叫深拷贝;
Handle类对象只拷贝Handle类对象本身,而没有拷贝句柄对象指向的实际数据。这是一个不完全的拷贝,也称浅拷贝。因为他没有深入到handle所指向的数据对实际数据进行拷贝。特征:如果通过nHandle修改matrix属性,也会影响mHandle的matrix属性,因为这两个Handle对象共享了一份matrix数据。

3.2.3 Value类对象和Handle类对象赋值有什么区别

不到万不得已matlab不会在内存中构造一个一模一样的拷贝,这个万不得已就是当nValue中的matrix的值做出改变时,当我们哪怕仅仅修改nValue矩阵中的一个元素的值,Matlab在内存中马上就构造出一个实体的拷贝,然后再修改其中元素的值。

  1. clear all;
  2. mValue=MatrixValue(10000);
  3. nValue=mValue;
  4. nValue.matrix(1,1)=10%仅改变一个值

3.2.4 Value类对象和Handle类对象当做函数参数有什么区别

  1. function assignVar(obj,var)
  2. obj.var=var;
  3. end

aValue.assignVar(20):没有起到作用。
对于Value类对象,参数传递方式是:拷贝必要的信息到函数的工作空间中,当MATLAB发现该函数对传入的参数进行了修改,并且该参数是Value类对象时,MATLAB就构建出了一个局部拷贝,所以对obj.var的修改只是局部的,函数退出时,该拷贝也就消失了。所以如果想把函数内部的修改保存下来。必须把obj当做输出参数返回。assignVar必须改为:

  1. function obj=assignVar(obj,var)
  2. obj.var=var;
  3. end

对于Handle类对象,参数的传递方式也是构造一个局部拷贝对象,但在函数内部,函数修改的不是针对Handle对象的而是Handle对象所指向的数据。所以成功修改了Handle对象所指向的数据。

  1. function assignVar(obj,var)
  2. obj.var=var;
  3. end
  4. bHandle.assignVar(20);

3.2.5 什么情况下使用Value类或Handle类

Value类适用于比较简单的数据,如果使用者不在意副本的存在,并且希望每次执行对象的拷贝都可以得到一个独立的副本。如果一个数据在其他地方有副本,并且我们希望修改其中一个副本,其他所有的副本都被修改,可以使用Handle类。
从实用计算的角度来说,如果我们的数据体积比较大,我们希望这些数据在各个方法、函数之间传递迅捷,不需要被局部拷贝,则可以用Handle类来包装数据。
从物理角度看,如果类的对象对应一个独一无二的物理对象,设计成Handle类拷贝的是一个访问他们的通道,设计成Value类则无法解释一个独一无二的个体,在程序上有两个全同的拷贝。
从功能的角度看,Value类没有提供任何内置的方法,Handle类则提供了delete方法等,功能也更加强大。
例如5¥*2=10¥
两张5元换成10元,是一个新的对象,所以设计成Value类。

3.3 类的析构函数(Destructor)

3.3.1 什么是对象的生存周期

定义:从对象产生到释放的过程

3.3.2 什么是析构函数(Destructor)

定义:在对象脱离作用域或者被销毁时,负责收尾工作,比如关闭文件句柄、释放数据所占内存空间等。MATLAB规定:这类方法要命名为delete。无论Value或者Handle都需要用户定义自己的delete函数。

3.3.3 对Object使用clear会发生什么

  1. Value类:直接把这个对象从工作空间中删除。
    2.Handle类:清除Handle对象本身,Handle对象指向的实际数据是否被清除取决于该数据是否还被其他Handle所指向。

3.3.4 对Object使用delete会发生什么

对Handle类对象使用delete将释放该Handle所指向的数据。但是Handle对象还是存在的,可以指向其他的内存数据。

3.3.5 什么情况下delete方法会被自动调用

  1. 对Handle类对象重新赋值,例如:mHandle被重新赋值之前所指向的内存数据引用计数是1,重新赋值之后,引用计数为0,delete方法将被调用。
  2. 工作空间使用clear命令,所有工作空间的Handle对象的delete方法都会被调用。
  3. 对象离开了作用域
  4. A和B都是Handle类,B对象隶属于A对象,A对象的销毁将导致B对象的delete函数被调用。

是不是所有局部对象离开作用域之后都会被销毁?
回答:所有用户定义的类的对象和大多数MATLAB内置的类的定义出来的对象,都具有这样的行为,这类对象通常叫做Scoped Object。但有少数MATLAB内置类定义的对象叫做User-Managed Object,离开作用域后,不会自动被销毁。User-Managed,即需要用户指示MATLAB去销毁。例如:timer、analoginput、videoinput等。

3.3.6 出现异常时delete函数如何被调用

面向对象程序异常时,直到MATLAB捕获异常之前,所有try catch所处的函数的堆栈之上的,已经声明的对象,他们的析构函数都会自动调用。

3.3.7何时用户需要自己定义一个delete方法

当类的对象占用可一些系统资源,而无法自动释放时,需要自己重载一个delete方法释放这些系统资源。比如文件的打开一般需要删除。对于普通属性,MATLAB在销毁对象时会自动释放这些属性所占用的内存。
1. Value类没有析构函数
Value类delete方法只是用户自己定义的,不是matlab承认的合法的析构函数,所以MATLAB不会在value类对象离开作用域是自动调用,所以需要用户显式调用。valObj.delete();
2. MATLAB自动调用handle类对象的析构函数
如果用户有额外需要,必须自己在子类中重载delete方法。如果有可能,还是应主动调用delete方法;否则MATLAB会在Handle对象离开其作用域时才会调用clear函数,从而出发delete方法。
3. Handle类的合法析构函数
+ 方法的名字叫做delete
+ 没有返回值
+ 只接受一个参数,且参数必须是obj,即对象本身
+ 方法不允许是Sealed 或者Static,或者Abstract的,但是delete方法可以是private
如果delete方法没有满足上述中的任意一点,仍可显式调用该函数,但是不能自动调用。

第4章 事件和响应

4.1 事件(Event)

4.1.1 什么是事件

定义:泛指对象内部状态的改变。
在事件发生和出发响应的模型中,通常把改变内部状态的对象叫做发布者,而把监听事件并作出响应的对象叫做观察者。

4.1.2 如何定义事件和监听事件

MATLAB规定:事件的定义要放在event block中。

  1. events
  2. dataChanged
  3. end
  4. obj.notify(‘dataChanged’);

notify方法:监视其数据变化的对象发布的消息。
addlistener:用来在发布者处登记观察者。
方法addlistener用来构造监听者,登记的相应函数可以使普通函数,也可以是类的成员方法,还可以是类的静态方法。
lh=addlistener(eventObj,’EventName’,@functionName);
lh=addlistener(eventObj,’EventName’,@Obj.methodName);
lh=addlistener(eventObj,’EventName’,@ClassName.MethodName);
第一个参数是发布者对象(Src);第二个参数是事件的数据(eventdata)
matlab使用addlistener方法在发布者和观察者之间建立一个中间层,发布者只接受addlistener方法构造出来的listener对象在其处注册,真正的观察者只需要把自己的响应函数提供给addlistener方法,addlistener方法将把这个响应函数的句柄包装在其构造的对象的内部。这样就实现了用户定义的Handle类与具体的各种观察者之间的解耦合。

4.1.3 为什么需要事件机制

4.2 发布者通知观察者对象,但不传递消息

4.3 发布者通知观察者,并且传递消息

因为event.EventData类是Handle类,所以任何用户定义的事件类本身也是handle类,因此event还可以用来传递大型数据。假设传递一个500*500的矩阵作为消息,并且有十个观察者在发布者出注册,notify所有观察者的成本仅是构造一个message对象,传递10次handle。因为500*500只存在一个拷贝,且被10个观察者共享。更节省空间的办法是:提供一个内部数据的公共接口给观察者。

4.4 删除listener

因为listener对象本身就是handle对象,所以删除一个listener对象时,只需要调用handle基类提供的delete方法。

第5章 MATLAB类文件的组织结构

5.1 如何使用其他文件夹中的类的定义

在其他路径上使用类的定义,要用addpath命令,把包含在该类的文件夹加到MATLAB搜索路径中去。

  1. addpath(‘Z:’\folder1\folder2’);
  2. p1=Point(1,1);

5.2 如何把类的定义和成员方法的定义分开

另一种定义类的方法:在类定义文件中仅仅提供方法的申明,不提供方法的定义,方法的定义放到另一个独立的.m文件中。适合成员方法比较多的情况,一个项目多人开发时,有利于团队代码的版本管理。
MATLAB规定,如果把方法的定义normalize和display放在单独的文件夹中,那么类定义Point.m、normalize.m、display.m必须放在一个以@开头的文件夹中,且该文件夹必须命名为@Point。
哪些方法一定要放在类定义中?
1. 类的constructor和delete方法的定义
2. 任何属性的get和set方法的定义
3. 类的static方法的定义

放在@文件夹中的任何方法都被默认为类的成员方法。如果。于疏忽没有某方法的申明,那么该方法的属性将使用方法的默认值。(非隐藏、public)
如何使用@Point中类的定义?
1. main程序需要使用PointClass的类定义,只需要放在和该文件夹同一目录下即可。
2. addpath把@Point文件夹添加到matlab的搜索路径,任何路径都可以使用了。

5.3 如何定义类的局部函数

局部函数不是类的方法,在类定义外部不可见,不能通过obj.method的方式从外部访问。局部函数仅对类定义内部的方法可见。
局部函数对函数参数要求没有限制,不像类的实例方法那样,参数中一定要包含一个对象。所有在同一文件中定义的类方法都能调用局部函数的方法,但那些没有声明的类方法除外。
在classdef外部定义的类的方法也可以拥有自己的局部函数,调用规则和类的局部函数相似。

5.4 如何使用Package文件夹管理类

5.4.1 Package中的类是如何组织的

如果程序结构再复杂一点,可以把各个类进一步组成package。MATLAB规定package文件目录必须以+开头,package中还可以包括各个类的文件夹,各类之间还可以有继承关系。

5.4.2 如何使用Package中的某个类

如果一个类的定义放在package中,使用该类时要在类名前加上Package的名称。

  1. p2=MyPointPackage.Point3D(1,1,1);

5.4.3 如何导入Package中的所有类

如果在程序的开头就用import命令导入整个Package。这样调用package的时候就不需要使用package的名称了。

  1. import MyPointPackage.*;

5.5 函数和类方法重名到底调用谁

两个function同名,但是定义不同,一个是普通函数,一个是类AClass的成员方法,这种情况下,调用哪个function将取决于用户程序的调用方法。

5.6 Package中的函数和当前路径上的同名函数谁有优先级

MATLAB的package中可以放置普通的函数,如果两个普通的函数,有相同的函数名,一个在当前路径下,一个在package中,且它们的签名是一样的,在main程序中,该函数被调用时,则MATLAB将调用路径上优先级最高的函数。
默认情况下,最直接路径中的函数具有最高优先级。如果用户想要调用的是package中的foo函数,可以先导入整个package。

第6章MATLAB对象的保存和载入

6.1 save和load命令

6.1.1 如何save和loadobject

save filename obj:把对象obj的数据保存到名为filename的mat文件夹中
load matfilename obj:把mat文件中的对象obj装载到工作空间中

6.1.2 MAT文件中保存了object中的哪些内容

1、obj所属的类的名称和package的名称
2、obj所属的类的属性的默认值
3、obj中普通属性的值
save时,会把save对象的属性的值和默认值相比较,如果一样,则只保存属性的默认值。一方面节省mat文件的空间,一方面又保持了兼容性。因为如果save时类的某属性值的默认值在load时发生了变化,为了保证装载对象能恢复到其初始的状态,而不是新的状态,load会沿用旧的默认值。
mat文件没有保存:
1. 对象的transient、constant、dependent类型的属性
2. 类的完整定义

保存handle类,如果已经使用delete方法,该对象指向的数据将被释放,但是handle对象仍然存在,但是是一个无效的handle对象。
mat文件不能保存类的定义,所以load对象时必须能找到该对象的类的定义。所以类的定义必须在MATLAB的搜索路径上,且类的定义必须和save时要保持”一致“,如果不一致程序需要付出一定的代价。

6.1.3 如果类的定义在save之后发生了变化

  1. 属性名称变了
    新变化的属性为空,继续装载下一个属性
  2. 添加了新的属性
    对象被正常装载,并且多了一个属性,且值取新定义中的默认值
  3. 属性被删除了
    对象被正常装载,删除属性不出现在对象中。
  4. 属性的默认值变了
    沿用旧的默认值

6.2 saveobj和loadobj方法

6.2.1 如何定义saveobj方法

当用户需要扩展或者定制save的行为时,可以在类的定义中重新定义saveobj成员方法,一旦提供了该类的saveobj方法,那么对该对象调用save命令时:save filename obj,MATLAB就会调用该类自己的saveobj方法保存对象。
saveobj返回值是一个struct,saveobj方法把一个object转换成一个struct。struct中field的名称最好和对象属性名称保持一致。

6.2.2 如何定义loadobj方法

为了保证MAT文件中的数据能够被正确初始化新的对象,用户还需要提供一个配套的loadobj方法。
loadobj成员方大的参数是一个struct,返回值必须是一个对象。
loadobj必须是一个静态方法,因为在调用loadobj时,类的对象还没有被建立起来,所以只能是静态的。loadobj必须返回一个新构成的该类的对象。
saveobj和loadobj应用:可以用他们保存中间计算的结果。比如自洽或者最优化计算,如果计算量大、时间长,只给出一次初始值,然后等很长时间希望其收敛,显然不是最佳的办法,如果计算到一半程序出错,还要重头再来更不方便。这种情况下应该每个一段时间对结果进行一次saveobj保存,下次重新开始时,再使用loadobj利用上次的保存结果作为新的初始值,根据收敛的情况,适当调整自洽或者优化参数让计算继续。
再比如,我们要遍历大量的数据,单次计算的时间不长,但是计算的次数很多,总体计算时间仍很长。这种情况下,我们也应该考虑使用saveobj把单次计算的结果保存下来,对大量的数据做分段遍历。

6.3 继承情况下的saveobj和loadobj方法

6.3.1 存在继承时如何设计saveobj方法

6.3.2 存在继承时如何设计loadobj方法

给每个loadobj成员方法都构造一个中间层,即在每个类中添加一个方法reload。该中间方法只负责赋值,不负责对象创建。这样父类和子类的loadobj就都可以正常工作了。

6.4 什么是瞬态(Transient)属性

可以直接给属性添加一个特征修饰词,告诉save命令,哪些属性需要保存,哪些属性不需要保存。这些特征修饰词叫做瞬态。
loadMat文件时,若没有对transient属性做特殊处理,该属性load过后,值为空。Transient属性分配内存,但是不会被保存到MAT文件,Dependent属性不分配内存,不会保存到MAT文件中。

6.5 什么是装载时构造(ConstructOnLoad)

如果希望在load过程中,自动给某些属性赋值,可以使用一个类叫ConstructOnLoad的关键词。如果把一个类声明成ConstructOnLoad=ture,那么在包含该类对象的mat文件被加载时,MATLAB会自动调用该类的缺省的构造函数。
只有在ConstructOnLoad和Transient无法解决问题时,才需要考虑重载函数saveobj和loadobj。

第7章 面向对象的GUI编程

7.1 如何使用GUIDE进行GUI编程

程序由一个主函数和一个FIG文件构成。主函数的GUIDE_GUI_OpeningFcn用来初始化控件的初值,以及balance变量。主函数中有GUIDE自动生成的回调函数的框架,回调函数可以通过handles来访问figure上各个控件对象,做必要的计算和更新。
GUIDE的优点是迅速地构造简单界面,缺点也很明显,当用户界面复杂到一定程度,尤其需要多个界面,并且经常需要修改时,使用主函数+若干子函数,并且用GUIDE界面来布置各个控件对象位置的方法就显得力不从心。

7.2 如何使用程序的方式(Programmatic)进行GUI编程

  1. 构造初始数据
  2. 产生figure对象和各个控件对象(可以设置layoutmanager来自动设置控件的位置)
  3. 注册响应的回调函数
  4. 设计回调函数

7.3 如何用面向对象的方式进行GUI编程

MVC(model-view-control)GUI编程
model:反应程序的中心逻辑。
view:显示user interface。主要职责1是产生Figure对象和控件对象,它们放在什么位置,以及设置默认值,2是把控件和它们的回调函数联合起来:
set(withdrawButton,'callback',@(o,e) withdraw_callback (o,e));
set(depositButton,'callback',@(o,e)deposit_callback (o,e));
MVC把GUI程序分成3部分:
model:负责程序的内在逻辑;
view:负责构造,展示用户界面;
controller:负责处理用户输入。

7.4 模型类中应该包括什么

需要定义一个事件,当值变化时,对model类对象发出通知,给监听该事件的listener。model对象和View对象之间是被监听和监听的关系。
model对象和View对象之间的关系是监听和被监听的关系。model的余额的改变将触发balanceChanged事件,View响应并更新balance的值。

7.5 视图类中应该包括什么

职责包括:
1. 向用户展示GUI界面。
2. 在模型中注册listener,监听模型内部状态的变化
3. 从模型中得到内部状态,并且显示到GUI上。

每一个View对象必须有一个控制器,因为控制器负责处理用户的输入。因为视图类的职责仅仅是展示,不包括响应,所以View类视图还要负责给自己拥有的控件注册回调函数。View类必须拥有model对象的handle,这样才能在updateBalance函数中查询model的内部状态,并且更新界面。
此外控制器对象也将拥有View对象的handle。
回调函数的两种方式
传统方式,使用set函数,对MATLAB的GUI控件注册回调函数
funcH=@controller.callback_drawbutton;
set(obj.drawButton,'callback',funcH);
第4章中给用户自定义类中的事件注册回调函数

  1. obj.modelObj.addlistener('balanceChanged',@obj.updateBalance);%注册

7.6 控制器类中应该包括什么

控制器的职责是:让模型和视图解耦,处理来自用户的输入,解释用户和GUI的交互,改变视图类上控件的外观。
注意controller类为了能够调用model对象中的函数withdraw和deposit,必须拥有model对象的handle。

7.7 如何把Model、View和Controller结合起来

withdraw按钮是MATLAB内置的控件对象,被监听的事件是“按钮被按下”,回调函数是withdraw_callback。该函数定义在controller中,在视图类中的attachToController方法中被注册。withdraw将造成balanceChanged,有一个listener监听该事件,内部的相应函数是视图类中的updateBalance。

7.8 如何设计多视图的GUI以及共享数据

如果只有一个视图,那么使用GUIDE或者面向过程式的设计方式就可以迅速地解决问题。否则就要利用MVC模式或者其他面向对象的设计模式。
多视图的GUI程序一个常见问题就是GUI之间共享数据,也就是模型和对象之间如何共享数据。只要对象互相拥有彼此的handle即可。如:

  1. classdef Model1<handle
  2. %MODEL1 此处显示有关此类的摘要
  3. % 此处显示详细说明
  4. properties
  5. hModel2
  6. hModelMain
  7. end
  8. methods
  9. function accessData(obj)
  10. %直接通过hModel2handle访问hModel2对象的数据
  11. temp=hModel2.someProp;
  12. end
  13. end
  14. end

如果各个模型、视图、控制器之间数据相互关联,都需要彼此的数据,为避免相互依赖耦合严重,可以设计一个Context类,其作用像一个枢纽,要求其他类的对象在创建时,都在这个上下文类中注册,并且给每个对象一个独一无二的ID,当一个对象需要取得其他类的对象的数据时,就可以通过ID到Context类对象处获取其他类的handle。
因为我们希望在任何路径和函数中都可以随时通过Context对象得到所需要的对象的句柄,所以该对象应该是一个全局对象,而且程序只需要这么一个上下文类,这样就需要限制该类所能产生的对象的数量,所以是singleton。

  1. context类的设计
    核心数据结果是一个MAP容器:其中,register函数要求外部对象(client)提供ID作为Key,并把外部对象的handle作为KeyValue,保存在Map容器中;getData方法通过client提供的ID在MAP容器中查询,并返回注册的handle
  2. 在context对象处注册ID
    先声明两个模型对象,并且每个对象都赋予一个在整个计算中都独一无二的ID,然后通过getInstance静态方法得到该上下文类的对象,在使用register注册。
  1. obj1=ModelDevice('Camera');
  2. obj2=ModelDevice('PowerSource');
  3. contextObj=Context.getInstance();%得到全局上下文对象
  4. contextObj.register('Camera',obj1);%注册Camera
  5. contextObj.register('PowerSource',obj2);%注册PowerSource对象
  1. 从Context对象处查询ID
    获得模型对戏Camera的句柄:只要Context类定义在MATLAB搜索路径上,我们就可以在任何环境中调用getInstance静态方法,context对象就好像一个被封装好的全局对象。提供一个不能处理无效ID的简化的Context类的实现:
  1. function someFunction( )
  2. contextObj=Context.getInstance();
  3. hCamera=contextObj.getData('Camera');
  4. end

7.9 如何设计GUI逻辑架构

针对含有Menubar、Toolbar和子视图的GUI结构,一种解决办法是:从基本的MVC模型出发,把各个视图用Composite(组合)关系组织起来,把各个控制器类用parent-child关系组织起来。
视图类:MainView可以作为最上层的视图对象容器。子视图对象也可以作为容器。
模型类:只有MainView拥有其他类对象的handle。
控制器类:MainController是最上层的控制器,是其他众Controller的parent。优点在于:底层控制器可以向高层控制器转发其无法处理的请求,响应的处理函数被分层。请求转发:

  1. function button1CallBack(obj)
  2. obj.parentController.handleRequest(RequestID);
  3. end

RequestID用来标记请求的类型,提供给上层的控制器用以查找响应的相应函数。

7.10 如何使用GUILayoutToolbox对界面自动布局

7.10.1为什么需要布局管理器

绝对布局仅适合简单的界面设计,可以把uicontrol的单位的单位设置成normalized,把绝对布局变成相对布局,可以避免重新计算的问题。如果想要按钮在放大Figure时保持原来的大小,须给控件提供一个resize函数,处理鼠标拖拽的相应。
相对布局:

  1. f=figure(‘Menubar’,‘none’,’Toolbar’,’none’,’’Position’,[200,200,100,200]);
  2. uicontrol(‘style’,’pushbutton’,’Units’,’normalized’,’Position’,[0.1 0.4 0.8 0.25],’parent’,f);

7.10.2 纵向布局类VBox

作用:用来对控件进行单列纵向布局。

7.10.3 横向布局类HBox

7.10.4 选项卡布局TabPanel

7.10.5 网格布局类Grid

7.10.6 GUILayout的复合布局

复合布局是指把各个布局管理器对象组合起来,通过多层的parent-Child关系对界面进行更加灵活的设计。

7.10.7 把GUILayoutToolbox和MVC模式结合起来

第8章 类的继承进阶

8.1 继承情况下的Constructor和Destructor

8.1.1 什么情况需要手动调用基类的Constructor

在初始化对象的过程中,如果有参数需要传递给基类Constructor,则需要显式地在子类constructor中调用基类的constructor。

  1. classdef Derived<Base
  2. properties
  3. b
  4. end
  5. methods
  6. function obj=Derived(a,b)
  7. obj=obj@Base(a);
  8. obj.b=b;
  9. end
  10. end
  11. end

8.1.2什么情况可以让MATLAB自动调用基类的Constructor

如果不需要向Base类的构造函数传递参数,就不需要在Derived的Constructor中显式调用parent的Constructor,MATLAB会隐式地自动帮助用户调用Base类的Constructor。
Base:

  1. classdef Base<handle
  2. methods
  3. function obj=Base()
  4. disp('Base CTOR called');
  5. end
  6. end
  7. end

Derived:

  1. classdef Derived<Base
  2. methods
  3. function obj=Derived()
  4. %matlab在这里先调用Base的构造函数
  5. disp('Derived CTOR called');
  6. end
  7. end
  8. end

注意:让MATLAB隐式地调用基类的缺省的Constructor的前提条件是基类必须定义了缺省的Constructor,或者说基类的Constructor必须能够处理零参数的情况,否则MATLAB会报错。

8.1.3 常见错误:没有提供缺省构造函数

MATLAB隐式地自动调用的是缺省的Constructor,如果用户自定义的Constructor没有提供nargin==0的情况处理,所以MATLAB将显示无法找到所需要的构造函数的错误。

8.1.4 在Constructor中调用哪个成员方法

子类可以重新定义基类的方法。如果子类的foo方法override了基类Base的foo方法,如果声明的是基类的对象,在基类的constructor中调用foo方法一定是来自于基类;如果声明一个子类对象,初始化过程中基类的constructor被调用,而基类constructor中又调用了成员方法foo,matlab将调用子类的foo方法。
matlab方法dispatch的规则是:查找方法的signature,而方法的signature由参数列表中的对象和方法名称决定。

8.1.5 析构函数被调用的顺序是什么

在继承结构下,一个类对象的析构函数调用和构造函数调用顺序相反。用户只需要调用子类的delete函数,整个对象在matlab内所占的内存空间就会得到释放,MATLAB会帮用户在子类的delete函数的末尾扩充,调用基类delete函数命令,所以用户不需要在子类的delete函数中显式调用基类的delete函数,且此时忽略delete方法的权限。

8.2 MATLAB的多重继承

8.2.1 什么情况下需要多重继承

8.2.2 什么是多重继承

包含一个以上的父类的继承。
classdef Derived

8.2.3 构造函数被调用的顺序是什么

先调用爷爷,然后大伯二伯。

8.2.4 多重继承如何处理属性重名

如果Base1的属性a是public的,则Base2的属性a设置成private的,如果声明一个Derived类的对象,其属性a的初始值来自Base1.

8.2.5 多重继承如何处理方法重名

1、基类中至少有一个方法是private方法
2、用户可以在Derived类中提供另一个同名方法foo,这个方法将覆盖Base1和Base2中的foo方法,从而消除模棱两可的可能性。

8.2.6 什么是钻石型继承

定义:一个基类在继承的层次中多次出现。

8.2.7 如何同时继承Value类和Handle类

实际中,可能同时需要重用Value类和handle类中的代码,可以使用value类的关键词HandleCompatible。

  1. classdef(HandleCompatible) BaseV
  2. end

注意:虽然使用了关键词,但是BaseV仍然是Value类。
给Value类注明HandleCompatible,就可以和Handle类一起做父类了,子类为handle类;给Value类注明HandleCompatible,value类一起做父类,子类为value类,但不是HandleCompatible;但是一般不这么做,因为没有意义。

8.3 如何禁止类被继承

使用关键词sealed

  1. classdef (sealed) A
  2. end

另一个办法就是把构造函数声明成private的,由于子类对象的建立必须要访问父类的构造函数,而private的构造函数将禁止子类的访问,所以该错误会出现在运行时,从而达到禁止继承的目的。

第9章 类的成员方法进阶

9.1 Derived类和Base类同名方法之间有哪几种关系

9.1.1 Derived的方法覆盖Base的方法

Base和Derived都定义了foo方法,声明Derived对象,调用该对象的foo方法,实际调用的是Derived的方法,父类的foo方法被覆盖了。

9.1.2 Derived的方法可以扩充Base的同名方法

可以利用foo@Base(obj)调用父类方法,但是只能调用直接的父类的方法。然后可以进行扩充。

9.1.3 Base的方法可以禁止被Derived重写

当基类的作者要确保该方法不被覆盖时,可以在基类方法中使用Sealed关键词。

9.2 什么是静态(Static)方法

也叫作类方法,它为类服务,最明显的特征就是不需要对象就能使用。因为类的Constant Property同样也为类服务,而不属于某个对象,所以静态方法可以访问类的Constant Property。类中的普通方法可以访问静态方法,只需要在方法前面加上类名。
因为静态方法没有把对象当做参数,所以定义类的静态方法既不能访问对象的一般属性,又不能调用类的一般方法。可以访问Constant Property。

9.3 同一个类的各个对象如何共享变量

9.3.1 什么情况下各个对象需要共享变量

各个对象之间共享数据。并且在对象的生存周期中,共享变量的值不变。

9.3.2 如何共享常量属性

如果在对象的生存周期中,共享变量的值不变,那么就可以把该属性声明成Constant,此时该属性被类的所有对象共有,内存中该Constant属性只有一个。

9.3.3 如何共享变量

如果要让类的各个对象共享变量,可以把该对象定义为静态成员方法中的persistent变量。比如可以用一个persistent变量计算对象创造的数量。
为什么MATLAB面向对象语言中不提供static变量?
C++和Java中,让一个类共享数据一般是通过static变量来实现的,但是MATLAB面向对象并没有。原因是:MATLAB长久以来的编程惯例是,在赋值时,变量比方法和类具有更高的优先级。如果添加了这个新功能,将造成向后不兼容,所以MATLAB不支持static变量。

第10章 抽象类

10.1 什么是抽象类(Abstract)和抽象方法

 定义:不能被实例化出对象的类。使用关键词Abstract来定义。

  1. classdef
  2. Shape<handle
  3.          methods(Abstract)
  4.                    draw(obj)
  5.          end
  6. end

定义抽象方法,只需要一行声明,不需要具体代码,并且该类的子类包括有一个同名的非抽象的办法。不能定义为Sealed类。

10.2 为什么需要抽象类

子类实现办法不尽相同,所以在抽象类中只声明,不定义,把定义留到具体的子类中完成。

10.3 如何使用抽象类

10.3.1抽象类不能直接用来声明对象

抽象类可以有构造函数,只是不能利用这个构造函数声明出对象来,该构造函数一般被子类构造函数所调用,需要显式调用。构造函数不是抽象的。

抽象方法声明中参数个数不是一个严格的限制。子类只需要实现同名方法即可,参数的数目不一定要和父类中一致。

10.3.2 子类要实现所有抽象方法

抽象类的子类必须实现抽象类中定义的所有抽象办法,否则,该子类仍然是抽象类。

第11章 对象数组

11.1 如何把对象串接成数组

串接:使用方括号[]把已有的变量组合起来,使其成为一个数组。使用这种方式构造的对象数组,适用于对象数量比较少的情况。对objArray中对象元素的访问和普通数组中元素的访问是一样的。在此基础上使用点+属性名称的语法,可以访问对象元素的属性。

  1. objArray=[b1 b2 b3];
  2. objArray(1).a=10;

11.2 如何直接声明对象数组

自动生成一个1*10的数组,并把第10个元素置为1,其余为没有被赋予初值的元素,用0作为其初值。
array(1,10)=1;
对象扩展:objArray(1,10)=Square(5);
MATLAB解释器会把这个命令翻译成如下指令:对第10个元素调用Square(5);其余的1-9个元素调用缺省的构造函数。缺省的构造函数只被调用一次,产生了一个对象,其余的都是直接拷贝对象。调用缺省的构造函数说明函数要考虑输入0参数的情况。在此扩展的对象数组是handle类的,尽管其内部数据的数值相同,但如果直接比较被拷贝的元素,它们的handle仍然是不同的。
注意Value类对象没有定义==运算符,所以为了比较两个对象,需要重载==(eq)运算符。

11.3 如何使用findobj寻找特定的对象

首先使用串接语法,构造一个对象数组。然后使用逻辑‘-and’或者‘-or’关键词,在findobj函数中查找指定对象。

11.4 如何利用Cellarray把不同类的对象组合到一起

MATLAB规定对象数组中元素的种类必须保持一致,如果不一致,matlab就会尝试把一个对象转换成另一个,如果找不到可以使用的对象转换函数,MATLAB就会报错。最简单的解决办法就是使用元胞数组(Cell Array),元胞数组是MATLAB中专门用来存放不同种类数据的工具。例如:

  1. o1=Square();
  2. o2=Circle();
  3. oCell={o1,o2}
  4. oCell{1}.a%访问CellArray中的一个元素

使用Cell取代array,简单实用,但是没有办法利用向量化式地方式集中访问数组中对象内部的元素。

11.5 什么是转换函数

定义:一种负责把该类的对象转换成其他类的对象的一种类的方法。
只需要在该类中定义转换函数,则调用的时候就能完成转换。转换函数只需要调用转换对象的Constructor。

11.6 如何利用转换函数把不同类的对象组合到一起

转换函数一般是隐式转换。假设数组的第一个为Square对象,则第二个赋值时需要检查,如果不相同,则需调用第二个对象的转换函数。如果没有定义转换函数,MATLAB会直接把第二个对象作为参数提供给square的构造函数。如果无法处理,MATLAB会报错。

11.7 如何用非同类(Heterogeneous)数组盛放不同类对象

11.7.1 为什么需要Heterogeneous数组

转换函数把不同类型的对象组合到一起,并且不改变对象的类型。
使用Heterogeneous需要两个类具有共同的父类,并且父类继承自一个叫做Heterogeneous的基类。
基类声明:
classdef Shape2D end
优点:
+ 不用定义convert函数,不存在对象之间的相互转换;
+ 构造出的数组可以存放共同基类的对象;
+ 可以使用数组的下标语法,向量化访问对象的共同特征;
+ 可以使用对象的共同方法,必须是Sealed。

11.7.2 含有不同类对象的数组类型

包含有不同对象的数组类型,总是取对象们最近的共同父类。

11.7.3 使用Heterogeneous要避免哪些情况

1、没有除Heterogeneous之外的共同的父类。
2、多重继承存在交叉继承,这时候也不能将子类的对象放到一个对象数组中去。

11.7.4 如何向量化遍历数组中对象的属性

如果用Dot语法访问数组中对象的共同属性,返回的结果将是一个用都好分隔的list。

11.7.5 如何设计成员方法使其支持向量化遍历

s=objArray.area
objArray被当做一个参数传入area方法中。
如果是HeterogeneousArray,想要支持向量化访问的成员方法,必须将其声明成sealed,禁止子类中有不一致的定义。
对象数组是否可以向量化调用destructor?
destructor禁止定义成Sealed,否则子类资源可能无法释放。但是MATLAB对delete方法仍支持使用objArray.delete的格式调用对象们的析构函数。MATLAB并没有向量化地调用对象们的析构函数,因为根本不存在这样一个共同的delete方法。objArray.delete这里为:逐个调用每个对象的析构函数。

第12章 类的运算符重载

12.1 理解MATLAB的subsref和subsasgn函数

12.1.1 MATLAB如何处理形如a(1,:)的表达式

A(1:2,:)会被MATLAB解释器转换成一个subsref的函数调用。该函数的第一个参数是要访问的数据A;第二个参数是要访问元素所在的位置,并且该位置信息存放在一个结构体中。subsref(A,s);例如A(1:2,:),这里s.type=’()’;s.subs={1:2,’:’};也可以通过以下方法访问数组元素:

  1. s.type=’()’;
  2. s.subs={1:2,’:’};
  3. subsref(A,s);

对数组元素的赋值会被Interpreter转换成一个subsasgn的函数调用,例如:
A(1,1)=0;被转换成subsasgn(A,s,0),其中s是一个结构体,
可以通过以下实现A(1,1)=0:
s.type=’()’;
s.subs={1,1};
subsasgn(A,s,0);

12.1.2 MATLAB如何处理形如a{1,:}的表达式

对元胞数组B第一列的访问B{:,1}将会被Interpreter转换成如下的subsref函数调用:

  1. s.type=’{}’;
  2. s.subs={‘:’,1};
  3. subsref(B,s);

对元胞数组B的第一个元胞的赋值:

  1. B{1,1}=0;将被MATLAB转换成如下subsasgn函数调用。
  2. s.type=’{}’;
  3. s.subs={1,1};
  4. subsasgn(B,s,0);

12.1.3 MATLAB如何处理形如s.f的表达式

对struct的访问和赋值也可以直接通过直接调用内置函数来完成。
days.f1和以下等价
s.type=’.’;
s.subs={‘f1’};
subsref(days,s);
days.f3=’NULL’ 和以下等价
s.type=’.’;
s.subs={‘f3’};
subsref(days,s,‘NULL’);

12.2 如何重载subsref函数

数组、矩阵、元胞数组、对象都支持上面的等价调用。形如subsref(obj,s)的调用,优先调用用户自定义的方法。

12.3 如何重载subsasgn函数

优先调用用户自定义的方法。
重载的时候需要返回obj.matrix和obj.cell因为该重载函数的任务是修改对象的属性,但是obj.matrix和obj.cell不是handle对象,所以在函数内部的修改仅仅是局部修改,必须将它传回来。
重载subsasgn和subsref,将会重载该类的属性访问权限,使用时要慎重。

12.4 什么情况下重载下标运算符

注意:使用自定义的下标运算符取代内置定义的下边运算符应当是一个方便编程的手段,不应该使用在对性能要求很高的程序中。

12.5 如何重载plus函数

MATLAB中+默认是做算数运算,所以在内部会把string转换成数字,调用plus函数。

12.6 MATLAB的Dispatching规则是什么

对于形如obj.foo(arg1,arg2,arg3)的调用,Dispatcher会直接检查obj类定义中是否定义了foo的方法,如果定义了,那就调用;没有定义,报错。
形如foo (obj,arg1,arg2,arg3),Dispatcher不会首先检查obj类定义中是否定义了foo的方法,而是首先检查四个参数obj,arg1,arg2,arg3哪个参数所属的类是更高级别的类,即为dominant类,然后查找dominant类中是否定义了foo方法,如果定义就调用;没有定义,报错。
类的级别:任何用户定义的类,级别高于MATLAB内置的类。
指定用户定义的类之间的级别
classdef (InferiorClasses={?A,?B})C
end
C的级别比A和B的高,如果这三类出现在参数列表中,C的对象是dominant对象。

12.7 如何判断两个对象是否相同

handle对象,==运算时在handle中就定义好的算法,用于检查handle类所指向的实际数据时同一个。handle对象的拷贝是浅拷贝,只复制了类中的地址,没有复制类中实际指向的数据对象。
Value类数据,没有定义‘==’运算符,需要用户自己指定行为。

12.8 如何让一个对象在行为上像一个函数

仿函数:使普通函数具有类的功能。

12.9 MATLAB中哪些算符允许重载

第13章 超类

13.1 什么是超类(MetaClass)

定义:用一种普遍的方式来描述类,即用类的方式来描述类。

13.2 如何获得一个类的meta.class对象

第一种得到meta对象的方法是:已知类的名字,可以在类名前面加上一个问号来获得meta.class例如:metaObj=?FileIterator。
第二种方法是:如果有了一个类的对象,可以用meta.class函数来获得meta.class对象,如:metaObj=metaclass(obj);
第三种方法最灵活:如果类的名字是以字符串的形式存在的,可以利用meta.class类中的成员方法fromName,该函数接受string input,返回meta.class对象,例如:name=’Vehicle’;metaObj=meta.class.fromName(name);

13.3 meta.class对象中有些什么内容

如何系统地获得类中所有property的名字?
metaobj=?Derived;
propNameList={metaobj.PropertyList.Name}
+ metaobj.PropertyList是一个对象数组,其中的内容是meta.property的对象
+ 使用.语法可以向量化访问数组中对象的共同属性
+ metaobj.PropertyList.Name返回的结果是string类型,由于string的长度不同,使用{}把返回的结果收集到元胞数组中去。

13.4 如何手动克隆一个对象

  1. handle类的浅拷贝
    对象的属性相互关联,不独立,俗称浅拷贝。
  2. 简单克隆
    优点:实现简单。
    缺点:如果要克隆有很多的属性,那么一个一个键入属性会很麻烦。
    方法1:构造一个方法,先在该方法中声明一个新的对象,叫做newObj,再把旧的对象每一个属性的值都复制到新的对象newObj中,最后在将该newObj返回。
    方法2:用meta.class函数,先获得该类的metaObject,然后取出其中的propertyList中属性的名字,然后遍历赋值即可。
    该方法中newobj.(props{j})=obj.(props{j})有一个隐藏的前提,所有的property都是Value类型的对象,如果有一个property是handle类型的,则得到的拷贝仍然是浅拷贝。
  3. 递归克隆
    为了解决仍然是浅拷贝的问题,用户需要提供两个类的clone函数
  1. classdef Ref<handle
  2. properties
  3. a
  4. bobj
  5. end
  6. methods
  7. function obj=Ref()
  8. obj.a=rand(1);
  9. obj.bobj=BHandle;
  10. end
  11. function newobj=clone(obj)
  12. newobj=Ref();
  13. metaobj=metaclass(obj);%得到meta Object
  14. props={metaobj.PropertyList.Name};%得到props名字
  15. for j=1:length(props)
  16. tmpProp=obj.(props{j});
  17. if(isa(tmpProp,'handle'))%调用该类的Prop方法
  18. newobj.(props{j})=tmpProp.clone();
  19. else
  20. newobj.(props{j})=tmpProp;
  21. end
  22. end
  23. end
  24. end
  25. end

Bhandle:

  1. classdef BHandle<handle
  2. properties
  3. var
  4. end
  5. methods
  6. function obj=BHandle()
  7. obj.var=rand(1);
  8. end
  9. function newobj=clone(obj)
  10. newobj=BHandle();
  11. metaobj=metaclass(obj);%得到meta Object
  12. props={metaobj.PropertyList.Name};%得到props名字
  13. for j=1:length(props)
  14. tmpProp=obj.(props{j});
  15. if(isa(tmpProp,'handle'))
  16. newobj.(props{j})=tmpProp.clone();%程序不会运行到这里
  17. else
  18. newobj.(props{j})=tmpProp;
  19. end
  20. end
  21. end
  22. end
  23. end

13.5 如何使用matlab.mixin.Copyable自动克隆一个对象

MATLAB提供mixin类的Copyable,其中包括copy和copyElement两个方法帮助用户完成基本的handle类对象的深拷贝。copy方法是sealed,不允许子类重载,copyElement是protected,允许子类重载。用户只需要让自己的类继承自matlab.mixin.Copyable即可,此时仍为handle类。和前面类似,copy和copyElement组合在一起也是自动遍历的对象中的各个属性做拷贝。

  1. clear all;clc;
  2. record1=StudentRecord();
  3. record1.name='A';
  4. record2=copy(record1);
  5. record2.name='B';
  6. record1.name
  7. record2.name

copyable类中的方法默认的并不包括对属性做递归的深拷贝,如果对象有一个handle对象,使用copy方法进行对象拷贝时,该对象被浅拷贝。
用户可以重载copyElement方法。先在copyElement中调用父类的copyElement方法,得到新的StudentRecord对象,这时homework属性是浅拷贝。然后对其中的homework属性做完全的复制。

第14章 面向对象程序设计的基本思想

14.1 单一职责原则

一个类最好只有一个引起它变化的因素。
UML,实心菱形表示非包括不可的组合关系;空心菱形表示松散的可有可无的组合关系,也叫聚集。

14.2 开放与封闭原则

程序的设计应该对修改是封闭的,对扩展是开放的。

14.3 多用组合少用继承

使用组合可以让系统有更大的弹性,不仅可以将算法封装成类,还可以在运行时动态地改变对象的行为。

14.4 面向接口编程

上层模块通常是包含抽象方法的抽象类,而继承他们的子类要提供这些方法的实现。通常这些子类叫做对接口的实现。

第15章 创建型模式

15.1工厂模式:构造不同种类的面条

15.1.1简单工厂模式

简单工厂模式:对象的产生细节由一个特定的类负责,并且该类中包含了必要的逻辑判断以产生不同的对象。

15.1.2工厂模式

工厂模式的关键在于具体的对象的创建时机推迟到工厂子类中完成。不希望高层的模块和具体的硬件细节打交道,所以把产生这个对象的工作交给工厂模式去完成。

15.1.3Factory模式总结

定义一个创建对象的接口,让子类决定实例化哪个类。factory模式使一个类的实例化延迟到其子类。

15.1.4如何进一步去掉switch/if语句

对于缺省的Constructor可以:

  1. function obj=createObj(classname)
  2. obj=eval(classname);
  3. end
  4. test:createObj(‘Sub1’);

否则对于如下的Constructor:

  1. classdef Sub1<handle
  2. properties
  3. a
  4. end
  5. methods
  6. function obi=Sub1(var)
  7. obj.a=var;
  8. end
  9. end
  10. end

可以使用strcat构造要执行的命令的字符,然后再用eval函数执行命令。
test:

  1. classname=’Sub1’;
  2. cmd=strcat(classname,’(’,’10’,’)’);
  3. obj=eval(cmd);

或者使用str2func函数,从classname处获得类的构造函数的句柄,然后像正常使用构造函数那样调用该函数句柄。

  1. classname=’Sub1’;
  2. ConstructHandle=str2func(classname);
  3. obj=ConstructHandle(3);

15.1.5 抽象工厂

最后的产品是各种面条的聚集。

15.1.6 AbstractFactory模式总结

提供一个创建一系列相关或者相互依赖的对象的接口,而无需指定它们具体的类。
1. 模式结构

  1. 类之间的协作
    在运行时,client将负责创建一个ConcreteFactory类的实例,这个具体的工厂具有创建不同对象的代码。Client可以更换具体的工厂以得到不同的具体的产品。
  2. 何时使用AbstractFactory模式
    当一个系统要独立于它的产品创建、组合、表示时。
    当一个系统需要多个产品系列中的一个来配置时。
    当需要强调一系列相关产品的设计时,以便进行联合调用。

15.2 单例模式:给工程计算添加一个LOG文件

15.2.1 如何控制对象的数量

MATLAB工程科学计算时,在过程中输出一些结果,用来调试程序。一般记录中间结果的文件叫做LOG。在程序运行期间,都能往LOG中写入数据,并且程序运行期间有且只有一个LOG。
单例Singleton模式:该模式用来控制一个类所产生的对象的数量。

  1. classdef MyClass<handle
  2. methods(Access=private)%私有构造函数
  3. function obj=MyClass()
  4. disp('constructor called');
  5. end
  6. end
  7. methods(Static)
  8. function obj=getInstance()%静态接口方法
  9. persistent localObj
  10. if isempty(localObj)||~isvalid(localObj)%如果localObj不存在则创建
  11. localObj=MyClass();
  12. end
  13. obj=localObj;%如果localObj已存在则返回
  14. end
  15. end
  16. end
  17. obj1=MyClass.getInstance();
  18. obj2=MyClass.getInstance();
  19. obj3=MyClass.getInstance();

这是一个Handle类,所以构造出来的obj1,obj2,obj3实际指向同一个实例。

15.2.2 应用:如何包装一个对象供全局使用

  1. classdef LogClass<handle
  2. properties
  3. FID
  4. end
  5. methods(Access=private)%私有构造函数
  6. function obj=LogClass()%打开文件
  7. obj.FID=fopen('logfile.txt','a');
  8. end
  9. end
  10. methods
  11. function delete(obj)%关闭文件
  12. fclose(obj.FID);
  13. end
  14. function print(obj,string)
  15. fprintf(obj.FID,string);
  16. end
  17. end
  18. methods(Static)%控制外部访问
  19. function obj=getInstance()%静态接口方法
  20. persistent localObj
  21. if isempty(localObj)||~isvalid(localObj)%如果localObj不存在则创建
  22. localObj=LogClass();
  23. end
  24. obj=localObj;%如果localObj已存在则返回
  25. end
  26. end
  27. end

15.3 建造者模式:如何用MATLAB构造一辆自行车

15.3.1 问题的提出

15.3.2 应用:Builder模式为大规模计算做准备工作

15.3.3Builder模式总结

将一个复杂对象的构建与它的表示方法分离,使得同样的构建过程可以创建不同的表示。
1. Builder模式结构
Director、Builder、ConcreteBuilder、Product、Part


2. 类之间的协作
+ Client(外部程序)负责构造Director对象,并且设定该Director所要指导的具体Builder。
+ Director拥有Builder对象,并且可以替换。
+ Builder拥有product对象,Product对象由parts对象组成
+ 构造产品的请求发自于Director,Builder接到请求之后按照Director所指导的顺序把parts对象添加到产品中去
3. 何时使用Builder
+ 当构造过程中允许被构造的对象有不同的表示时。
+ 当创建复杂对象的算法,要独立于该地对象的装配方式时。

第16章 构造型模式

16.1 装饰者模式:动态地给对象添加额外的职责

16.1.1 装饰者模式的引入

16.1.2 面馆菜单代码

这种设计对修改是封闭的,还支持多次装饰,对扩展是开放的。

16.1.3 装饰者模式总结

动态地给对象添加一些额外的职责,就增加功能来说,Decorator模式相比生成子类更为灵活,Decorator模式也叫做包装器。
1. 装饰者模式结构
Component、ConcreteComp1、ConcreteComp2、Decorator、ConcreteDeco1、ConcreteDeco2

  1. 类之间的协作
    Concrete Component和Concrete Decorator都有相同的基类,并且Decorator基类中的compHandle被用来指向
  2. 何时使用Decorator模式
    Decorator模式可以避免通过创建子类来扩展类的功能,Decorator是以动态的方式给单个对象添加新的功能。想要扩展类,又想避免子类数量爆炸时,可以考虑使用Decorator模式。

第17章 行为模式

17.1 观察者模式:用MATLAB实现观察者模式

17.1.1 发布和订阅的基本模型

17.1.2 订阅者查询发布者的状态

17.1.3 把发布者和订阅者抽象出来

17.1.4 Observer模式总结

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知,并且自动被更新。
1. 模式结构
Subject、ConcreteSubject、Observer、ConcreteObserver

  1. 类之间的协作
    • 具体的观察者向ConcreteSubject对象发出订阅的请求,该ConcreteSubject对象把具体观察者加到其内部的一个列表中。
    • 当ConcreteSubject内部发生改变,需要通知其观察者时,它将遍历其内部的观察者的列表,依次调用这些观察者的update方法。
    • 观察者得到更新的通知,它可以向ConcreteSubject对象提出查询请求。

17.2 策略模式:分离图像数据和图像处理算法

17.2.1 问题的提出

17.2.2 应用:更复杂的分离数据和算法的例子

17.2.3 Strategy模式总结

  1. 模式结构
    Context、Strategy、StrategyA、StrategyB、StrategyC

  1. 类之间的协作
    • 在Context对象中存放数据,而Strategy对象中存放算法,Context对象可以选择所需要的算法。
    • Context对象把来自外界的计算请求转交给Strategy对象。
    • 转交请求是,Context把自己作为一个参数传递给Strategy对象,以提供Strategy对象计算所需要的数据。
  2. Strategy模式何时使用
    Strategy模式是用来封装算法的,在实践中,可以用来封装几乎任何类型的规则。如果在分析过程中,需要在不同的情况下应用不同的算法,就可以考虑使用Strategy模式来处理问题。

17.3 遍历者模式:工程科学计算中如何遍历大量数据

17.3.1 问题的提出

  1. files=dir(c:\datafolder);
  2. files=files(cell2mat({files(:).isdir})~=1);%去除文件夹中的目录
  3. for i=1:length(files)
  4. inputname=file(i).name;
  5. imgObj=Image(inputname);
  6. imgObj.doMethod();
  7. end

17.3.2 聚集(Aggregator)和遍历者(Iterator)

17.3.3 Iterator模式总结

Iterator(内部)的意图是用一种方法顺序去访问一个聚集对象中的各个元素,而又不用暴露对象的内部显示。
1. Iterator模式结构
Aggregator、ConcreteAggregator1、ConcreteAggregator2、Iterator、ConcreteIterator1、ConcreteIterator2

  1. 类之间的协作
    • 因为遍历的方式和Aggregator自身的内部情况有关。只有Aggregator才有足够的信息来告诉外部类该如何遍历自己,所以具体的Aggregator负责产生具体的Iterator。
    • 具体的Iterator类将拥有Aggregator的Handle用来从集合中取元素
    • 两个Base类用来提供接口,规定具体的Aggregator和具体的Iterator应该提供何种方法,以及方法的signature应该是怎么样的
    • ConcreteIterator提供不同的遍历方式,比如ConcreteIterator1提供从前往后的遍历,而第二个则是随机遍历。

17.4 状态模式:用MATLAB模拟自动贩卖机

17.4.1 使用if语句的自动贩卖机

17.4.2 使用StatePattern的自动贩卖机

17.4.3 State模式总结

状态模式:允许对象修改内部状态是改变它的行为,对象看起来好像是修改了它的类。
1. State模式的结构
Context、State、ConcreteState1、ConcreteState2

  1. 类之间的协作
    对于来自外界的和自身状态相关的请求,Context对象将这些请求委托给ConcreteState对象处理。
    ConcreteStata对象可以访问Context对象,也可以改变Context对象的内部状态。这就要求:在request方法中,Context对象必须把自己作为一个参数传递给ConcreteState对象。
    使用Context类的client不需要和State的对象打交道。
  2. 何时使用State模式
    • 当对象的行为取决于它的状态,并且该对象在运行时会改变状态。也就是说对象的行为会在运行时改变,可以考虑使用State模式。
    • 当一个操作中含有庞大的多分支条件语句,而且这些分支语句依赖于对象的状态时,可以考虑使用State模式将每个条件分支放入一个独立的类中,而对象的状态对应于拥有一个状态类对象。

17.5 模板模式:下面条和煮水饺有什么共同之处

17.5.1 抽象下面条和煮水饺的过程

17.5.2 应用:把策略和模板模式结合起来

17.5.3 Template模式总结

模板方法模式:定义一个操作中的算法的骨架,将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可以重新定义该算法的某些特殊的步骤。
1. Template模式的结构
AbstractClass、ConcreteClass1、ConcreteClass2。Template模式中,含有反向控制的结构,因为通常是子类调用父类的操作,而在这个模式里,却是父类调用子类。这种结构也被叫做好莱坞模式。

17.6 备忘录模式:实现GUI的UNDO功能

17.6.1如何记录对象的内部状态

Map:http://blog.sina.com.cn/s/blog_6163bdeb0100rdx3.html
http://blog.sina.com.cn/s/blog_4a0824490102vamk.html

17.6.2如何利用备忘录模式实现GUI的do和undo操作

17.6.3Memento模式总结

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样,以后可以将该对象复原到原先的状态。
1、 memento模式结构

2、 类之间的协作
+ Originator是拥有状态的对象,外部的命令向originator发出一个请求,要求保存备忘录。
+ originator把自身状态数据封装到一个memento对象中,并且提交给caretaker保存。
+ 只有Originator知道该如何利用Memento对象中的数据,Caretaker的工作仅仅是保存各个Memento对象,不能对备忘录的内容进行操作。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注