Written by

Senior Cloud Architect at InterSystems
Question Eduard Lebedyuk · Aug 13, 2018

Forbid extending my class

I have a class (to be deployed) and I want to forbid anyone from extending it.

Is there any way to do that?

Comments

Eduard Lebedyuk  Aug 13, 2018 to Benjamin De Boe

Final keyword can be removed and the class extended.

To clarify, I want to deploy a class that the user would be unable to extend while having access to the system.

0
Eduard Lebedyuk  Aug 14, 2018 to Sergei Shutov

I believe it's enough to have [Final] keyword set in deployed mode to give a developer a hint that this class should not be extended.

Well, it's mainly for developers who can't take a hint.

If you want to enforce this behaviour, I would add a check into each method as a first line , something like

if $this.%ClassName(1)'="My.Class" quit $$$ERROR(50000,"don't extend this class")

Good idea.

You can also try to add a method-generator, I believe when you have a deployed class with method generator it will not be able to compile a subclass without method generator's source (though I'm not sure).

At first it didn't work - method generator by default works while deployed. Then I added a class check to generator.  Compilation was now failing, but other methods were actually compiled so I was able to call them. Finally I worked out this solution:

Class Package.Final [ Final ]
{

ClassMethod ANoExtend() [ CodeMode = objectgenerator, Final, ForceGenerate, Private ]
{
    quit:%class.Name="Package.Final" $$$OK
    quit $$$ERROR($$$GeneralError, "No extending")
}

ClassMethod ProtectedMethod() As %Status [ Private, ForceGenerate, GenerateAfter = ANoExtend ]
{
        // code
    quit $$$OK
}
}

This way each protected method should be recompiled but only after method-generator which always fails in subclasses. This way no code gets generated.

0
Evgeny Shvarov  Aug 13, 2018 to Eduard Lebedyuk

So you want to prevent users from changing the source code of the class. 

Deploy it without a source code?

0
Eduard Lebedyuk  Aug 13, 2018 to Evgeny Shvarov

Deploying does not prevent user from removing Final keyword.

Deploying only prevents user from easily modifying source code.

0
Evgeny Shvarov  Aug 13, 2018 to Eduard Lebedyuk

How could one remove Final keyword without changing the source code?

0
Eduard Lebedyuk  Aug 13, 2018 to Evgeny Shvarov
set c=##class(%Dictionary.ClassDefinition).%OpenId("Deployed.Class")
set c.Final=0
write c.%Save()

And after that compiling subclasses would be possible.

0
Evgeny Shvarov  Aug 13, 2018 to Eduard Lebedyuk

Interesting. Didn't know about the option.

0
Eduard Lebedyuk  Aug 13, 2018 to Evgeny Shvarov

Not without modifying system classes.

0
Evgeny Shvarov  Aug 13, 2018 to Eduard Lebedyuk

Is there a way to prevent successful %Save() for the ClassDefinition?

0
Warlin Garcia  Aug 13, 2018 to Eduard Lebedyuk

Is ClassDefinition available even if you deploy without the source code e.g. OBJ version?

0
Eduard Lebedyuk  Aug 14, 2018 to Chris Thompson

ReadOnly database could be made Read-Write. I assume developer has complete access to Cache and underlying OS.

meta data of class definition is included in the read only database

It is.

0
Benjamin De Boe  Aug 14, 2018 to Eduard Lebedyuk

I think all this trench-digging is becoming a somewhat pessimistic perspective, but indeed if you assume someone has admin access, they're admins and can take administrator action...

Maybe this is what lawyers invented licenses for :-) (agreeing that that's then becoming a somewhat simplistic perspective)

0
Evgeny Shvarov  Aug 14, 2018 to Warlin Garcia

You are right. But I believe there are cases.

I don't know why Eduard needs it but e.g. if you consider implementing licensing on your ObjectScript solution with controlling logic in ObjectScript I believe you don't want the method of license checking to be overloaded. Final method in deployed mode could be a solution in this case.

0
Chris Thompson  Aug 13, 2018 to Eduard Lebedyuk

This is probably an aggressive approach but you could try and do the following:

  • Create a code database with your class marked with final in it
  • Deploy said code with class marked as final and mount code as read only
  • Map the read only code database to the namespace that needs to access it

This should prevent a developer from being able to inherit from the class while still being able to access it. This is also an assumption that the meta data of class definition is included in the read only database. If it isn't, that's a very interesting flaw of what a read only code database means. 

0
Sergei Shutov  Aug 14, 2018 to Eduard Lebedyuk

I'm glad you found the solution, though still not sure about its usefulness :)

0
Warlin Garcia  Aug 14, 2018 to Eduard Lebedyuk

Agree on your "usefulness" statement. Making a class non-extendable breaks all recommended development practices. For example TDD. I won't be able to mock this class for testing purposes and will be forced to test with your class only (Granted Cache is not strong typed so in theory I have ways to bypass this limitation but still). 

Another recommended principal is to use composition instead of inheritance so in theory I could do exactly the same I wanted to do with your class (after you "lock it down") by making it part of another class without extending it. If a developer wants to get around your class ( don't know the reasons why) he/she can by other means unless you plague your code with a bunch of type checks (making Cache strong typed). 

Even if a developer get to extending your class, the fact you can't overload methods in Cache gives you a level of restriction when combined with deployed code and final as there's "no way" a developer can change your implementation so your code will still call your methods' implementation on a subclass.

All and all it seems you're going to extremes for no real benefit but since we don't know the whole picture maybe this effort is worth it. 

0
Sergei Shutov · Aug 13, 2018

I believe it's enough to have [Final] keyword set in deployed mode to give a developer a hint that this class should not be extended.

If you want to enforce this behaviour, I would add a check into each method as a first line , something like

if $this.%ClassName(1)'="My.Class" quit $$$ERROR(50000,"don't extend this class")

Since all code will be deployed, developers will not be able to remove this check easily.

You can also try to add a method-generator, I believe when you have a deployed class with method generator it will not be able to compile a subclass without method generator's source (though I'm not sure).

0