MATLAB Language
Programowanie obiektowe
Szukaj…
Definiowanie klasy
Klasę można zdefiniować za pomocą classdef
w pliku .m
o tej samej nazwie co klasa. Plik może zawierać blok end
classdef
... i funkcje lokalne do użycia w metodach klasowych.
Najbardziej ogólna definicja klasy MATLAB ma następującą strukturę:
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
Dokumentacja MATLAB: Atrybuty klas, atrybuty właściwości, atrybuty metod, atrybuty zdarzeń , ograniczenia klas wyliczeń .
Przykładowa klasa:
Klasę o nazwie Car
można zdefiniować w pliku Car.m
jako
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
Zauważ, że konstruktor jest metodą o tej samej nazwie co klasa. <Konstruktor to specjalna metoda klasy lub struktury w programowaniu obiektowym, która inicjuje obiekt tego typu. Konstruktor jest metodą instancji, która zwykle ma taką samą nazwę jak klasa, i może być używana do ustawiania wartości elementów obiektu, na wartości domyślne lub wartości zdefiniowane przez użytkownika.
Instancję tej klasy można utworzyć, wywołując konstruktor;
>> myCar = Car('Ford', 'Mustang'); //creating an instance of car class
Wywołanie metody drive
zwiększy przebieg
>> myCar.mileage
ans =
0
>> myCar.drive(450);
>> myCar.mileage
ans =
450
Wartości a klasy uchwytów
Klasy w MATLAB są podzielone na dwie główne kategorie: klasy wartości i klasy obsługi. Główną różnicą jest to, że podczas kopiowania instancji klasy wartości dane bazowe są kopiowane do nowej instancji, podczas gdy dla klas uchwytów nowa instancja wskazuje na oryginalne dane, a zmiana wartości w nowej instancji zmienia je w oryginale. Klasę można zdefiniować jako uchwyt, dziedzicząc po klasie handle
.
classdef valueClass
properties
data
end
end
i
classdef handleClass < handle
properties
data
end
end
następnie
>> 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
Dziedziczenie z klas i klas abstrakcyjnych
Zastrzeżenie: przedstawione tutaj przykłady służą wyłącznie do pokazania użycia klas abstrakcyjnych i dziedziczenia i niekoniecznie mogą mieć praktyczne zastosowanie. Ponadto w MATLAB nie ma takiej rzeczy polimorficznej, dlatego użycie klas abstrakcyjnych jest ograniczone. W tym przykładzie pokazano, kto ma utworzyć klasę, odziedziczyć po innej klasie i zastosować klasę abstrakcyjną, aby zdefiniować wspólny interfejs.
Korzystanie z klas abstrakcyjnych jest raczej ograniczone w MATLAB, ale wciąż może się przydać przy kilku okazjach.
Powiedzmy, że chcemy rejestratora wiadomości. Możemy stworzyć klasę podobną do poniższej:
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
Właściwości i metody
Krótko mówiąc, właściwości utrzymują stan obiektu, podczas gdy metody są jak interfejs i definiują działania na obiektach.
scrh
nieruchomości jest chroniony. Dlatego należy go zainicjować w konstruktorze. Istnieją inne metody (getters), aby uzyskać dostęp do tej właściwości, ale nie jest to możliwe w tym przykładzie. Dostęp do właściwości i metod można uzyskać za pomocą zmiennej, która zawiera odwołanie do obiektu, za pomocą notacji kropkowej, po której następuje nazwa metody lub właściwości:
mylogger = ScreenLogger(1); % OK
mylogger.LogMessage('My %s %d message', 'very', 1); % OK
mylogger.scrh = 2; % ERROR!!! Access denied
Właściwości i metody mogą być publiczne, prywatne lub chronione. W tym przypadku scrh
chroniony oznacza, że będę mógł uzyskać dostęp do scrh
z odziedziczonej klasy, ale nie z zewnątrz. Domyślnie wszystkie właściwości i metody są publiczne. Dlatego LogMessage()
może być swobodnie używany poza definicją klasy. LogMessage
definiuje również interfejs, co oznacza, że musimy to wywoływać, gdy chcemy, aby obiekt rejestrował nasze niestandardowe komunikaty.
Podanie
Załóżmy, że mam skrypt, w którym korzystam z mojego programu rejestrującego:
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');
Jeśli mam wiele miejsc, w których korzystam z tego samego rejestratora, a następnie chcę go zmienić na coś bardziej zaawansowanego, na przykład napisać wiadomość w pliku, musiałbym utworzyć inny obiekt:
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
i po prostu zmień jeden wiersz kodu na:
clc;
% ... a code
logger = DeepLogger('mymessages.log');
Powyższa metoda po prostu otworzy plik, doda wiadomość na końcu pliku i zamknie go. W tej chwili, aby zachować spójność z moim interfejsem, muszę pamiętać, że nazwa metody to LogMessage()
ale równie dobrze może to być coś innego. MATLAB może zmusić programistę do trzymania się tej samej nazwy za pomocą klas abstrakcyjnych. Załóżmy, że definiujemy wspólny interfejs dla dowolnego programu rejestrującego:
classdef MessageLogger
methods(Abstract=true)
LogMessage(obj, varargin);
end
end
Teraz, jeśli zarówno ScreenLogger
jak i DeepLogger
dziedziczą z tej klasy, MATLAB wygeneruje błąd, jeśli LogMessage()
nie zostanie zdefiniowany. Klasy abstrakcyjne pomagają budować podobne klasy, które mogą korzystać z tego samego interfejsu.
Ze względu na ten przykład dokonam nieco innej zmiany. Zakładam, że DeepLogger będzie jednocześnie rejestrował komunikat na ekranie i w pliku. Ponieważ ScreenLogger
już rejestruje wiadomości na ekranie, zamierzam odziedziczyć DeepLogger
z ScreenLoggger
aby uniknąć powtórzeń. ScreenLogger
wcale się nie zmienia poza pierwszym wierszem:
classdef ScreenLogger < MessageLogger
// the rest of previous code
Jednak DeepLogger
wymaga więcej zmian w metodzie 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
Po pierwsze, po prostu inicjuję właściwości w konstruktorze. Po drugie, ponieważ ta klasa dziedziczy z ScreenLogger
muszę również zainicjować ten obiekt ScreenLogger
. Ta linia jest jeszcze ważniejsza, ponieważ konstruktor ScreenLogger
wymaga jednego parametru do zainicjowania własnego obiektu. Ta linia:
obj = obj@ScreenLogger(screenhandler);
po prostu mówi „zadzwoń do konstruktora ScreenLoggera i zainicjuj go za pomocą procedury obsługi ekranu”. Warto tutaj zauważyć, że zdefiniowałem scrh
jako chroniony. Dlatego mogłem uzyskać równy dostęp do tej właściwości z DeepLogger
. Jeśli właściwość została zdefiniowana jako prywatna. Jedynym sposobem na zainicjalizowanie go byłoby użycie konsoli.
Kolejna zmiana dotyczy methods
sekcji. Ponownie, aby uniknąć powtórzeń, LogMessage()
z klasy nadrzędnej, aby zarejestrować komunikat na ekranie. Gdybym musiał coś zmienić, aby ulepszyć rejestrowanie ekranu, teraz muszę to zrobić w jednym miejscu. Pozostały kod jest taki sam, ponieważ jest częścią DeepLogger
.
Ponieważ ta klasa dziedziczy również po klasie abstrakcyjnej MessageLogger
, musiałem się upewnić, że LogMessage()
w DeepLogger
jest również zdefiniowany. Dziedziczenie z MessageLogger
jest tutaj trochę trudne. Myślę, że to przypadek redefinicji LogMessage
obowiązkowej - zgaduję.
Jeśli chodzi o kod, w którym stosowany jest rejestrator, dzięki wspólnemu interfejsowi w klasach, mogę się upewnić, że zmiana tego jednego wiersza w całym kodzie nie spowoduje żadnych problemów. Te same wiadomości będą wyświetlane na ekranie logowania jak poprzednio, ale dodatkowo kod zapisuje takie wiadomości do pliku.
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');
Mam nadzieję, że te przykłady wyjaśniły użycie klas, wykorzystanie dziedziczenia i użycie klas abstrakcyjnych.
PS. Rozwiązanie powyższego problemu jest jednym z wielu. Innym rozwiązaniem, mniej złożonym, byłoby uczynienie ScreenLoger
składnikiem innego programu rejestrującego, takiego jak FileLogger
itp. ScreenLogger
byłby przechowywany w jednej z właściwości. Jego LogMessage
po prostu wywoła LogMessage
z ScreenLogger
i wyświetli tekst na ekranie. Wybrałem bardziej złożone podejście, aby raczej pokazać, jak działają klasy w MATLAB. Przykładowy kod poniżej:
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
Konstruktory
Konstruktor to specjalna metoda w klasie, która jest wywoływana podczas tworzenia instancji obiektu. Jest to zwykła funkcja MATLAB, która akceptuje parametry wejściowe, ale musi również przestrzegać pewnych zasad .
Konstruktory nie są wymagane, ponieważ MATLAB tworzy domyślny. W praktyce jest to jednak miejsce do definiowania stanu obiektu. Na przykład właściwości można ograniczyć, określając atrybuty . Następnie konstruktor może domyślnie zainicjować takie właściwości lub wartości zdefiniowane przez użytkownika, które w rzeczywistości mogą zostać przesłane przez parametry wejściowe konstruktora.
Wywołanie konstruktora prostej klasy
To jest prosta Person
klasowa.
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
Nazwa konstruktora jest taka sama jak nazwa klasy. W konsekwencji konstruktory są wywoływane przez nazwę swojej klasy. Person
klasowa może zostać utworzona w następujący sposób:
>> p = Person('John','Smith','London')
p =
Person with properties:
name: 'John'
surname: 'Smith'
address: 'London'
Wywołanie konstruktora klasy potomnej
Klasy można dziedziczyć z klas nadrzędnych, jeśli mają wspólne właściwości lub metody. Gdy klasa jest dziedziczona od innej, prawdopodobnie wywoływany jest konstruktor klasy nadrzędnej.
A klasa Member
dziedziczy z klasy Person
ponieważ Member
wykorzystuje te same właściwości jak klasy Person, ale również dodaje payment
do jej definicji.
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
Podobnie jak w przypadku klasy Person
, Member
jest tworzony przez wywołanie jego konstruktora:
>> m = Member('Adam','Woodcock','Manchester',20)
m =
Member with properties:
payment: 20
name: 'Adam'
surname: 'Woodcock'
address: 'Manchester'
Konstruktor Person
wymaga trzech parametrów wejściowych. Member
musi szanować ten fakt i dlatego wywołać konstruktora klasy Person
z trzema parametrami. Spełnia go linia:
obj = obj@Person(name,surname,address);
Powyższy przykład pokazuje przypadek, gdy klasa potomna potrzebuje informacji dla swojej klasy nadrzędnej. Dlatego konstruktor elementu Member
wymaga czterech parametrów: trzech dla swojej klasy nadrzędnej i jednego dla siebie.