Matlab面向物件程式設計的兩種方式
Matlab支援面向物件程式設計,主要有兩種方式,一種是利用class命令,一種是利用classdef關鍵字。Octave(一種開源科學計算程式,可視為Matlab的替代品)目前只支援第一種方式,對classdef暫不支援。下面對這兩種程式設計方式做簡單介紹。
1. 利用class命令建立類
建立一個@class形式的資料夾,其中class代表要實現的類的名稱。假定需要建立一個名為point的類,可以建立一個名為@point的資料夾:
mkdir @point
之後,資料夾@point下定義的函式會被視為point類的成員函式。主要包括:
- point.m
建構函式。這是一個與類名稱同名的函式。 - get.m
用於獲取類point的屬性。 - set.m
用於設定類point的屬性。 - display.m
用於控制類的顯示字串。 - disp.m
同display.m,但是比display更加高階,在disp.m中會呼叫display.m。因而display.m可以實現對顯示更加精細的控制。 - move.m
可以是任何使用者自定義函式。
以上列出的幾類函式中,只有建構函式是必需的。但是,由於一般面向物件程式設計中都會涉及對屬性的訪問(讀取和設定),所以大多數情況下也會實現get.m和set.m。使用者自定義函式根據不同的應用場景,可以有也可以沒有,而且可以有多個使用者自定義成員函式。
以下給出一份示例程式碼:
% point.m
% 建構函式
function obj = point(x, y)
persistent id_
if (isempty(id_)); id_ = 0; end
d.id = id_;
d.x = x;
d.y = y;
obj = class(d, 'point');
id_ = id_ + 1;
end
% get.m
% 屬性獲取函式
function val = get(obj, name)
switch name
case 'x'
val = obj.x;
case 'y'
val = obj. y;
case 'id'
val = obj.id;
otherwise
error(['Invalid property: ' name]);
end
end
% set.m
% 屬性設定函式
function obj = set(obj, varargin)
n = numel(varargin);
assert(rem(n, 2) == 0)
i = 1;
while i < n
name = varargin{i};
val = varargin{i+1};
switch name
case 'x'
obj.x = val;
case 'y'
obj.y = val;
case 'id'
error('set private field "id" is not allowed');
otherwise
error(['Invalid property: ' name]);
end
i = i + 2;
end
end
% display.m
% 顯示函式
function display(obj)
fprintf(1, 'point class (id = %d):\n', obj.id);
fprintf(1, 'x = %d\n', obj.x);
fprintf(1, 'y = %d\n', obj.y);
end
% move.m
% 使用者自定義函式
function obj = move(obj, varargin)
assert(numel(varargin) >= 1);
if (isstruct(varargin{1}))
% move(obj, s)
s = varargin{1};
assert(all(isfield(s, {'x', 'y'})));
obj.x = s.x;
obj.y = s.y;
else
% move(obj, x, y)
assert(numel(varargin) >= 2);
x = varargin{1};
y = varargin{2};
obj.x = x;
obj.y = y;
end
end
以上的程式碼具有一定的代表性。point函式中利用class命令建立了一個point類。它包含兩個public屬性x和y,還有一個只讀屬性id(從get.m和set.m可以看出來:在get.m中可以獲取id的值,而在set.m中無法設定id的值)。此外,我們還定義了一個使用者自定義方法move。
這裡需要注意兩點:
- 只能在成員函式中對class的屬性進行access,這也就是為什麼要定義get和set的原因。通過point.x或者point.y的方式來訪問類的屬性會報錯。
- 利用class方式生成的類只能通過函式方式呼叫其成員函式,而無法通過
.
操作符對成員函式進行呼叫。例如,呼叫move函式:
move(point, 2, 3)
而
point.move(2, 3)
則是非法的。
2. 利用classdef關鍵字建立類
classdef是Matlab中用於建立類的關鍵字。其基本結構為
classdef classname
properties
PropName
end
methods
methodName
end
events
EventName
end
end
其中properties用於定義類的屬性,methods定義類的成員函式,events塊定義類的事件。
classdef支援類的繼承,通過<
操作符進行說明,多個父類中間用&
分隔。其基本語法為:
classdef classname [< [superclass1] & [superclass2]]
...
end
在Matlab OOP中,handle類是所有類的基類。
此外,methods
和properties
語句塊還可以利用更多的描述符控制其訪問級別,從而使得類能夠支援公共屬性,私有屬性,公共方法,私有方法,靜態方法等特性。關於classdef的更多細節請參考Matlab文件或者網上資料。
這裡給出一個利用classdef實現第1節中point類的例子:
classdef point
properties
x
y
end
properties (SetAccess = private)
id
end
methods
function self = point(x, y)
persistent id_
if (isempty(id_)); id_ = 0; end
self.id = id_;
self.x = x;
self.y = y;
id_ = id_ + 1;
end
function display(self)
fprintf(1, 'point class:\n');
fprintf(1, 'x = %d\n', self.x);
fprintf(1, 'y = %d\n', self.y);
end
function move(self, varargin)
assert(numel(varargin) >= 1);
if (isstruct(varargin{1}))
% move(obj, s)
s = varargin{1};
assert(all(isfield(s, {'x', 'y'})));
self.x = s.x;
self.y = s.y;
else
% move(obj, x, y)
assert(numel(varargin) >= 2);
x = varargin{1};
y = varargin{2};
self.x = x;
self.y = y;
end
end
end
end
上面的程式碼中,x和y為point類的public屬性,id為私有屬性。classdef建立的類支援.
操作符。可以直接通過.
訪問類的屬性和呼叫類的成員函式。所以不需要額外編寫get函式和set函式,直接通過point.x即可獲取類的屬性,通過point.y=1即可完成對屬性的設定。可見這種方式使用起來略微方便。
目前Matlab對classdef方式的支援還不是很完善,有些應用場景下使用Matlab面向物件程式設計在效能方面可能會有些損失;但是,面向物件程式設計方式會使程式碼結構變得比較清晰,程式內部邏輯更容易讓人理解。所以需要根據實際應用場景合理做出選擇(參考Matlab面向物件程式設計是否值得大量使用?)。由於Octave目前還不支援classdef關鍵字,為了保證程式碼的可移植性,不建議採用這種方式。
最後,貼出測試兩種程式設計方式的測試程式碼:
clear all; clc;
%% Test case 1: class
clear all;
% test constructor
point = point(1, 2);
% test getter
x = get(point, 'x')
y = get(point, 'y')
id = get(point, 'id')
% get(point, 'xx') % this should issue an error
% test setter
% set(point, 'x') % this should issue an error
set(point, 'x', 2)
set(point, 'x', 2, 'y', 3)
set(point, 'y', 4, 'x', 5)
% set(point, 'id', 1) % this should issue an error
% test display
point
display(point)
disp(point)
% test user-defined function
move(point, 4, 5)
display(point)
%% Test case 2: classdef
clear all;
% test constructor
point = point.point(1, 2);
% test getter
x = point.x
y = point.y
id = point.id
% point.xx % this should issue an error
% test setter
point.x = 2
point.y = 3
% point.id = 1 % this should issue an error
% test display
point
display(point)
disp(point)
% test user-defined function
point.move(4, 5)
display(point)