因为简书字数限制,完整版地址:https://www.zybuluo.com/hainingwyx/note/609905
如有错误,烦请指正!
第11章 对象数组
11.1 如何把对象串接成数组
串接:使用方括号[]把已有的变量组合起来,使其成为一个数组。使用这种方式构造的对象数组,适用于对象数量比较少的情况。对objArray中对象元素的访问和普通数组中元素的访问是一样的。在此基础上使用点+属性名称的语法,可以访问对象元素的属性。
objArray=[b1 b2 b3];
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中专门用来存放不同种类数据的工具。例如:
o1=Square();
o2=Circle();
oCell={o1,o2}
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<handle&matlab.mixin. Heterogeneous
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,’:’};也可以通过以下方法访问数组元素:
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);
12.1.2 MATLAB如何处理形如a{1,:}的表达式
对元胞数组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);
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 什么情况下重载下标运算符
- 当用户想完全禁止通过dot来访问对象内部属性时,可以重载subsref函数。
- 当用户想构造一个对象,使其行为看上去像函数 一样,可以重载subsref函数。
- 当用户想在赋值对象数组时,做更多的检查和限制,可以重载subsasgn函数。
- 让一个标量对象行为像矢量一样。
注意:使用自定义的下标运算符取代内置定义的下边运算符应当是一个方便编程的手段,不应该使用在对性能要求很高的程序中。
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 如何手动克隆一个对象
- handle类的浅拷贝
对象的属性相互关联,不独立,俗称浅拷贝。 - 简单克隆
优点:实现简单。
缺点:如果要克隆有很多的属性,那么一个一个键入属性会很麻烦。
方法1:构造一个方法,先在该方法中声明一个新的对象,叫做newObj,再把旧的对象每一个属性的值都复制到新的对象newObj中,最后在将该newObj返回。
方法2:用meta.class函数,先获得该类的metaObject,然后取出其中的propertyList中属性的名字,然后遍历赋值即可。
该方法中newobj.(props{j})=obj.(props{j})有一个隐藏的前提,所有的property都是Value类型的对象,如果有一个property是handle类型的,则得到的拷贝仍然是浅拷贝。 - 递归克隆
为了解决仍然是浅拷贝的问题,用户需要提供两个类的clone函数
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
13.5 如何使用matlab.mixin.Copyable自动克隆一个对象
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属性做完全的复制。
第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可以:
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);
15.1.5 抽象工厂
最后的产品是各种面条的聚集。
15.1.6 AbstractFactory模式总结
提供一个创建一系列相关或者相互依赖的对象的接口,而无需指定它们具体的类。
-
模式结构
类之间的协作
在运行时,client将负责创建一个ConcreteFactory类的实例,这个具体的工厂具有创建不同对象的代码。Client可以更换具体的工厂以得到不同的具体的产品。何时使用AbstractFactory模式
当一个系统要独立于它的产品创建、组合、表示时。
当一个系统需要多个产品系列中的一个来配置时。
当需要强调一系列相关产品的设计时,以便进行联合调用。
15.2 单例模式:给工程计算添加一个LOG文件
15.2.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实际指向同一个实例。
15.2.2 应用:如何包装一个对象供全局使用
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
15.3 建造者模式:如何用MATLAB构造一辆自行车
15.3.1 问题的提出
15.3.2 应用:Builder模式为大规模计算做准备工作
15.3.3Builder模式总结
将一个复杂对象的构建与它的表示方法分离,使得同样的构建过程可以创建不同的表示。
- Builder模式结构
Director、Builder、ConcreteBuilder、Product、Part
- 类之间的协作
- Client(外部程序)负责构造Director对象,并且设定该Director所要指导的具体Builder。
- Director拥有Builder对象,并且可以替换。
- Builder拥有product对象,Product对象由parts对象组成
- 构造产品的请求发自于Director,Builder接到请求之后按照Director所指导的顺序把parts对象添加到产品中去
- 何时使用Builder
- 当构造过程中允许被构造的对象有不同的表示时。
- 当创建复杂对象的算法,要独立于该地对象的装配方式时。
第16章 构造型模式
16.1 装饰者模式:动态地给对象添加额外的职责
16.1.1 装饰者模式的引入
16.1.2 面馆菜单代码
这种设计对修改是封闭的,还支持多次装饰,对扩展是开放的。
16.1.3 装饰者模式总结
动态地给对象添加一些额外的职责,就增加功能来说,Decorator模式相比生成子类更为灵活,Decorator模式也叫做包装器。
-
装饰者模式结构
Component、ConcreteComp1、ConcreteComp2、Decorator、ConcreteDeco1、ConcreteDeco2
类之间的协作
Concrete Component和Concrete Decorator都有相同的基类,并且Decorator基类中的compHandle被用来指向何时使用Decorator模式
Decorator模式可以避免通过创建子类来扩展类的功能,Decorator是以动态的方式给单个对象添加新的功能。想要扩展类,又想避免子类数量爆炸时,可以考虑使用Decorator模式。
第17章 行为模式
17.1 观察者模式:用MATLAB实现观察者模式
17.1.1 发布和订阅的基本模型
17.1.2 订阅者查询发布者的状态
17.1.3 把发布者和订阅者抽象出来
17.1.4 Observer模式总结
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知,并且自动被更新。
-
模式结构
Subject、ConcreteSubject、Observer、ConcreteObserver
类之间的协作
- 具体的观察者向ConcreteSubject对象发出订阅的请求,该ConcreteSubject对象把具体观察者加到其内部的一个列表中。
- 当ConcreteSubject内部发生改变,需要通知其观察者时,它将遍历其内部的观察者的列表,依次调用这些观察者的update方法。
- 观察者得到更新的通知,它可以向ConcreteSubject对象提出查询请求。
17.2 策略模式:分离图像数据和图像处理算法
17.2.1 问题的提出
17.2.2 应用:更复杂的分离数据和算法的例子
17.2.3 Strategy模式总结
- 模式结构
Context、Strategy、StrategyA、StrategyB、StrategyC
- 类之间的协作
- 在Context对象中存放数据,而Strategy对象中存放算法,Context对象可以选择所需要的算法。
- Context对象把来自外界的计算请求转交给Strategy对象。
- 转交请求是,Context把自己作为一个参数传递给Strategy对象,以提供Strategy对象计算所需要的数据。
- Strategy模式何时使用
Strategy模式是用来封装算法的,在实践中,可以用来封装几乎任何类型的规则。如果在分析过程中,需要在不同的情况下应用不同的算法,就可以考虑使用Strategy模式来处理问题。
17.3 遍历者模式:工程科学计算中如何遍历大量数据
17.3.1 问题的提出
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
17.3.2 聚集(Aggregator)和遍历者(Iterator)
17.3.3 Iterator模式总结
Iterator(内部)的意图是用一种方法顺序去访问一个聚集对象中的各个元素,而又不用暴露对象的内部显示。
- Iterator模式结构
Aggregator、ConcreteAggregator1、ConcreteAggregator2、Iterator、ConcreteIterator1、ConcreteIterator2
- 类之间的协作
- 因为遍历的方式和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模式总结
状态模式:允许对象修改内部状态是改变它的行为,对象看起来好像是修改了它的类。
-
State模式的结构
Context、State、ConcreteState1、ConcreteState2
类之间的协作
对于来自外界的和自身状态相关的请求,Context对象将这些请求委托给ConcreteState对象处理。
ConcreteStata对象可以访问Context对象,也可以改变Context对象的内部状态。这就要求:在request方法中,Context对象必须把自己作为一个参数传递给ConcreteState对象。
使用Context类的client不需要和State的对象打交道。何时使用State模式
- 当对象的行为取决于它的状态,并且该对象在运行时会改变状态。也就是说对象的行为会在运行时改变,可以考虑使用State模式。
- 当一个操作中含有庞大的多分支条件语句,而且这些分支语句依赖于对象的状态时,可以考虑使用State模式将每个条件分支放入一个独立的类中,而对象的状态对应于拥有一个状态类对象。
17.5 模板模式:下面条和煮水饺有什么共同之处
17.5.1 抽象下面条和煮水饺的过程
17.5.2 应用:把策略和模板模式结合起来
17.5.3 Template模式总结
模板方法模式:定义一个操作中的算法的骨架,将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可以重新定义该算法的某些特殊的步骤。
- 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模式总结
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样,以后可以将该对象复原到原先的状态。
-
memento模式结构
- 类之间的协作
- Originator是拥有状态的对象,外部的命令向originator发出一个请求,要求保存备忘录。
- originator把自身状态数据封装到一个memento对象中,并且提交给caretaker保存。
- 只有Originator知道该如何利用Memento对象中的数据,Caretaker的工作仅仅是保存各个Memento对象,不能对备忘录的内容进行操作。