Поиск…


Определение класса

Класс можно определить с помощью classdef в файле .m с тем же именем, что и класс. Файл может содержать classdef ... end блока и локальные функции для использования в методах класса.

Наиболее общее определение класса MATLAB имеет следующую структуру:

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

Документация MATLAB: атрибуты класса, атрибуты свойств, атрибуты метода, атрибуты событий , ограничения класса перечисления .

Пример:

Класс под названием Car можно определить в файле Car.m как

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

Обратите внимание, что конструктор - это метод с тем же именем, что и класс. <Конструктор - это специальный метод класса или структуры в объектно-ориентированном программировании, который инициализирует объект этого типа. Конструктор - это метод экземпляра, который обычно имеет то же имя, что и класс, и может использоваться для установки значений элементов объекта либо по умолчанию, либо по определенным пользователем значениям.>

Экземпляр этого класса может быть создан путем вызова конструктора;

>> myCar = Car('Ford', 'Mustang'); //creating an instance of car class 

Вызов метода drive увеличит пробег

>> myCar.mileage 
    
    ans = 
            0

>> myCar.drive(450);

>> myCar.mileage
    
   ans = 
            450

Классы Value vs Handle

Классы в MATLAB подразделяются на две основные категории: классы значений и классы дескрипторов. Основное различие заключается в том, что при копировании экземпляра класса значений базовые данные копируются в новый экземпляр, тогда как для классов дескрипторов новый экземпляр указывает на исходные данные и изменение значений в новом экземпляре меняет их в оригинале. Класс может быть определен как дескриптор путем наследования класса handle .

classdef valueClass
    properties
        data
    end
end

а также

classdef handleClass < handle
    properties
        data
    end
end

затем

>> 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

Наследование классов и абстрактных классов

Отказ от ответственности: примеры, представленные здесь, предназначены только для того, чтобы показать использование абстрактных классов и наследования и, возможно, не обязательно иметь практическое применение. Кроме того, в MATLAB нет ничего, кроме полиморфного, и поэтому использование абстрактных классов ограничено. В этом примере показано, кто должен создавать класс, наследовать от другого класса и применять абстрактный класс для определения общего интерфейса.

Использование абстрактных классов довольно ограничено в MATLAB, но оно все же может пригодиться несколько раз.

Предположим, нам нужен регистратор сообщений. Мы могли бы создать класс, подобный приведенному ниже:

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

Свойства и методы

Короче говоря, свойства имеют состояние объекта, тогда как методы похожи на интерфейс и определяют действия над объектами.

Свойство scrh защищено. Вот почему он должен быть инициализирован в конструкторе. Существуют другие методы (getters) для доступа к этому свойству, но это не соответствует этому примеру. Свойства и методы могут быть доступны через переменную, содержащую ссылку на объект, с использованием точечной нотации, за которой следует имя метода или свойства:

mylogger = ScreenLogger(1);                         % OK
mylogger.LogMessage('My %s %d message', 'very', 1); % OK
mylogger.scrh = 2;                                  % ERROR!!! Access denied

Свойства и методы могут быть общедоступными, частными или защищенными. В этом случае protected означает, что я получаю доступ к scrh из унаследованного класса, но не извне. По умолчанию все свойства и методы являются общедоступными. Поэтому LogMessage() может свободно использоваться вне определения класса. Также LogMessage определяет интерфейс, означающий, что это то, что мы должны вызывать, когда мы хотим, чтобы объект регистрировал наши пользовательские сообщения.

заявка

Предположим, у меня есть сценарий, в котором я использую свой регистратор:

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');

Если у меня есть несколько мест, где я использую один и тот же журнал, а затем хочу изменить его на нечто более сложное, например, написать сообщение в файле, мне придется создать другой объект:

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

и просто измените одну строку кода на это:

clc;
% ... a code
logger = DeepLogger('mymessages.log');

Вышеупомянутый метод просто откроет файл, добавит сообщение в конец файла и закроет его. На данный момент, чтобы быть в согласии с моим интерфейсом, мне нужно помнить, что имя метода - LogMessage() но в равной степени это может быть что угодно. MATLAB может заставить разработчиков придерживаться одного и того же имени, используя абстрактные классы. Предположим, мы определяем общий интерфейс для любого регистратора:

classdef MessageLogger
    methods(Abstract=true)
        LogMessage(obj, varargin);
    end
end

Теперь, если оба ScreenLogger и DeepLogger наследуются от этого класса, MATLAB будет генерировать ошибку, если LogMessage() не определен. Абстрактные классы помогают создавать похожие классы, которые могут использовать один и тот же интерфейс.

