OOP - convert base class object to sub class object
Afficher commentaires plus anciens
Is it possible that a base class object "updates" itself into a sub class object by its own method? E.g., I have a rack that can hold up to 4 modules (objects) of different types (via handle) but the existing modules are unknown. Now, each module has (amongst other things) the functionality to check its own type by a method; to not repeat code there is a base class all specific modules inherit from. It would be nice, though, if I first can create a base class module object that checks its type and then expands functionality depending on its return.
Kind of the handle changes from module.base_modul to module.specific_modul with a methode that only exists in the base_modul class and later in its sub class specific_modul. I hope my thoughts are understandable?
6 commentaires
Guillaume
le 11 Juin 2019
Konrad's comment posted as an answer moved here:
Thanks a lot for both comments! I’m new into concepts of OOP in general and thinking of my design pattern for too long already; good to have some feedback. I see that my “converting object idea” probably does not have a successful outcome or at least is not the most elegant way.
Maybe I give you the whole picture: I need serial (RS232) communication to a module rack that can hold up to four individual modules. Through setting the RTS and DTS pins in combination each module can be addressed. The commands can be sent/ received via RxD/TxD then.
My approached was a program code similar to the "real world" objects. The user initializes the rack object that holds handles for the rack_slots 1-4 and a serial port. The build in modules are unknown, otherwise it would be more easy, I think.
The code is just the rough outline of what I have so far, you probably see my lack of programming experience. At least it could identify the right modules in their slots already.
classdef rack < handle
properties
Slots % Handles for slots 1-4
end
properties (Hidden=true)
SerialPort % Handle serial Port
end
methods
function obj = rack(Port) %constructor
obj.SerialPort = serial(sprintf('COM%i',Port));
for modulenumber = 1:4
obj.Slots{i} = rack_slots(obj,modulenumber);
end
end
function RS232connect/disconnect(obj)
...
end
function moduleininitialisation
for i=1:4
Typ = obj.rack_slots{i}.CheckModulType();
if strcmp(Typ(1:end),'Typ1')
% somehow extend functionality or override slots handle ?
elseif strcmp(Typ(1:end),'Typ2')
% somehow extend functionality or override slots handle ?
end
end
end
function RxD = sendcommand(obj, command)
fprintf(obj.SerialPort,command);
RxD = fscanf(obj.SerialPort);
end
end
Each rack_slot has a different DTR / RTS combination which is passed to the serial object before sending a command. Through sending the command 'typ?' it returns the module information...
classdef rack_slots < handle
properties (SetAccess=protected)
Parent
end
properties (SetAccess=private, Hidden=true)
DTR
RTS
end
methods
function obj = rack_slots(Parent, modulenumber)
switch modulenumber
case 1
obj.DTR = 'off';
obj.RTS = 'off';
case 2
obj.DTR = 'off';
obj.RTS = 'on';
case 3
obj.DTR = 'on';
obj.RTS = 'off';
case 4
obj.DTR = 'on';
obj.RTS = 'on';
end
end
function Return = sendcommand(obj,command)
obj.Parent.SerialPort.DataTerminalReady = obj.DTR;
obj.Parent.SerialPort.RequestToSend = obj.RTS;
RxD = obj.Parent.sendcommand(command);
end
function Typ = CheckModulType(obj) %provide also more status information
Typ = obj.sendcommand('typ?');
end
end
end
end
However, the idea was that the rack_slots object somehow extends funcionallity by inheritance.
classdef module_typ1 < rack_slots
methods
%% Konstruktor.. doesn't work of course
function obj = module_typ1(Parent,modulenumber)
obj@rack_slots(Parent, modulnummer);
end
%% read..
function Return = readdata_X(obj)
X = obj.sendcommand('X');
%% set..
function opperationMode1(obj, U, I, etc)
obj.sendcommand('U-%i');
obj.sendcommand('I-%i');
obj.sendcommand('etc-%i');
end
end
end
The heterogeneous class hierarchy seems interesting approach. But before getting into detail, is there maybe another common design strategy for those hardware interfaces. Just to put me on the right track.
My own reply moved here:
Most of your design is what I would have done.
The one thing I don't agree with at all, is that you initially create the slot array as an array of the base class, then later on change each to the derived type. As Steven explained, that's a bad OOP design.
Really, the base class should be abstract so you can never create objects of the base class. it's just an interface definition. Is there any reason to have the rack with the modules in an unknown state? If not, the best course of action would be to identify the modules in the rack constructor. Are the modules hot-swappable?
I don't have time to go into more details right now. I'll come back and give an implementation later on.
Guillaume
le 11 Juin 2019
Konrad's reply moved here:
There is no hot-swap of modules while script is running but type and position is difficult to see for users from outside. Nevertheless different combinations of modules can occure in the future, so it would be much more user friendly and adaptable.
I thought about the same, using the identify function inside the constructor or rack class, but aslo felt bad copying code (to keep access of module information later on too).
Any suggestions?
Guillaume
le 11 Juin 2019
Konrad's new commen moved here:
Sorry, I'm back again.. still struggling with my code design. I have heeded your notes and changed the structure a little bit (now create the individual modules - which inherit from an abstract class - directly from the main rack class). But I'm still not 100% happy, or does it look all right? E.g, I can't initialize the module objects inside the rack class constructor because I first need to create the SerialPort object and then set different RTS/DTS pin combinations.. Any advises for smarter solutions are welcome, it's really just the outline of the code, that I can word on details.
classdef WG5K_rack < handle
properties
Slots % Handles for slots 1-4 (array)
end
properties (Hidden=true)
SerialPort
end
methods
%% constructor
function obj = WG5K_rack(port)
if nargin~=1
fprintf('...error...');
return
end
try
obj.SerialPort = serial(sprintf('COM%i',Port), ...
'BaudRate', 115200, ...
'Terminator', 'CR');
catch err
fprintf('error with communication setup: %s\n',err.message);
return
end
try
fopen(obj.SerialPort);
catch err
fprintf('error by open communication: %s\n',err.message)
return
end
end
%% destrucor
function delete(obj)
...
end
%% RS232 connection
function openRS232(obj)
...
end
function closeRS232(obj)
...
end
%% moduleininitialisation
function findModules(obj)
if strcmp(obj.SerialPort.Status,'open')
for i = 1:4
switch i
case 1
obj.SerialPort.DataTerminalReady = 'off';
obj.SerialPort.RequestToSend = 'off';
case 2
obj.SerialPort.DataTerminalReady = 'off';
obj.SerialPort.RequestToSend = 'off';
case 3
obj.SerialPort.DataTerminalReady = 'off';
obj.SerialPort.RequestToSend = 'off';
case 4
obj.SerialPort.DataTerminalReady = 'off';
obj.SerialPort.RequestToSend = 'off';
end
try
RxD = obj.sendCommand('typ?');
if sum(strcmp(RxD(1:end),{'30V', '45V', '60V', '120V'}))
obj.Slots{i} = PM3K_module(obj,i); % create PM3K-typ module
fprintf('slot %i is occupied by PM3K Typ\n',i)
elseif strcmp(RxD(1:end),'WR')
obj.Slots{i} = PM3AC10_module(obj,i); % create PM3AC10-typ module
fprintf('slot %i is occupied by M3AC10 Typ\n',i)
elseif strcmp(RxD(1:end),'AC')
obj.Slots{i} = PM3AC10_1X_module(obj,i); % create PM3AC10_1X-typ module
fprintf('slot %i is occupied by M3AC10_1X Typ\n',i)
else
obj.Slots{i} = Vacant_module(obj,i);
fprintf('slot %i is occupied by unknown Typ: %s\n',i,RxD)
end
catch
obj.Slots{i} = Vacant_module(obj,i);
fprintf('Modulplatz %i is vacant', i)
continue
end
end
else
fprintf('error: communication closed')
end
end
%% send commands
function RxD = sendCommand(obj, command)
if ~strcmp(obj.SerialPort.Status,'open')
fprintf('error: communication closed\n');
return
end
% wright and read
fprintf(obj.SerialPort,command);
RxD = fscanf(obj.SerialPort);
if isempty(RxD)
RxD = -1;
return
end
end
end
Then, the classes for the different modules (e.g PM3K)
classdef PM3K_module < WG5K_slots
methods
function obj = PM3K_module(Parent,slot_number)
% passed to constructor of general slots class
obj = obj@WG5K_slots(Parent,'PM3Kxxx',slot_number);
end
%% read..
function Return = readdata_X(obj)
X = obj.sendcommand('X');
%% set..
function opperationMode1(obj, U, I, etc)
obj.sendcommand('U-%i');
obj.sendcommand('I-%i');
obj.sendcommand('etc-%i');
end
end
end
And the rack_slots class, which provide fuctionality for all different module types.
classdef (Abstract) WG5K_slot < handle
properties
ErrorCode
end
properties (SetAccess=protected)
Parent
Name
Error
end
properties (SetAccess=private, Hidden=true)
DTR
RTS
end
methods
%% constructor
function obj = WG5K_Slot(parent, name, slot_number)
obj.Parent = parent;
obj.Name = name
obj.ErrorCode = 0;
obj.Error = false;
% encoding slot_number
switch slot_number
case 1
obj.DTR = 'off';
obj.RTS = 'off';
case 2
obj.DTR = 'off';
obj.RTS = 'on';
case 3
obj.DTR = 'on';
obj.RTS = 'off';
case 4
obj.DTR = 'on';
obj.RTS = 'on';
end
end
%% pass back command with right DTR/ RTS settings
function RxD = sendCommand(obj,Befehl)
if obj.Error == false;
obj.Parent.SerialPort.DataTerminalReady = obj.DTR;
obj.Parent.SerialPort.RequestToSend = obj.RTS;
RxD = obj.Parent.sendCommand(Befehl);
else
fprintf('error: module type wrong\n')
end
end
function quitError(obj)
obj.sendCommand('cq');
end
function turnModuleOnOff(obj,on_off)
...
end
function Info = Modulinfo(obj)
Info.FirmwareNr=-1;
Info.SerienNr=-1;
Info.Datum=-1;
Info.Typ = obj.sendCommand('typ');
if Info.Typ==-1, return; end
Info.FirmwareNr = obj.sendCommand('firmware');
if Info.FirmwareNr==-1, return; end
Info.SeriesNr = obj.sendCommand('seriesNr.');
if Info.SerienNr==-1, return; end
Info.date = obj.sendCommand('date');
end
function checkModuleType(obj)
Info = Modulinfo(obj);
if sum(strcmp(Info.Typ(1:end),{'30V', '45V', '60V', '120V'}))
ModName = 'pm3kxxx';
elseif strcmp(Info.Typ(1:end),'WR')
ModName = 'pm3ac10';
elseif strcmp(Info.Typ(1:end),'AC')
ModName = 'pm3ac10_1x';
else
ModName = '';
end
if ~strcmpi(ModName,obj.Name);
obj.Error = true;
end
end
end
end
Guillaume
le 11 Juin 2019
The hierarchy looks fine to me. I possibly would have another derived class from WG5K_slot called Unknown_slot and fill the slots array with that in the WG5K_rack class when it's constructed.
The only thing that looks odd is the duplicate module identification code in the slot and rack class. I probably would have just it as a static method of the base slot class as I initially outline.
Konrad Warner
le 12 Juin 2019
Modifié(e) : Konrad Warner
le 17 Juin 2019
Réponse acceptée
Plus de réponses (1)
Th design you describe is fairly common and sounds sensible but I'm not entirely clear on the finer point, particularly on how the module type is stored/retrieved
Typically, with a design like that the base class is abstract and the module type is simply determined by the type of the derived class. So, you can never construct a module without a type. What you can have though is a factory method in the base class that construct objects of the correct class:
classdef (Abstract) ModuleBase < handle %the fact that the class is a handle class is irrelevant here
properties %properties common to all modules
slot;
end
methods
%base class constructor
function this = ModuleBase(slot)
this.slot = slot;
end
end
methods (Static)
%factory method
function module = IdentifyModule(slot)
%Identify the module in the given slot and return an instance of the module of the correct type
moduleid = queryslot(slot); %get identification of module from slot. No idea how you do that
%using switch here. In practice I'd probably build the class name from the module id and use feval to call the appropriate constructor.
switch(moduleid)
case 'type1':
module = ModuleType1(slot); %call constructor of Type1 module
case 'type2':
module = ModuleType2(slot); %call constructor of Type2 module
end
end
end
end
classdef ModuleType1 < ModuleBase
properties
%properties specific to Type1
end
methods
function this = ModuleType1(slot)
this@ModuleBase(slot);
%set other properties
end
end
end
classdef ModuleType2 < ModuleBase
properties
%properties specific to Type2
end
methods
function this = ModuleType2(slot)
this@ModuleBase(slot);
%set other properties
end
end
end
edit: As pointed out by Steven, the factory method should have been static
Catégories
En savoir plus sur Modulation dans Centre d'aide et File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!