[OOP] Use extended interface instead of base interface as a method return object

FPawlak
2017-02-28
2017-03-02
  • FPawlak - 2017-02-28

    Hi guys,
    I come from "Java world" so OOP is good know to me. But in codesys 3 I have some problems. I think codesys OOP is not so rich as Java and other high-level object oriented languages.

    I have created two base interfaces - IProduct and IFactory (with method returning instance of IProduct)

    INTERFACE IProduct
    
    INTERFACE IFactory
    METHOD getProduct : IProduct
    VAR_INPUT
    END_VAR
    

    And two Functions Blocks - ProductTypeOne, and FactoryOne which should return instances to ProductTypeOne

    FUNCTION_BLOCK ProductTypeOne IMPLEMENTS IProduct
    VAR_INPUT
    END_VAR
    VAR_OUTPUT
    END_VAR
    VAR
    END_VAR
    
    FUNCTION_BLOCK FactoryOne IMPLEMENTS IFactory
    VAR_INPUT
    END_VAR
    VAR_OUTPUT
    END_VAR
    VAR
    END_VAR
    METHOD getProduct : ProductTypeOne
    

    Unfortunately, when I try to compile this I get an error: "Interface of overridden method 'GETPRODUCT' of interface 'IFACTORY' doesn't match declaration getProduct"
    When I change the return type to the basic one - IProduct it works. I have checked that this work in Java (method declaration with return ProductTypeOne), so maybe it is also possible to do this in Codesys.

    I want to mark that FactoryOne return only ProductTypeOne because I plan to use FactoryOne as a base FunctionBlock for FBs which should extends it and they should only return ProductTypeOne - eg. FB FactoryOneA, FactoryOneB,......
    Also FactoryTwoA, FactoryTwoB,...... should return only ProcutTypeTwo and so on.

    I hope you can point me out how to achieve this in codesys.

     
  • Anonymous - 2017-03-01

    Originally created by: scott_cunningham

    IProduct is not the same as ProductTypeOne. Therefor the error is correct. You can return an instance of ProductTypeOne with your method (because it implements the interface and it will be cast automatically), but you cannot return the type ProductTypeOne as this is a different object. Maybe you meant to actually return the type ProductTypeOne (FBs are objects!) - but then you don't need the interface IProduct.

    You can do this:

    FB Factory...
    VAR
       MyProduct : ProductTypeOne;
    END_VAR
    METHOD getProduct : IProduct
    getProduct := MyProduct;
    
     
  • FPawlak - 2017-03-01

    Yes, I want to return instance of object type ProductTypeOne.

    As long as ProductTypeOne implements IProduct, everywhere IProduct is correct also ProductTypeOne is also correct because it can be cast to IProduct.
    So if FactoryOne implements IFactory then method getProduct should return anything what implements IProduct.
    But in Java I can also specify that method getProduct in FactoryOne can only return object which extends ProductTypeOne (they also implements IProduct). I hope that this is also possible in Codesys.

    The benefit of "narrowing return type" is that later I can write without any casting

    product : ProductTypeOne := FactoryOne.getProduct()
    //instead of
    product : IProduct := FactoryOne.getProduct()
    

    and call on that object methods specific to ProductTypeOne

    My english is not so very well so maybe I haven't express correctly what I am trying to achieve.

     
  • Anonymous - 2017-03-02

    Originally created by: scott_cunningham

    This code will not work:

    product : ProductTypeOne := FactoryOne.getProduct()
    

    I think for what you are trying to do, you need to look at the REFERENCE keyword. You want to define product to be of type ProductTypeOne but have it actually be whatever the product is out of FactoryOne.

    Try this:

    product : REFERENCE TO ProductTypeOne := FactoryOne.getProduct()
    

    But I guess I am not understanding your use of interfaces... for me I understand it this way:

    ProductTypeOne implements IProduct, but that doesn't mean ProductTypeOne is an IProduct. FBs can implement multiple interfaces, so whenever you write in code that you want to return an object IProduct, this does not mean the same as a ProductTypeOne.

    For example:

    INTERFACE IProduct
    METHOD ProduceNoise
    METHOD FlashLights
    INTERFACE IWiFi
    METHOD ConnectToWiFi
    PROPERTY NeedsUpdate
    

    And now you make the product FB:

    FUNCTION BLOCK ProductTypeOne IMPLEMENTS IProduct, IWiFi
    METHOD ProduceNoise
    METHOD FlashLights
    METHOD ConnectToWiFi
    PROPERTY NeedsUpdate
    

    So, the FB is required to have methods ProduceNoise and FlashLights, but also has other required features like a property NeedsUpdate and method ConnectToWiFi.

    Now you do this:

    PROGRAM PLC_PRG
    VAR
       Toy1 : ProductTypeOne;
       Obj  : IProduct := Toy1;
    END_VAR
    Toy1.ProduceNoise(); //valid
    Toy1.ConnectToWiFi(); //valid
    Obj.ProduceNoise(); //valid
    Obj.ConnectToWiFi(); //not valid - does not exist to interface
    

    With REFERENCE TO, you would get:

    PROGRAM PLC_PRG
    VAR
       Toy1 : ProductTypeOne;
       Obj  : REFERENCE TO ProductTypeOne;
    END_VAR
    Toy1.ProduceNoise(); //valid
    Toy1.ConnectToWiFi(); //valid
    Obj.ProduceNoise(); //valid
    Obj.ConnectToWiFi(); //valid
    
     
  • rickj - 2017-03-02

    I am new to codesys interfaces and have only done a bit of reading. A couple points come to mind that you may find helpful.

    1. IProduct is a subset of ProductTypeOne. If I understand correctly the _QUERRYINTERFACE macro can be used to cast ProductTypeOne to IProduct. Check help for usage.

    2. An object (FB) may implement multiple interfaces

    FUNCTION_BLOCK FactoryOne IMPLEMENTS IFactory, IProduct
    VAR_INPUT
    END_VAR
    VAR_OUTPUT
    END_VAR
    VAR
       
    END_VAR
    METHOD getProduct : IProduct
       IF _QUERRYINTERFACE (...) THEN
          getProduct := ...;
       END_IF
    
     
  • Anonymous - 2017-03-02

    Originally created by: scott_cunningham

    QueryInterface can be used for two purposes: verify an object implements an interface, and cast the object. For it to work, the object must extend __System.IQueryInterface. Instead of remembering that all FBs must extend, I create a basic "ALL" interface that all FBs implement.

    For example, I had a project where I wanted collections (arrays) of objects (because QueryInterface took too much time on my embedded controller for live-time determination)...

    Created a base interface all object implement:

    INTERFACE IAll EXTENDS __System.IQueryInterface
    PROPERTY Instance : STRING
    

    Created a interface for all OD objects:

    INTERFACE IOdAll
    

    Example object with automatic instance name!

    {attribute 'reflection'}
    FUNCTION_BLOCK ClassOdWord IMPLEMENTS IOdAll, IAll
    VAR_OUTPUT
       {attribute 'instance-path'}
       {attribute 'noinit'}
       InstanceName   : STRING;   //object's full instance name - automatically
    END_VAR
    

    And the collection object:

    FUNCTION_BLOCK ClassOdCollection
    VAR
       Collection   : ARRAY[1..GVL_COLLECT.MAX_OBJECTS] OF IOdAll;
       ObjNames   : ARRAY[1..GVL_COLLECT.MAX_OBJECTS] OF STRING;
       J      : INT;
       _ArrayOver   : BOOL;
       _BadObj   : INT;
       _DupeObj   : INT;
       _OverObj   : INT;
       _TotalObj   : INT;
    END_VAR
    METHOD AddObject
    VAR_INPUT
       Obj   : IAll; //general object
    END_VAR
    VAR
       Cast: IOdAll; //object for the collection
    END_VAR
    //try to cast the object
    IF (__QUERYINTERFACE(Obj, Cast)) THEN
       //it is a member!
       // make sure haven't already added this object
       FOR J:= 1 TO _TotalObj DO
          IF Collection[J] = Cast THEN
             // object pointer matches - dupe! do not add it
             _DupeObj:= _DupeObj + 1;
             RETURN;
          END_IF
       END_FOR
       
       IF _TotalObj < GVL_COLLECT.MAX_OBJECTS THEN
          _TotalObj:= _TotalObj + 1;
          Collection[_TotalObj]:= Cast;
          ObjNames[_TotalObj]:= Obj.Instance;
       ELSE
          _ArrayOver:= TRUE;
          _OverObj:= _OverObj + 1;
       END_IF
       
    ELSE
       //object was not a member!
       _BadObj:= _BadObj + 1;
    END_IF
    

    Some additional discussion can be found here: l viewtopic.php?f=11&t=5908 l .

     

Log in to post a comment.