Ради этого примера я сделаю несколько другое изменение. Я собираюсь предположить, что DeepLogger будет делать как сообщение регистрации на экране, так и в файле одновременно. Поскольку ScreenLogger уже регистрирует сообщения на экране, я собираюсь наследовать DeepLogger из ScreenLoggger чтобы избежать повторения. ScreenLogger не изменяется вообще, кроме первой строки:

classdef ScreenLogger < MessageLogger
// the rest of previous code 

Тем не менее, DeepLogger нуждается в дополнительных изменениях в методе 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

Во-первых, я просто инициализирую свойства в конструкторе. Во-вторых, поскольку этот класс наследуется от ScreenLogger мне также нужно инициализировать этот объект parrent. Эта строка еще важнее, потому что конструктор ScreenLogger требует одного параметра для инициализации собственного объекта. Эта строка:

obj = obj@ScreenLogger(screenhandler);

просто говорит: «Назовите конструктора ScreenLogger и активируйте его с помощью обработчика экрана». Здесь стоит отметить, что я определил scrh как защищенный. Поэтому я мог бы получить доступ к этому свойству от DeepLogger . Если свойство было определено как личное. Единственный способ его инициализации - использовать консуктора.

Другое изменение - в methods раздела. Опять же, чтобы избежать повторения, я вызываю LogMessage() из родительского класса для регистрации сообщения на экране. Если бы мне пришлось что-то менять, чтобы улучшить внесение изменений в экранный журнал, теперь я должен сделать это в одном месте. Код DeepLogger такой же, как и часть DeepLogger .

Поскольку этот класс также наследуется от абстрактного класса MessageLogger я должен был убедиться, что LogMessage() внутри DeepLogger также определен. Наследовать от MessageLogger здесь немного сложно. Я думаю, что случаи переопределения LogMessage обязательны - думаю.

Что касается кода, в котором применяется регистратор, благодаря общему интерфейсу в классах, я могу быть уверенным, что это изменение в одной строке во всем коде не вызовет никаких проблем. Те же сообщения будут отображаться на экране, как и раньше, но дополнительно код будет записывать такие сообщения в файл.

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');

Надеюсь, что эти примеры объясняют использование классов, использование наследования и использование абстрактных классов.


PS. Решение этой проблемы является одним из многих. Другим решением, менее сложным, было бы заставить ScreenLoger быть компонентом другого регистратора, такого как FileLogger и т. Д. ScreenLogger будет храниться в одном из свойств. Его LogMessage просто вызовет LogMessage ScreenLogger и покажет текст на экране. Я выбрал более сложный подход, чтобы показать, как классы работают в MATLAB. Пример кода ниже:

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

Конструкторы

Конструктор - это особый метод в классе, который вызывается при создании экземпляра объекта. Это обычная функция MATLAB, которая принимает входные параметры, но также должна следовать определенным правилам .

Конструкторы не требуются, так как MATLAB создает по умолчанию. На практике, однако, это место для определения состояния объекта. Например, свойства могут быть ограничены указанием атрибутов . Затем конструктор может инициализировать такие свойства по умолчанию или пользовательские значения, которые на самом деле могут быть отправлены входными параметрами конструктора.

Вызов конструктора простого класса

Это простой класс 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

Имя конструктора совпадает с именем класса. Следовательно, конструкторы вызываются именем своего класса. Класс Person может быть создан следующим образом :

>> p = Person('John','Smith','London')
p = 
  Person with properties:

       name: 'John'
    surname: 'Smith'
    address: 'London'

Вызов конструктора дочернего класса

Классы могут быть унаследованы от родительских классов, если общие свойства или методы общего доступа. Когда класс унаследован от другого, вполне вероятно, что должен быть вызван конструктор родительского класса.

Member класса наследуется от класса Person поскольку Member использует те же свойства, что и класс Person, но также добавляет payment к его определению.

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

Аналогично классу Person , Member создается путем вызова его конструктора:

>> m = Member('Adam','Woodcock','Manchester',20)
m = 
  Member with properties:

    payment: 20
       name: 'Adam'
    surname: 'Woodcock'
    address: 'Manchester'

Для конструктора Person требуются три входных параметра. Member должен уважать этот факт и поэтому вызывать конструктор класса Person с тремя параметрами. Это выполняется по строке:

obj = obj@Person(name,surname,address);

В приведенном выше примере показан случай, когда дочерний класс нуждается в информации для своего родительского класса. Вот почему конструктор Member требует четыре параметра: три для его родительского класса и один для себя.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow