1. 程式人生 > >Matlab面向物件程式設計的兩種方式

Matlab面向物件程式設計的兩種方式

Matlab支援面向物件程式設計,主要有兩種方式,一種是利用class命令,一種是利用classdef關鍵字。Octave(一種開源科學計算程式,可視為Matlab的替代品)目前只支援第一種方式,對classdef暫不支援。下面對這兩種程式設計方式做簡單介紹。

1. 利用class命令建立類

建立一個@class形式的資料夾,其中class代表要實現的類的名稱。假定需要建立一個名為point的類,可以建立一個名為@point的資料夾:

mkdir @point

之後,資料夾@point下定義的函式會被視為point類的成員函式。主要包括:

  1. point.m
    建構函式。這是一個與類名稱同名的函式。
  2. get.m
    用於獲取類point的屬性。
  3. set.m
    用於設定類point的屬性。
  4. display.m
    用於控制類的顯示字串。
  5. disp.m
    同display.m,但是比display更加高階,在disp.m中會呼叫display.m。因而display.m可以實現對顯示更加精細的控制。
  6. 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。

這裡需要注意兩點:

  1. 只能在成員函式中對class的屬性進行access,這也就是為什麼要定義get和set的原因。通過point.x或者point.y的方式來訪問類的屬性會報錯。
  2. 利用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類是所有類的基類。

此外,methodsproperties語句塊還可以利用更多的描述符控制其訪問級別,從而使得類能夠支援公共屬性,私有屬性,公共方法,私有方法,靜態方法等特性。關於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)