@hainingwyx
2017-07-27T16:20:54.000000Z
字数 34556
阅读 15078
matlab
面向对象
定义:一种以过程为核心的编程算法,把问题的过程按照步骤分解出来,然后用函数形式加以实现。
面向过程编程方法的优点是简单快捷,缺点是面对复杂的程序难以修改和维护。
面向对象编程(Object Oriented Programming,OOP)把任务分解成一个个相互独立的对象,通过各对象的组合和通信来模拟实际问题。
真实世界中具体的东西。
特点:具有各种属性;具有相关的行为。
对各个具体、相似对象的共性的抽象。
Unified Modeling Language:对程序的一种图形表达方式
Matlab要求每个类的定义保存为一个同名的文件。
在matlab中建立实体对象的方式是:调用类的构造函数(Constructor)。构造函数和类同名;构造函数的返回值是构造出来的新的对象。
不容易维护和扩张。大多数情况下,如果已有了面向过程的程序,可以用面向对象的思想去包装这些已有的程序,并且在此基础上继续维护和扩张已有的程序。
把大问题分解成小的对象,通过组合和信息传递完成任务,通过继承达到代码的复用,修改或添加模块不会影响到其他模块
classdef Point2D<handle
properties
end
methods
end
end
类定义中包含一个属性block和一个方法block。
constructor构造方法:负责产生并且返回类的对象,通常还可以用来初始化对象的属性。
创建对象的方式是直接调用类的constructor
面向对象编程中,使用Dot运算符来访问对象的属性。
在MATLAB类的property Block定义中,可以为属性直接赋一个值,此时为默认值。
如果默认值的赋值使用表达式,该表达式仅在类定义被MATLAB装载时执行一次,所以表达式计算的结果最好是固定。
除了在property block中给属性赋默认值,还可以在constructor对属性变量做初始化。
定义:在对象生存周期中值保持不变的属性,如果对该属性进行修改都将报错。
定义constant property需要用constant关键词。如
classdef A<handle
properties(Constant)
R=pi/180
end
end
如果不显示给定常量属性一个特定的值,则默认为empty double。另外constant property不用创建对象就可以直接使用类中的常量。
A. R %A是类名而不是对象名
定义:其值依赖于其他的属性,一旦其他的属性改变,该属性也做响应的变化,在概念上可以理解为数学中的因变量。为了不需要每次都在自变量变化之后更新因变量,可以把该值设为dependent(依赖)属性。
dependent属性特点:对象内部没有给属性分配物理的存储空间,每次该属性被访问时,其值将被动态地计算出来。计算该属性的方法由get提供。
classdef Point2D<handle
properties
x
y
end
properties(Dependent)
r
end
methods
function obj=Point2D(x0,y0)
obj.x=x0;
obj.y=y0;
end
function r=get.r(obj)%dependent属性要放在get方法中
r=sqrt(obj.x^2+obj.y^2);
disp('get.r called');
end
end
end
验证程序:
p1=Point2D(1.0,2.0);
p1.r
p1.y=1;
p1.r
设置为dependent还有一个好处:支持dot和向量化操作,如果r是一个矩阵或者矢量,在类的外部,可以直接进行适量操作(obj.r(1:2));如果r是一个结构体,可以直接使用dot继续访问r内部的其他fields(obj.r.otherfields)
另一个例子:
classdef View<handle
properties
hFig
hEdit
end
properties(Dependent)
text
end
methods
function obj=View()
obj.hFig=figure();
obj.hEdit=uicontrol('style','edit','parent',obj.hFig);
end
function str=get.text(obj)
str=get(obj.hEdit,'String');
end
end
end
什么是matlab的解释器?
回答:MATLAB解释器将MATLAB命令行、脚本、函数中的MATLAB的代码翻译成内部指令,并且执行。MATLAB作为一种解释型语言,从写代码到执行代码的转换是立即完成的,并且源代码总是存在,一旦出现错误,MATLAB解释器就能很容易找出错误的位置。
隐藏的效果是在命令行中查看对象的信息时,该属性不会被显示出来。
classdef A<handle
properties(Hidden)
var
end
methods(Hidden)
function internalFunc(obj)
disp(‘I am a hidden function’);
end
end
end
用户如果知道属性的名字仍然可以正常地访问该属性。
Hidden关键词用处是隐藏类的内部的细节。
methods
function [returnValue]=functionName(arguments)
…….
end
end
如果成员方法只有几行可以放在类定义中;否则可以在类中仅给出方法的声明,把实现放到一个独立的文件中去。
签名:对象连同方法的名称构成了该方法在matlab中独一无二的签名。
函数签名=函数名+所属类
每次调用对象的方法时,MATLAB的dispatcher都会动态地判断该方法的签名。虽然从表面看两个属于不同类的对象可能调用了方法名称相同的方法,但是MATLAB还是可以通过判断该函数所属类来找到匹配对象的。
脚本中的clear classes命令是必须的吗?
回答:是的,作为一个良好的编程习惯,在每个程序开头使用clear清除残存的变量和旧的定义是必要的。特别是在类的定义被秀海之后。我们要清楚内存中旧的定义,这样才能使新的修改生效。
类是一种抽象的定义,包括property和method,占用内存。对象是一个物理实体,根据类模板创作出来的,也有物理内存,构成对象本身的只有数据。方法是类具有的一种操作,被所有类共享。
在MATLAB面向对象编程中,可以通过定义类的disp方法(也叫覆盖override)来定制对象在命令行上的输出内容,包含定制disp方法的类的对象,在命令行被用户查询时,会优先调用类的disp方法。
function disp(obj)
s=sprintf('%-17s(%s:%s)\n',obj.name,obj.exchange,obj.symbol);
s=[s,sprintf('--------------------------------------------\n')];
s=[s,sprintf('Last Trade: %6.2f',obj.last_price)];
s=[s,sprintf(' (%s %s)\n',obj.last_time,obj.last_date)];
disp(s);
end
定义:是一种特殊的成员方法。和类的名称相同,用来创造类的实例。类定义中只能有一个constructor。constructor只能有一个返回值,且必须是新创建的对象。
MATLAB中声明一个对象时,工作顺序是:先装载类的定义,然后调用constructor。
MATLAB是弱解释性语言,不能通过参数的数目的不同来决定调用哪个参数,类似的功能只能放到函数体中,通过判断参数的个数(nargin)来实现,根据nargin的不同,选择不同的代码。
function obj=Point2D(x0,y0)
if nargin==0 %没有提供参数,default constructor
%.....
elseif nargin==2
%......
else
%......
定义:不带任何参数的构造函数
如果用户没有提供任何形式的constructor,MATLAB会在内部提供一个default Constructor。看似一个空函数,但后台还是有工作的,如给对象、属性分配空间等。该自动提供的constructor不解释任何参数,如果尝试提供任何参数,将会报错。
继承关系也叫做泛化关系,被继承的类叫做父类或者基类,继承的类叫做子类或者派生类。在MATLAB中可以用isa查询一个对象是否属于一个特定的类。如:isa (p2,’Point2D’);
UML中继承用空心三角箭头表示
子类先继承了父类的属性和方法,然后在父类的基础上增加自己的属性和方法。代码中的@表示调用父类的constructor,返回一个对象。
obj=obj@Super();
句柄:句柄是一种抽象的思想:隐藏了内核实现的细节,同时为调用提供了方便,保证了内核的安全。
参考文章:http://blog.chinaunix.net/uid-26285146-id-3262293.html
子类和父类成员方法可以有相同的名字,并且在子类方法内部可以调用父类的同名方法,在子类中,除同名方法外的其他地方不能调用父类的同名方法。
调用语法:superMethod@superClass(this,otherArguments)
superMethod:父类同名函数
superClass:父类类名
this:子类对象的Handle传递给父类,obj
otherArguments:向父类同名函数传递其他参数
概念:建立在继承的基础上。同名的方法被不同的对象调用,能产生不同的行为(形态)。
例子:
obj1=Print2D();
obj1.print();
obj2=Print2D();
obj2.print();
使用继承可以提高程序的复用性,但是盲目继承会造成逻辑混乱和程序适用性降低。所如果A和B不相关,不能为了B的功能更多些继承A的功能和属性。
共有继承可以用is a“是一个”等价
如果在逻辑上A是B的一部分,则不要为了让A得到B的功能去继承B,而是要用A和其他东西组合出B。如眼鼻口耳、头的组合关系。组合通过Head对象的constructor来保证。组合关系要求Head对象一定在内部拥有Nose、Eye、Mouth、Ear。
UML图上,实心菱形箭头表示组合关系。
组合:整体和部分,
聚集:松散的整体和部分,自行车由架子、轮子、坐垫组成。反映在程序上就是,Wheel和Seat对象可以独立创建。
UML图上,空心菱形箭头表示聚集。
set方法给对象属性的赋值提供了一个中间层,可以检查赋值是否符合要求或者检查赋值的类型。还可以在set方法中做一些其他工作,比如做一个LOG记录每次属性被赋的值。
classdef A<handle
properties
a
end
methods
function set.a(obj,val)
if val>=0; %检查赋值要求
obj.a=val;
else
error('a must be positive');
end
end
end
end
main:
obj=A();
obj.a=-10 %试图给a赋负数
load一个对象时,属性set方法会被MATLAB调用,如果这时对象的某些属性值仍是默认值,这些属性会经过set方法,被验证有效性。可以认为是数据有效性的最后一层屏障。
get方法提供了对成员属性查询操作的一个中间层。可以使程序变得向后兼容。比如一个大型程序的升级,需要把date的名字改成timeStamp。
classdef Record <handle
properties(Dependent,Hidden)
date%设置成dependent不占用内存,Hidden属性使得不会被命令行显示出这个旧的属性
end
properties
timestamp
end
methods
function set.date(obj,val)
obj.timeStamp=val;
end
function val=get.date(obj)
val=obj.timeStamp
end
end
end
set和get方法还是有时间成本的。
classdef SomeClass<handle
properties(Access=private)%私有属性
prop_private
end
properties(Access=protected)%保护属性
prop_protected
end
properties(Access=public)%默认是共有的
prop_public
end
methods(Access=privated)%同上
function result=someFunction(obj)
%...
end
end
end
UML图中,+表示public,#表示protected,-表示private。
属性的访问权限可以被细分为赋值SetAccess和查询GetAccess的权限。如:
properties(SetAccess=private, GetAccess=public)
var
end
表明该属性可以被外界程序查询值,但不能被外部程序赋值,赋值只能在类的内部进行。
classdef BankAccount<handle
properties(SetAccess=private)
balance
accountNumber
end
methods
function obj=BankAccount(balance,num)
obj.balance=balance
obj.accountNumber=num
end
function deposit(obj,val)
obj.balance=obj.balance+val
end
function withdraw(obj,val)
obj.balance=obj.valance-val
end
end
end
public属性,在C++和Java中,如果把一个成员属性定义成public,意味着该属性可以被外部直接访问和赋值,这相当于该属性直接暴露给外部。matlab在外部直接访问和public属性之间可能还存在一个set和get的中间层,即使是public属性,Matlab面向对象语言提供了检查措施。
private属性,在C++和Java中,如果把一个成员定义成private,意味着不可以被外部直接访问和赋值,但是习惯上会提供一个public的set和get方法。两者有差别,前者只是普通方法。C++和Java是现实调用set方法,matlab是隐式调用set方法。
matlab 2014b引入了新的自动更新类定义的功能。
类被首次使用,Matlab会将类定义一次性装载进内存中之后再声明类的对象时,不需要重新读入类定义。面向对象编程时,如果定义了一个类,声明了一个对象,然后改变了该类的定义,再尝试声明一个对象时,会出现Warning。只要有旧的instance存在,新的类定义就无法生效。通常可以
1:clear obj
如果工作空间中还有其他重要变量存在,用户不希望全部清除他们,可以有选择地清除对象。如:工作空间中有A类的对象obj1和obj2,我们修改A类的定义,只要执行,就可以在下次声明A的对象时,使用新的定义。
clear obj1 obj2
2:clear classes
清除工作空间所有变量、所有之前被装载的类定义、类中的constant属性
如果参数的值在函数内部没有改变,函数的工作空间知识复制了参数的必要数据,而实际数据仍然在函数工作空间之外。这些必要数据提供了访问实际数据的一种渠道(指针)。
function result=myAdd(a,b)
result=a+b;
end
如果函数体内修改了参数的值,在参数的值改变的前一刻,Matlab会在函数工作空间中复制一个局部拷贝,以保证所有对a矩阵的修改都是局部的,即函数体内对a的修改不会影响到Main工作空间中的a矩阵。这种技术也叫Lazy Copy。不到万不得已不构造局部拷贝。
function result=myAdd(a,b)
a=round(a);
result=a+b;
end
两者的区别是:用户定义的类是否继承了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字节。
nValue=mValue;
nHandle=mHandle;
Value类对象在内存中进行了完全拷贝,也叫深拷贝;
Handle类对象只拷贝Handle类对象本身,而没有拷贝句柄对象指向的实际数据。这是一个不完全的拷贝,也称浅拷贝。因为他没有深入到handle所指向的数据对实际数据进行拷贝。特征:如果通过nHandle修改matrix属性,也会影响mHandle的matrix属性,因为这两个Handle对象共享了一份matrix数据。
不到万不得已matlab不会在内存中构造一个一模一样的拷贝,这个万不得已就是当nValue中的matrix的值做出改变时,当我们哪怕仅仅修改nValue矩阵中的一个元素的值,Matlab在内存中马上就构造出一个实体的拷贝,然后再修改其中元素的值。
clear all;
mValue=MatrixValue(10000);
nValue=mValue;
nValue.matrix(1,1)=10%仅改变一个值
function assignVar(obj,var)
obj.var=var;
end
aValue.assignVar(20):没有起到作用。
对于Value类对象,参数传递方式是:拷贝必要的信息到函数的工作空间中,当MATLAB发现该函数对传入的参数进行了修改,并且该参数是Value类对象时,MATLAB就构建出了一个局部拷贝,所以对obj.var的修改只是局部的,函数退出时,该拷贝也就消失了。所以如果想把函数内部的修改保存下来。必须把obj当做输出参数返回。assignVar必须改为:
function obj=assignVar(obj,var)
obj.var=var;
end
对于Handle类对象,参数的传递方式也是构造一个局部拷贝对象,但在函数内部,函数修改的不是针对Handle对象的而是Handle对象所指向的数据。所以成功修改了Handle对象所指向的数据。
function assignVar(obj,var)
obj.var=var;
end
bHandle.assignVar(20);
Value类适用于比较简单的数据,如果使用者不在意副本的存在,并且希望每次执行对象的拷贝都可以得到一个独立的副本。如果一个数据在其他地方有副本,并且我们希望修改其中一个副本,其他所有的副本都被修改,可以使用Handle类。
从实用计算的角度来说,如果我们的数据体积比较大,我们希望这些数据在各个方法、函数之间传递迅捷,不需要被局部拷贝,则可以用Handle类来包装数据。
从物理角度看,如果类的对象对应一个独一无二的物理对象,设计成Handle类拷贝的是一个访问他们的通道,设计成Value类则无法解释一个独一无二的个体,在程序上有两个全同的拷贝。
从功能的角度看,Value类没有提供任何内置的方法,Handle类则提供了delete方法等,功能也更加强大。
例如5¥*2=10¥
两张5元换成10元,是一个新的对象,所以设计成Value类。
定义:从对象产生到释放的过程
定义:在对象脱离作用域或者被销毁时,负责收尾工作,比如关闭文件句柄、释放数据所占内存空间等。MATLAB规定:这类方法要命名为delete。无论Value或者Handle都需要用户定义自己的delete函数。
对Handle类对象使用delete将释放该Handle所指向的数据。但是Handle对象还是存在的,可以指向其他的内存数据。
是不是所有局部对象离开作用域之后都会被销毁?
回答:所有用户定义的类的对象和大多数MATLAB内置的类的定义出来的对象,都具有这样的行为,这类对象通常叫做Scoped Object。但有少数MATLAB内置类定义的对象叫做User-Managed Object,离开作用域后,不会自动被销毁。User-Managed,即需要用户指示MATLAB去销毁。例如:timer、analoginput、videoinput等。
面向对象程序异常时,直到MATLAB捕获异常之前,所有try catch所处的函数的堆栈之上的,已经声明的对象,他们的析构函数都会自动调用。
当类的对象占用可一些系统资源,而无法自动释放时,需要自己重载一个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方法没有满足上述中的任意一点,仍可显式调用该函数,但是不能自动调用。
定义:泛指对象内部状态的改变。
在事件发生和出发响应的模型中,通常把改变内部状态的对象叫做发布者,而把监听事件并作出响应的对象叫做观察者。
MATLAB规定:事件的定义要放在event block中。
events
dataChanged
end
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类与具体的各种观察者之间的解耦合。
因为event.EventData类是Handle类,所以任何用户定义的事件类本身也是handle类,因此event还可以用来传递大型数据。假设传递一个500*500的矩阵作为消息,并且有十个观察者在发布者出注册,notify所有观察者的成本仅是构造一个message对象,传递10次handle。因为500*500只存在一个拷贝,且被10个观察者共享。更节省空间的办法是:提供一个内部数据的公共接口给观察者。
因为listener对象本身就是handle对象,所以删除一个listener对象时,只需要调用handle基类提供的delete方法。
在其他路径上使用类的定义,要用addpath命令,把包含在该类的文件夹加到MATLAB搜索路径中去。
addpath(‘Z:’\folder1\folder2’);
p1=Point(1,1);
另一种定义类的方法:在类定义文件中仅仅提供方法的申明,不提供方法的定义,方法的定义放到另一个独立的.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的搜索路径,任何路径都可以使用了。
局部函数不是类的方法,在类定义外部不可见,不能通过obj.method的方式从外部访问。局部函数仅对类定义内部的方法可见。
局部函数对函数参数要求没有限制,不像类的实例方法那样,参数中一定要包含一个对象。所有在同一文件中定义的类方法都能调用局部函数的方法,但那些没有声明的类方法除外。
在classdef外部定义的类的方法也可以拥有自己的局部函数,调用规则和类的局部函数相似。
如果程序结构再复杂一点,可以把各个类进一步组成package。MATLAB规定package文件目录必须以+开头,package中还可以包括各个类的文件夹,各类之间还可以有继承关系。
如果一个类的定义放在package中,使用该类时要在类名前加上Package的名称。
p2=MyPointPackage.Point3D(1,1,1);
如果在程序的开头就用import命令导入整个Package。这样调用package的时候就不需要使用package的名称了。
import MyPointPackage.*;
两个function同名,但是定义不同,一个是普通函数,一个是类AClass的成员方法,这种情况下,调用哪个function将取决于用户程序的调用方法。
MATLAB的package中可以放置普通的函数,如果两个普通的函数,有相同的函数名,一个在当前路径下,一个在package中,且它们的签名是一样的,在main程序中,该函数被调用时,则MATLAB将调用路径上优先级最高的函数。
默认情况下,最直接路径中的函数具有最高优先级。如果用户想要调用的是package中的foo函数,可以先导入整个package。
save filename obj:把对象obj的数据保存到名为filename的mat文件夹中
load matfilename obj:把mat文件中的对象obj装载到工作空间中
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时要保持”一致“,如果不一致程序需要付出一定的代价。
当用户需要扩展或者定制save的行为时,可以在类的定义中重新定义saveobj成员方法,一旦提供了该类的saveobj方法,那么对该对象调用save命令时:save filename obj,MATLAB就会调用该类自己的saveobj方法保存对象。
saveobj返回值是一个struct,saveobj方法把一个object转换成一个struct。struct中field的名称最好和对象属性名称保持一致。
为了保证MAT文件中的数据能够被正确初始化新的对象,用户还需要提供一个配套的loadobj方法。
loadobj成员方大的参数是一个struct,返回值必须是一个对象。
loadobj必须是一个静态方法,因为在调用loadobj时,类的对象还没有被建立起来,所以只能是静态的。loadobj必须返回一个新构成的该类的对象。
saveobj和loadobj应用:可以用他们保存中间计算的结果。比如自洽或者最优化计算,如果计算量大、时间长,只给出一次初始值,然后等很长时间希望其收敛,显然不是最佳的办法,如果计算到一半程序出错,还要重头再来更不方便。这种情况下应该每个一段时间对结果进行一次saveobj保存,下次重新开始时,再使用loadobj利用上次的保存结果作为新的初始值,根据收敛的情况,适当调整自洽或者优化参数让计算继续。
再比如,我们要遍历大量的数据,单次计算的时间不长,但是计算的次数很多,总体计算时间仍很长。这种情况下,我们也应该考虑使用saveobj把单次计算的结果保存下来,对大量的数据做分段遍历。
给每个loadobj成员方法都构造一个中间层,即在每个类中添加一个方法reload。该中间方法只负责赋值,不负责对象创建。这样父类和子类的loadobj就都可以正常工作了。
可以直接给属性添加一个特征修饰词,告诉save命令,哪些属性需要保存,哪些属性不需要保存。这些特征修饰词叫做瞬态。
loadMat文件时,若没有对transient属性做特殊处理,该属性load过后,值为空。Transient属性分配内存,但是不会被保存到MAT文件,Dependent属性不分配内存,不会保存到MAT文件中。
如果希望在load过程中,自动给某些属性赋值,可以使用一个类叫ConstructOnLoad的关键词。如果把一个类声明成ConstructOnLoad=ture,那么在包含该类对象的mat文件被加载时,MATLAB会自动调用该类的缺省的构造函数。
只有在ConstructOnLoad和Transient无法解决问题时,才需要考虑重载函数saveobj和loadobj。
程序由一个主函数和一个FIG文件构成。主函数的GUIDE_GUI_OpeningFcn用来初始化控件的初值,以及balance变量。主函数中有GUIDE自动生成的回调函数的框架,回调函数可以通过handles来访问figure上各个控件对象,做必要的计算和更新。
GUIDE的优点是迅速地构造简单界面,缺点也很明显,当用户界面复杂到一定程度,尤其需要多个界面,并且经常需要修改时,使用主函数+若干子函数,并且用GUIDE界面来布置各个控件对象位置的方法就显得力不从心。
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:负责处理用户输入。
需要定义一个事件,当值变化时,对model类对象发出通知,给监听该事件的listener。model对象和View对象之间是被监听和监听的关系。
model对象和View对象之间的关系是监听和被监听的关系。model的余额的改变将触发balanceChanged事件,View响应并更新balance的值。
职责包括:
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章中给用户自定义类中的事件注册回调函数
obj.modelObj.addlistener('balanceChanged',@obj.updateBalance);%注册
控制器的职责是:让模型和视图解耦,处理来自用户的输入,解释用户和GUI的交互,改变视图类上控件的外观。
注意controller类为了能够调用model对象中的函数withdraw和deposit,必须拥有model对象的handle。
withdraw按钮是MATLAB内置的控件对象,被监听的事件是“按钮被按下”,回调函数是withdraw_callback。该函数定义在controller中,在视图类中的attachToController方法中被注册。withdraw将造成balanceChanged,有一个listener监听该事件,内部的相应函数是视图类中的updateBalance。
如果只有一个视图,那么使用GUIDE或者面向过程式的设计方式就可以迅速地解决问题。否则就要利用MVC模式或者其他面向对象的设计模式。
多视图的GUI程序一个常见问题就是GUI之间共享数据,也就是模型和对象之间如何共享数据。只要对象互相拥有彼此的handle即可。如:
classdef Model1<handle
%MODEL1 此处显示有关此类的摘要
% 此处显示详细说明
properties
hModel2
hModelMain
end
methods
function accessData(obj)
%直接通过hModel2的handle访问hModel2对象的数据
temp=hModel2.someProp;
end
end
end
如果各个模型、视图、控制器之间数据相互关联,都需要彼此的数据,为避免相互依赖耦合严重,可以设计一个Context类,其作用像一个枢纽,要求其他类的对象在创建时,都在这个上下文类中注册,并且给每个对象一个独一无二的ID,当一个对象需要取得其他类的对象的数据时,就可以通过ID到Context类对象处获取其他类的handle。
因为我们希望在任何路径和函数中都可以随时通过Context对象得到所需要的对象的句柄,所以该对象应该是一个全局对象,而且程序只需要这么一个上下文类,这样就需要限制该类所能产生的对象的数量,所以是singleton。
obj1=ModelDevice('Camera');
obj2=ModelDevice('PowerSource');
contextObj=Context.getInstance();%得到全局上下文对象
contextObj.register('Camera',obj1);%注册Camera
contextObj.register('PowerSource',obj2);%注册PowerSource对象
function someFunction( )
contextObj=Context.getInstance();
hCamera=contextObj.getData('Camera');
end
针对含有Menubar、Toolbar和子视图的GUI结构,一种解决办法是:从基本的MVC模型出发,把各个视图用Composite(组合)关系组织起来,把各个控制器类用parent-child关系组织起来。
视图类:MainView可以作为最上层的视图对象容器。子视图对象也可以作为容器。
模型类:只有MainView拥有其他类对象的handle。
控制器类:MainController是最上层的控制器,是其他众Controller的parent。优点在于:底层控制器可以向高层控制器转发其无法处理的请求,响应的处理函数被分层。请求转发:
function button1CallBack(obj)
obj.parentController.handleRequest(RequestID);
end
RequestID用来标记请求的类型,提供给上层的控制器用以查找响应的相应函数。
绝对布局仅适合简单的界面设计,可以把uicontrol的单位的单位设置成normalized,把绝对布局变成相对布局,可以避免重新计算的问题。如果想要按钮在放大Figure时保持原来的大小,须给控件提供一个resize函数,处理鼠标拖拽的相应。
相对布局:
f=figure(‘Menubar’,‘none’,’Toolbar’,’none’,’’Position’,[200,200,100,200]);
uicontrol(‘style’,’pushbutton’,’Units’,’normalized’,’Position’,[0.1 0.4 0.8 0.25],’parent’,f);
作用:用来对控件进行单列纵向布局。
复合布局是指把各个布局管理器对象组合起来,通过多层的parent-Child关系对界面进行更加灵活的设计。
在初始化对象的过程中,如果有参数需要传递给基类Constructor,则需要显式地在子类constructor中调用基类的constructor。
classdef Derived<Base
properties
b
end
methods
function obj=Derived(a,b)
obj=obj@Base(a);
obj.b=b;
end
end
end
如果不需要向Base类的构造函数传递参数,就不需要在Derived的Constructor中显式调用parent的Constructor,MATLAB会隐式地自动帮助用户调用Base类的Constructor。
Base:
classdef Base<handle
methods
function obj=Base()
disp('Base CTOR called');
end
end
end
Derived:
classdef Derived<Base
methods
function obj=Derived()
%matlab在这里先调用Base的构造函数
disp('Derived CTOR called');
end
end
end
注意:让MATLAB隐式地调用基类的缺省的Constructor的前提条件是基类必须定义了缺省的Constructor,或者说基类的Constructor必须能够处理零参数的情况,否则MATLAB会报错。
MATLAB隐式地自动调用的是缺省的Constructor,如果用户自定义的Constructor没有提供nargin==0的情况处理,所以MATLAB将显示无法找到所需要的构造函数的错误。
子类可以重新定义基类的方法。如果子类的foo方法override了基类Base的foo方法,如果声明的是基类的对象,在基类的constructor中调用foo方法一定是来自于基类;如果声明一个子类对象,初始化过程中基类的constructor被调用,而基类constructor中又调用了成员方法foo,matlab将调用子类的foo方法。
matlab方法dispatch的规则是:查找方法的signature,而方法的signature由参数列表中的对象和方法名称决定。
在继承结构下,一个类对象的析构函数调用和构造函数调用顺序相反。用户只需要调用子类的delete函数,整个对象在matlab内所占的内存空间就会得到释放,MATLAB会帮用户在子类的delete函数的末尾扩充,调用基类delete函数命令,所以用户不需要在子类的delete函数中显式调用基类的delete函数,且此时忽略delete方法的权限。
包含一个以上的父类的继承。
classdef Derived
先调用爷爷,然后大伯二伯。
如果Base1的属性a是public的,则Base2的属性a设置成private的,如果声明一个Derived类的对象,其属性a的初始值来自Base1.
1、基类中至少有一个方法是private方法
2、用户可以在Derived类中提供另一个同名方法foo,这个方法将覆盖Base1和Base2中的foo方法,从而消除模棱两可的可能性。
定义:一个基类在继承的层次中多次出现。
实际中,可能同时需要重用Value类和handle类中的代码,可以使用value类的关键词HandleCompatible。
classdef(HandleCompatible) BaseV
end
注意:虽然使用了关键词,但是BaseV仍然是Value类。
给Value类注明HandleCompatible,就可以和Handle类一起做父类了,子类为handle类;给Value类注明HandleCompatible,value类一起做父类,子类为value类,但不是HandleCompatible;但是一般不这么做,因为没有意义。
使用关键词sealed
classdef (sealed) A
end
另一个办法就是把构造函数声明成private的,由于子类对象的建立必须要访问父类的构造函数,而private的构造函数将禁止子类的访问,所以该错误会出现在运行时,从而达到禁止继承的目的。
Base和Derived都定义了foo方法,声明Derived对象,调用该对象的foo方法,实际调用的是Derived的方法,父类的foo方法被覆盖了。
可以利用foo@Base(obj)调用父类方法,但是只能调用直接的父类的方法。然后可以进行扩充。
当基类的作者要确保该方法不被覆盖时,可以在基类方法中使用Sealed关键词。
也叫作类方法,它为类服务,最明显的特征就是不需要对象就能使用。因为类的Constant Property同样也为类服务,而不属于某个对象,所以静态方法可以访问类的Constant Property。类中的普通方法可以访问静态方法,只需要在方法前面加上类名。
因为静态方法没有把对象当做参数,所以定义类的静态方法既不能访问对象的一般属性,又不能调用类的一般方法。可以访问Constant Property。
各个对象之间共享数据。并且在对象的生存周期中,共享变量的值不变。
如果在对象的生存周期中,共享变量的值不变,那么就可以把该属性声明成Constant,此时该属性被类的所有对象共有,内存中该Constant属性只有一个。
如果要让类的各个对象共享变量,可以把该对象定义为静态成员方法中的persistent变量。比如可以用一个persistent变量计算对象创造的数量。
为什么MATLAB面向对象语言中不提供static变量?
C++和Java中,让一个类共享数据一般是通过static变量来实现的,但是MATLAB面向对象并没有。原因是:MATLAB长久以来的编程惯例是,在赋值时,变量比方法和类具有更高的优先级。如果添加了这个新功能,将造成向后不兼容,所以MATLAB不支持static变量。
定义:不能被实例化出对象的类。使用关键词Abstract来定义。
classdef
Shape<handle
methods(Abstract)
draw(obj)
end
end
定义抽象方法,只需要一行声明,不需要具体代码,并且该类的子类包括有一个同名的非抽象的办法。不能定义为Sealed类。
子类实现办法不尽相同,所以在抽象类中只声明,不定义,把定义留到具体的子类中完成。
抽象类可以有构造函数,只是不能利用这个构造函数声明出对象来,该构造函数一般被子类构造函数所调用,需要显式调用。构造函数不是抽象的。
抽象方法声明中参数个数不是一个严格的限制。子类只需要实现同名方法即可,参数的数目不一定要和父类中一致。
抽象类的子类必须实现抽象类中定义的所有抽象办法,否则,该子类仍然是抽象类。
串接:使用方括号[]把已有的变量组合起来,使其成为一个数组。使用这种方式构造的对象数组,适用于对象数量比较少的情况。对objArray中对象元素的访问和普通数组中元素的访问是一样的。在此基础上使用点+属性名称的语法,可以访问对象元素的属性。
objArray=[b1 b2 b3];
objArray(1).a=10;
自动生成一个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)运算符。
首先使用串接语法,构造一个对象数组。然后使用逻辑‘-and’或者‘-or’关键词,在findobj函数中查找指定对象。
MATLAB规定对象数组中元素的种类必须保持一致,如果不一致,matlab就会尝试把一个对象转换成另一个,如果找不到可以使用的对象转换函数,MATLAB就会报错。最简单的解决办法就是使用元胞数组(Cell Array),元胞数组是MATLAB中专门用来存放不同种类数据的工具。例如:
o1=Square();
o2=Circle();
oCell={o1,o2}
oCell{1}.a%访问CellArray中的一个元素
使用Cell取代array,简单实用,但是没有办法利用向量化式地方式集中访问数组中对象内部的元素。
定义:一种负责把该类的对象转换成其他类的对象的一种类的方法。
只需要在该类中定义转换函数,则调用的时候就能完成转换。转换函数只需要调用转换对象的Constructor。
转换函数一般是隐式转换。假设数组的第一个为Square对象,则第二个赋值时需要检查,如果不相同,则需调用第二个对象的转换函数。如果没有定义转换函数,MATLAB会直接把第二个对象作为参数提供给square的构造函数。如果无法处理,MATLAB会报错。
转换函数把不同类型的对象组合到一起,并且不改变对象的类型。
使用Heterogeneous需要两个类具有共同的父类,并且父类继承自一个叫做Heterogeneous的基类。
基类声明:
classdef Shape2D
end
优点:
+ 不用定义convert函数,不存在对象之间的相互转换;
+ 构造出的数组可以存放共同基类的对象;
+ 可以使用数组的下标语法,向量化访问对象的共同特征;
+ 可以使用对象的共同方法,必须是Sealed。
包含有不同对象的数组类型,总是取对象们最近的共同父类。
1、没有除Heterogeneous之外的共同的父类。
2、多重继承存在交叉继承,这时候也不能将子类的对象放到一个对象数组中去。
如果用Dot语法访问数组中对象的共同属性,返回的结果将是一个用都好分隔的list。
s=objArray.area
objArray被当做一个参数传入area方法中。
如果是HeterogeneousArray,想要支持向量化访问的成员方法,必须将其声明成sealed,禁止子类中有不一致的定义。
对象数组是否可以向量化调用destructor?
destructor禁止定义成Sealed,否则子类资源可能无法释放。但是MATLAB对delete方法仍支持使用objArray.delete的格式调用对象们的析构函数。MATLAB并没有向量化地调用对象们的析构函数,因为根本不存在这样一个共同的delete方法。objArray.delete这里为:逐个调用每个对象的析构函数。
A(1:2,:)会被MATLAB解释器转换成一个subsref的函数调用。该函数的第一个参数是要访问的数据A;第二个参数是要访问元素所在的位置,并且该位置信息存放在一个结构体中。subsref(A,s);例如A(1:2,:),这里s.type=’()’;s.subs={1:2,’:’};也可以通过以下方法访问数组元素:
s.type=’()’;
s.subs={1:2,’:’};
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);
对元胞数组B第一列的访问B{:,1}将会被Interpreter转换成如下的subsref函数调用:
s.type=’{}’;
s.subs={‘:’,1};
subsref(B,s);
对元胞数组B的第一个元胞的赋值:
B{1,1}=0;将被MATLAB转换成如下subsasgn函数调用。
s.type=’{}’;
s.subs={1,1};
subsasgn(B,s,0);
对struct的访问和赋值也可以直接通过直接调用内置函数来完成。
days.f1和以下等价
s.type=’.’;
s.subs={‘f1’};
subsref(days,s);
days.f3=’NULL’ 和以下等价
s.type=’.’;
s.subs={‘f3’};
subsref(days,s,‘NULL’);
数组、矩阵、元胞数组、对象都支持上面的等价调用。形如subsref(obj,s)的调用,优先调用用户自定义的方法。
优先调用用户自定义的方法。
重载的时候需要返回obj.matrix和obj.cell因为该重载函数的任务是修改对象的属性,但是obj.matrix和obj.cell不是handle对象,所以在函数内部的修改仅仅是局部修改,必须将它传回来。
重载subsasgn和subsref,将会重载该类的属性访问权限,使用时要慎重。
注意:使用自定义的下标运算符取代内置定义的下边运算符应当是一个方便编程的手段,不应该使用在对性能要求很高的程序中。
MATLAB中+默认是做算数运算,所以在内部会把string转换成数字,调用plus函数。
对于形如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对象。
handle对象,==运算时在handle中就定义好的算法,用于检查handle类所指向的实际数据时同一个。handle对象的拷贝是浅拷贝,只复制了类中的地址,没有复制类中实际指向的数据对象。
Value类数据,没有定义‘==’运算符,需要用户自己指定行为。
仿函数:使普通函数具有类的功能。
定义:用一种普遍的方式来描述类,即用类的方式来描述类。
第一种得到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);
如何系统地获得类中所有property的名字?
metaobj=?Derived;
propNameList={metaobj.PropertyList.Name}
+ metaobj.PropertyList是一个对象数组,其中的内容是meta.property的对象
+ 使用.语法可以向量化访问数组中对象的共同属性
+ metaobj.PropertyList.Name返回的结果是string类型,由于string的长度不同,使用{}把返回的结果收集到元胞数组中去。
classdef Ref<handle
properties
a
bobj
end
methods
function obj=Ref()
obj.a=rand(1);
obj.bobj=BHandle;
end
function newobj=clone(obj)
newobj=Ref();
metaobj=metaclass(obj);%得到meta Object
props={metaobj.PropertyList.Name};%得到props名字
for j=1:length(props)
tmpProp=obj.(props{j});
if(isa(tmpProp,'handle'))%调用该类的Prop方法
newobj.(props{j})=tmpProp.clone();
else
newobj.(props{j})=tmpProp;
end
end
end
end
end
Bhandle:
classdef BHandle<handle
properties
var
end
methods
function obj=BHandle()
obj.var=rand(1);
end
function newobj=clone(obj)
newobj=BHandle();
metaobj=metaclass(obj);%得到meta Object
props={metaobj.PropertyList.Name};%得到props名字
for j=1:length(props)
tmpProp=obj.(props{j});
if(isa(tmpProp,'handle'))
newobj.(props{j})=tmpProp.clone();%程序不会运行到这里
else
newobj.(props{j})=tmpProp;
end
end
end
end
end
MATLAB提供mixin类的Copyable,其中包括copy和copyElement两个方法帮助用户完成基本的handle类对象的深拷贝。copy方法是sealed,不允许子类重载,copyElement是protected,允许子类重载。用户只需要让自己的类继承自matlab.mixin.Copyable即可,此时仍为handle类。和前面类似,copy和copyElement组合在一起也是自动遍历的对象中的各个属性做拷贝。
clear all;clc;
record1=StudentRecord();
record1.name='A';
record2=copy(record1);
record2.name='B';
record1.name
record2.name
copyable类中的方法默认的并不包括对属性做递归的深拷贝,如果对象有一个handle对象,使用copy方法进行对象拷贝时,该对象被浅拷贝。
用户可以重载copyElement方法。先在copyElement中调用父类的copyElement方法,得到新的StudentRecord对象,这时homework属性是浅拷贝。然后对其中的homework属性做完全的复制。
一个类最好只有一个引起它变化的因素。
UML,实心菱形表示非包括不可的组合关系;空心菱形表示松散的可有可无的组合关系,也叫聚集。
程序的设计应该对修改是封闭的,对扩展是开放的。
使用组合可以让系统有更大的弹性,不仅可以将算法封装成类,还可以在运行时动态地改变对象的行为。
上层模块通常是包含抽象方法的抽象类,而继承他们的子类要提供这些方法的实现。通常这些子类叫做对接口的实现。
简单工厂模式:对象的产生细节由一个特定的类负责,并且该类中包含了必要的逻辑判断以产生不同的对象。
工厂模式的关键在于具体的对象的创建时机推迟到工厂子类中完成。不希望高层的模块和具体的硬件细节打交道,所以把产生这个对象的工作交给工厂模式去完成。
定义一个创建对象的接口,让子类决定实例化哪个类。factory模式使一个类的实例化延迟到其子类。
对于缺省的Constructor可以:
function obj=createObj(classname)
obj=eval(classname);
end
test:createObj(‘Sub1’);
否则对于如下的Constructor:
classdef Sub1<handle
properties
a
end
methods
function obi=Sub1(var)
obj.a=var;
end
end
end
可以使用strcat构造要执行的命令的字符,然后再用eval函数执行命令。
test:
classname=’Sub1’;
cmd=strcat(classname,’(’,’10’,’)’);
obj=eval(cmd);
或者使用str2func函数,从classname处获得类的构造函数的句柄,然后像正常使用构造函数那样调用该函数句柄。
classname=’Sub1’;
ConstructHandle=str2func(classname);
obj=ConstructHandle(3);
最后的产品是各种面条的聚集。
提供一个创建一系列相关或者相互依赖的对象的接口,而无需指定它们具体的类。
1. 模式结构
MATLAB工程科学计算时,在过程中输出一些结果,用来调试程序。一般记录中间结果的文件叫做LOG。在程序运行期间,都能往LOG中写入数据,并且程序运行期间有且只有一个LOG。
单例Singleton模式:该模式用来控制一个类所产生的对象的数量。
classdef MyClass<handle
methods(Access=private)%私有构造函数
function obj=MyClass()
disp('constructor called');
end
end
methods(Static)
function obj=getInstance()%静态接口方法
persistent localObj
if isempty(localObj)||~isvalid(localObj)%如果localObj不存在则创建
localObj=MyClass();
end
obj=localObj;%如果localObj已存在则返回
end
end
end
obj1=MyClass.getInstance();
obj2=MyClass.getInstance();
obj3=MyClass.getInstance();
这是一个Handle类,所以构造出来的obj1,obj2,obj3实际指向同一个实例。
classdef LogClass<handle
properties
FID
end
methods(Access=private)%私有构造函数
function obj=LogClass()%打开文件
obj.FID=fopen('logfile.txt','a');
end
end
methods
function delete(obj)%关闭文件
fclose(obj.FID);
end
function print(obj,string)
fprintf(obj.FID,string);
end
end
methods(Static)%控制外部访问
function obj=getInstance()%静态接口方法
persistent localObj
if isempty(localObj)||~isvalid(localObj)%如果localObj不存在则创建
localObj=LogClass();
end
obj=localObj;%如果localObj已存在则返回
end
end
end
将一个复杂对象的构建与它的表示方法分离,使得同样的构建过程可以创建不同的表示。
1. Builder模式结构
Director、Builder、ConcreteBuilder、Product、Part
2. 类之间的协作
+ Client(外部程序)负责构造Director对象,并且设定该Director所要指导的具体Builder。
+ Director拥有Builder对象,并且可以替换。
+ Builder拥有product对象,Product对象由parts对象组成
+ 构造产品的请求发自于Director,Builder接到请求之后按照Director所指导的顺序把parts对象添加到产品中去
3. 何时使用Builder
+ 当构造过程中允许被构造的对象有不同的表示时。
+ 当创建复杂对象的算法,要独立于该地对象的装配方式时。
这种设计对修改是封闭的,还支持多次装饰,对扩展是开放的。
动态地给对象添加一些额外的职责,就增加功能来说,Decorator模式相比生成子类更为灵活,Decorator模式也叫做包装器。
1. 装饰者模式结构
Component、ConcreteComp1、ConcreteComp2、Decorator、ConcreteDeco1、ConcreteDeco2
何时使用Decorator模式
Decorator模式可以避免通过创建子类来扩展类的功能,Decorator是以动态的方式给单个对象添加新的功能。想要扩展类,又想避免子类数量爆炸时,可以考虑使用Decorator模式。
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知,并且自动被更新。
1. 模式结构
Subject、ConcreteSubject、Observer、ConcreteObserver
files=dir(c:\datafolder);
files=files(cell2mat({files(:).isdir})~=1);%去除文件夹中的目录
for i=1:length(files)
inputname=file(i).name;
imgObj=Image(inputname);
imgObj.doMethod();
end
Iterator(内部)的意图是用一种方法顺序去访问一个聚集对象中的各个元素,而又不用暴露对象的内部显示。
1. Iterator模式结构
Aggregator、ConcreteAggregator1、ConcreteAggregator2、Iterator、ConcreteIterator1、ConcreteIterator2
状态模式:允许对象修改内部状态是改变它的行为,对象看起来好像是修改了它的类。
1. State模式的结构
Context、State、ConcreteState1、ConcreteState2
模板方法模式:定义一个操作中的算法的骨架,将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可以重新定义该算法的某些特殊的步骤。
1. Template模式的结构
AbstractClass、ConcreteClass1、ConcreteClass2。Template模式中,含有反向控制的结构,因为通常是子类调用父类的操作,而在这个模式里,却是父类调用子类。这种结构也被叫做好莱坞模式。
Map:http://blog.sina.com.cn/s/blog_6163bdeb0100rdx3.html
http://blog.sina.com.cn/s/blog_4a0824490102vamk.html
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样,以后可以将该对象复原到原先的状态。
1、 memento模式结构
2、 类之间的协作
+ Originator是拥有状态的对象,外部的命令向originator发出一个请求,要求保存备忘录。
+ originator把自身状态数据封装到一个memento对象中,并且提交给caretaker保存。
+ 只有Originator知道该如何利用Memento对象中的数据,Caretaker的工作仅仅是保存各个Memento对象,不能对备忘录的内容进行操作。