MATLAB Language
Programmazione orientata agli oggetti
Ricerca…
Definire una classe
Una classe può essere definita usando classdef
in un file .m
con lo stesso nome della classe. Il file può contenere il classdef
... blocco end
e funzioni locali da utilizzare nei metodi di classe.
La definizione di classe MATLAB più generale ha la seguente struttura:
classdef (ClassAttribute = expression, ...) ClassName < ParentClass1 & ParentClass2 & ...
properties (PropertyAttributes)
PropertyName
end
methods (MethodAttributes)
function obj = methodName(obj,arg2,...)
...
end
end
events (EventAttributes)
EventName
end
enumeration
EnumName
end
end
Documentazione MATLAB: attributi di classe , attributi di proprietà , attributi di metodo , attributi di eventi , restrizioni di classe di enumerazione .
Classe di esempio:
Una classe chiamata Car
può essere definita nel file Car.m
as
classdef Car < handle % handle class so properties persist
properties
make
model
mileage = 0;
end
methods
function obj = Car(make, model)
obj.make = make;
obj.model = model;
end
function drive(obj, milesDriven)
obj.mileage = obj.mileage + milesDriven;
end
end
end
Si noti che il costruttore è un metodo con lo stesso nome della classe. <Un costruttore è un metodo speciale di una classe o di una struttura in programmazione orientata agli oggetti che inizializza un oggetto di quel tipo. Un costruttore è un metodo di istanza che di solito ha lo stesso nome della classe e può essere utilizzato per impostare i valori dei membri di un oggetto, sia a valori predefiniti che a valori definiti dall'utente.>
Un'istanza di questa classe può essere creata chiamando il costruttore;
>> myCar = Car('Ford', 'Mustang'); //creating an instance of car class
Chiamando il metodo di drive
aumenterà il chilometraggio
>> myCar.mileage
ans =
0
>> myCar.drive(450);
>> myCar.mileage
ans =
450
Classi Value vs Handle
Le classi in MATLAB sono divise in due categorie principali: classi di valore e classi di handle. La principale differenza è che quando si copia un'istanza di una classe di valore, i dati sottostanti vengono copiati nella nuova istanza, mentre per le classi di handle la nuova istanza punta ai dati originali e la modifica dei valori nella nuova istanza li modifica nell'originale. Una classe può essere definita come un handle ereditando dalla classe handle
.
classdef valueClass
properties
data
end
end
e
classdef handleClass < handle
properties
data
end
end
poi
>> v1 = valueClass;
>> v1.data = 5;
>> v2 = v1;
>> v2.data = 7;
>> v1.data
ans =
5
>> h1 = handleClass;
>> h1.data = 5;
>> h2 = h1;
>> h2.data = 7;
>> h1.data
ans =
7
Ereditando da classi e classi astratte
Dichiarazione di non responsabilità: gli esempi presentati qui hanno lo scopo di mostrare l'uso di classi astratte ed ereditarietà e potrebbero non essere necessariamente di uso pratico. Inoltre, non c'è alcuna cosa come polimorfica in MATLAB e quindi l'uso di classi astratte è limitato. Questo esempio è per mostrare a chi creare una classe, ereditare da un'altra classe e applicare una classe astratta per definire un'interfaccia comune.
L'uso di classi astratte è piuttosto limitato in MATLAB ma può ancora venire utile in un paio di occasioni.
Diciamo che vogliamo un registratore di messaggi. Potremmo creare una classe simile a quella seguente:
classdef ScreenLogger
properties(Access=protected)
scrh;
end
methods
function obj = ScreenLogger(screenhandler)
obj.scrh = screenhandler;
end
function LogMessage(obj, varargin)
if ~isempty(varargin)
varargin{1} = num2str(varargin{1});
fprintf(obj.scrh, '%s\n', sprintf(varargin{:}));
end
end
end
end
Proprietà e metodi
In breve, le proprietà mantengono uno stato di un oggetto mentre i metodi sono come l'interfaccia e definiscono le azioni sugli oggetti.
La proprietà scrh
è protetta. Questo è il motivo per cui deve essere inizializzato in un costruttore. Esistono altri metodi (getter) per accedere a questa proprietà, ma questo non è all'altezza di questo esempio. Proprietà e metodi possono essere accessibili tramite una variabile che contiene un riferimento a un oggetto utilizzando la notazione punto seguita da un nome di un metodo o una proprietà:
mylogger = ScreenLogger(1); % OK
mylogger.LogMessage('My %s %d message', 'very', 1); % OK
mylogger.scrh = 2; % ERROR!!! Access denied
Proprietà e metodi possono essere pubblici, privati o protetti. In questo caso, protetto significa che sarò in grado di accedere a scrh
da una classe ereditata ma non dall'esterno. Di default tutte le proprietà e i metodi sono pubblici. Pertanto, LogMessage()
può essere utilizzato liberamente al di fuori della definizione della classe. Anche LogMessage
definisce un'interfaccia che significa che questo è ciò che dobbiamo chiamare quando vogliamo che un oggetto registri i nostri messaggi personalizzati.
Applicazione
Diciamo che ho uno script in cui utilizzo il mio logger:
clc;
% ... a code
logger = ScreenLogger(1);
% ... a code
logger.LogMessage('something');
% ... a code
logger.LogMessage('something');
% ... a code
logger.LogMessage('something');
% ... a code
logger.LogMessage('something');
Se ho più posti dove utilizzo lo stesso logger e poi voglio cambiarlo in qualcosa di più sofisticato, come scrivere un messaggio in un file, dovrei creare un altro oggetto:
classdef DeepLogger
properties(SetAccess=protected)
FileName
end
methods
function obj = DeepLogger(filename)
obj.FileName = filename;
end
function LogMessage(obj, varargin)
if ~isempty(varargin)
varargin{1} = num2str(varargin{1});
fid = fopen(obj.fullfname, 'a+t');
fprintf(fid, '%s\n', sprintf(varargin{:}));
fclose(fid);
end
end
end
end
e basta cambiare una riga di un codice in questo:
clc;
% ... a code
logger = DeepLogger('mymessages.log');
Il metodo sopra semplicemente aprirà un file, aggiungerà un messaggio alla fine del file e lo chiuderà. Al momento, per essere coerente con la mia interfaccia, ho bisogno di ricordare che il nome di un metodo è LogMessage()
ma potrebbe essere ugualmente qualsiasi altra cosa. MATLAB può forzare lo sviluppatore ad attenersi allo stesso nome usando classi astratte. Supponiamo di definire un'interfaccia comune per qualsiasi registratore:
classdef MessageLogger
methods(Abstract=true)
LogMessage(obj, varargin);
end
end
Ora, se entrambi ScreenLogger
e DeepLogger
ereditano da questa classe, MATLAB genererà un errore se LogMessage()
non è definito. Le classi astratte aiutano a costruire classi simili che possono utilizzare la stessa interfaccia.
Per il bene di questo exmaple, farò un cambiamento leggermente diverso. Suppongo che DeepLogger esegua entrambi i messaggi di registrazione su uno schermo e in un file contemporaneamente. Poiché ScreenLogger
registra già i messaggi sullo schermo, erediterò DeepLogger
dallo ScreenLoggger
per evitare la ripetizione. ScreenLogger
non cambia affatto a parte la prima riga:
classdef ScreenLogger < MessageLogger
// the rest of previous code
Tuttavia, DeepLogger
necessita di più modifiche nel metodo LogMessage
:
classdef DeepLogger < MessageLogger & ScreenLogger
properties(SetAccess=protected)
FileName
Path
end
methods
function obj = DeepLogger(screenhandler, filename)
[path,filen,ext] = fileparts(filename);
obj.FileName = [filen ext];
pbj.Path = pathn;
obj = obj@ScreenLogger(screenhandler);
end
function LogMessage(obj, varargin)
if ~isempty(varargin)
varargin{1} = num2str(varargin{1});
LogMessage@ScreenLogger(obj, varargin{:});
fid = fopen(obj.fullfname, 'a+t');
fprintf(fid, '%s\n', sprintf(varargin{:}));
fclose(fid);
end
end
end
end
Innanzitutto, inizializzo semplicemente le proprietà nel costruttore. In secondo luogo, poiché questa classe eredita da ScreenLogger
devo inizializzare anche questo oggetto parrent. Questa linea è ancora più importante perché il costruttore ScreenLogger
richiede un parametro per inizializzare il proprio oggetto. Questa linea:
obj = obj@ScreenLogger(screenhandler);
dice semplicemente "chiama il responsabile di ScreenLogger e inizializza con uno screen handler". Vale la pena notare qui che ho definito scrh
come protetto. Pertanto, potrei ugualmente accedere a questa proprietà da DeepLogger
. Se la proprietà è stata definita come privata. L'unico modo per inizializzarlo sarebbe utilizzare il consuctor.
Un altro cambiamento è nei methods
sezione. Ancora una volta per evitare la ripetizione, chiamo LogMessage()
da una classe genitore per registrare un messaggio su uno schermo. Se dovessi cambiare qualcosa per migliorare la registrazione dello schermo, ora devo farlo in un posto. Il codice di riposo è lo stesso in quanto fa parte di DeepLogger
.
Poiché questa classe eredita anche da una classe astratta MessageLogger
dovevo assicurarmi che anche LogMessage()
all'interno di DeepLogger
fosse definito. L'adesione a MessageLogger
è un po 'complicata qui. Penso che la ridefinizione dei casi di LogMessage
obbligatoria - la mia ipotesi.
In termini di codice in cui viene applicato un logger, grazie a un'interfaccia comune nelle classi, posso essere certo che questa riga dell'intero codice non creerebbe alcun problema. Gli stessi messaggi saranno sullo schermo come prima ma in aggiunta il codice scriverà tali messaggi su un file.
clc;
% ... a code
logger = DeepLogger(1, 'mylogfile.log');
% ... a code
logger.LogMessage('something');
% ... a code
logger.LogMessage('something');
% ... a code
logger.LogMessage('something');
% ... a code
logger.LogMessage('something');
Spero che questi esempi spieghino l'uso delle classi, l'uso dell'ereditarietà e l'uso di classi astratte.
PS. La soluzione per il problema sopra riportato è una delle tante. Un'altra soluzione, meno complessa, sarebbe quella di rendere ScreenLoger
un componente di un altro logger come FileLogger
ecc. ScreenLogger
sarebbe contenuto in una delle proprietà. Il suo LogMessage
chiamerebbe semplicemente LogMessage
di ScreenLogger
e mostrerà il testo su uno schermo. Ho scelto un approccio più complesso per mostrare piuttosto come funzionano le classi in MATLAB. Il codice di esempio qui sotto:
classdef DeepLogger < MessageLogger
properties(SetAccess=protected)
FileName
Path
ScrLogger
end
methods
function obj = DeepLogger(screenhandler, filename)
[path,filen,ext] = fileparts(filename);
obj.FileName = [filen ext];
obj.Path = pathn;
obj.ScrLogger = ScreenLogger(screenhandler);
end
function LogMessage(obj, varargin)
if ~isempty(varargin)
varargin{1} = num2str(varargin{1});
obj.LogMessage(obj.ScrLogger, varargin{:}); % <-------- thechange here
fid = fopen(obj.fullfname, 'a+t');
fprintf(fid, '%s\n', sprintf(varargin{:}));
fclose(fid);
end
end
end
end
Costruttori
Un costruttore è un metodo speciale in una classe che viene chiamata quando viene creata un'istanza di un oggetto. È una normale funzione MATLAB che accetta i parametri di input ma deve anche seguire determinate regole .
I costruttori non sono obbligatori in quanto MATLAB ne crea uno predefinito. In pratica, tuttavia, questo è un posto dove definire uno stato di un oggetto. Ad esempio, le proprietà possono essere limitate specificando gli attributi . Quindi, un costruttore può initalizzare tali proprietà per impostazione predefinita o valori definiti dall'utente che, in effetti, possono essere inviati dai parametri di input di un costruttore.
Chiamando un costruttore di una classe semplice
Questa è una semplice classe Person
.
classdef Person
properties
name
surname
address
end
methods
function obj = Person(name,surname,address)
obj.name = name;
obj.surname = surname;
obj.address = address;
end
end
end
Il nome di un costruttore è uguale al nome di una classe. Di conseguenza, i costruttori sono chiamati con il nome della sua classe. Una classe Person
può essere creata come segue:
>> p = Person('John','Smith','London')
p =
Person with properties:
name: 'John'
surname: 'Smith'
address: 'London'
Chiamare un costruttore di una classe figlio
Le classi possono essere ereditate dalle classi padre se condivide proprietà o metodi comuni. Quando una classe è ereditata da un'altra, è probabile che si debba chiamare un costruttore di una classe genitrice.
Un Member
classe eredita da una classe Person
perché Member
utilizza le stesse proprietà della classe Person ma aggiunge anche il payment
alla sua definizione.
classdef Member < Person
properties
payment
end
methods
function obj = Member(name,surname,address,payment)
obj = obj@Person(name,surname,address);
obj.payment = payment;
end
end
end
Analogamente alla classe Person
, Member
viene creato chiamando il suo costruttore:
>> m = Member('Adam','Woodcock','Manchester',20)
m =
Member with properties:
payment: 20
name: 'Adam'
surname: 'Woodcock'
address: 'Manchester'
Un costruttore di Person
richiede tre parametri di input. Member
deve rispettare questo fatto e quindi chiamare un costruttore della classe Person
con tre parametri. È soddisfatto dalla linea:
obj = obj@Person(name,surname,address);
L'esempio sopra mostra il caso in cui una classe figlia ha bisogno di informazioni per la sua classe genitore. Ecco perché un costruttore di Member
richiede quattro parametri: tre per la sua classe genitore e uno per se stesso.