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.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow