preventing a child class from being created

Posted by jmls on 04-Jan-2012 22:06

I have a situation where I need to have several instances of a class (A) controlled by another class (B)

in other words, B will create numerous instances of A. B is not a super-class of A, nor the other way around.

My problem is that I only want Class A to be instantiated by Class B. I don't want any other class or procedure to be able to create them directly.

Is there any way of preventing this in the class definition ?

I was thinking along the lines of having the constructor of A taking a parameter of class B,but that seems kludgey and still wouldn't stop someone from doing

new A(new B())

Another thought was trying to determine the type of the class creating class A (like  the good old procedure-name(1..n) )

Anyone with a bright idea ?

Thanks!

All Replies

Posted by Admin on 05-Jan-2012 05:08

Ah, a use-case for friend classes :-)

I'd like to see them too. Friend classes with friend members. In your case, the constructor would be a friend member.

As a workaround you can provide a "secret value" to the constructor of B, that only A knows (which is difficult when you are providing open-source). When a differnt value is passed to the constructor then B will raise an error. It's a workaround, but certainly better than relying on passing a reference of A to B.

Posted by Peter Judge on 05-Jan-2012 07:28

jmls wrote:

I have a situation where I need to have several instances of a class (A) controlled by another class (B)

in other words, B will create numerous instances of A. B is not a super-class of A, nor the other way around.

My problem is that I only want Class A to be instantiated by Class B. I don't want any other class or procedure to be able to create them directly.

Is there any way of preventing this in the class definition ?

I was thinking along the lines of having the constructor of A taking a parameter of class B,but that seems kludgey and still wouldn't stop someone from doing

new A(new B())

Another thought was trying to determine the type of the class creating class A (like  the good old procedure-name(1..n) )

Anyone with a bright idea ?

Thanks!

Qustion first: In this case it makes sense that type A's constructor takes an instance of B. This shows me that there's a mandatory dependency between the 2 types/instances.

But I'm wonderin why new A(new B()) is bad? If new B() gives a properly-constructed, capable-of-working instance of type B, how is that code wrong?

Answering the "symptom" question more directly  ...

The Olde Program-Name() thing works, even in OO-land. It's a kludge, but I think the best one you have right now.

This code is modified from some AutoEdge code and so won't really make sense and can be cleaned up, but should get the idea.

    method protected logical InvokedByDesigner():

        define variable iLoop as integer no-undo.

        define variable lFromTypeConstructor as logical no-undo.

        iLoop = 1.

        lFromTypeConstructor = false.

        do while program-name(iLoop) ne ? and

            lFromTypeConstructor eq false:

            assign lFromTypeConstructor =

                            entry(1, program-name(iLoop), ' ') eq

                                   entry(num-entries(P.L.Class:GetClass('TypeB'):TypeName, '.'),

                                         P.L.Class:GetClass('TypeB'):TypeName,

                                         '.')

                   iLoop = iLoop + 1.

        end.

-- peter

Posted by jmls on 05-Jan-2012 08:29

Yeah, looks like a kludge is the only way round.

Thanks.

Julian

Posted by Thomas Mercer-Hursh on 05-Jan-2012 11:18

Have you considered that you are being too controlling?   If it only makes sense to instantiate an A in the presence of B, then why would anyone do otherwise?   If there is a genuine dependency, e.g., A needs to call methods on its parent, then passing in the instance of B is not only reasonable, but necessary.  And, like Peter, I wonder what is illegal or undesireable about the in-line solution, if it makes sense and works.

Posted by gus on 06-Jan-2012 10:21

Some of us arer wondering why you need this restriction. My initial reaction is that if A objects can be created only by B objects, then your definition of A is wrong. But what do I know?

Posted by Admin on 06-Jan-2012 10:29

gus schrieb:

Some of us arer wondering why you need this restriction. My initial reaction is that if A objects can be created only by B objects, then your definition of A is wrong. But what do I know?

My concern (Julian might have a different one) is that as a framework vendor, I'd like to be able to have a level of access between objects just in my framework. I want to be able to change B completly, maybe even remove it in the future and be sure, that this will have no negative impact on my customers.

Even if I'd have ever documented that B is intended to be only used internally, my customers might have used it. When I make changes to B this would affect their code. But if B would be of an access level (friend or package) I'd know for sure, that nobody is using it outside of my framework internals and I can change it without ever harming others.

It's a question of the interfaces. If you can only have a class being PUBLIC to access it from anywhere else, you cannot say: This class it not part of the public interfaces of the "library" but still can be used from multiple classes in the library.

Posted by gus on 06-Jan-2012 10:39

Perhaps you could withhold the interface definitions of those classes that

you don't want your customers to use.

Posted by Admin on 06-Jan-2012 10:43

gus schrieb:

Perhaps you could withhold the interface definitions of those classes that

you don't want your customers to use.

Security through obscurity?

Won't work. I tent to use well sounding names and OpenEdge Architect, sorry Progress Developer Studio for OpenEdge, has a class browser that also works on R-Code.

Posted by jmls on 06-Jan-2012 10:59

ok, here's the why :

I'm creating a set of sms classes, and as each provider has a

different API, I want to write a class for each provider, and then

write an interface class to allow the end-user to switch providers

without having to change their code.

This is all well and good - and it works fine.

So, I have a model with from, to , and message. You create a new sms

message, queue it and send when all messages are queued.

This works well for all providers. With the exception of one. They

require an xml message structured like

and the problem is that they require that there is only one sender

number per submission, whereas all other providers allow you to mix &

match the sender number.

This problem is easily solved by making this providers API class a

"child", and the top level API is now a placeholder, and instantiates

a new child API for each unique sender. When :Send() is called in the

top-level API, it now loops through all "child" API instances, calling

the :Send() method in them.

I don't want people to be able to use the child API classes, because

they can mix&match senders, and only the messages for the first sender

added are sent.

There are other ways round this, including rewriting the interfaces,

but I would rather not have to do that.

If I add the "secret" code (top level passes a code to the child

class), then this will prevent accidents from happening, and if

someone then deliberately alters the code to make it possible to

create a child class, then that is there problem

Posted by Thomas Mercer-Hursh on 06-Jan-2012 11:35

What you are describing sounds like a classic case of a small subsystem of classes with a common purpose accessed through a facade object.  One documents the facade and posts warning signs that using any of the contained classes other through the facade is unsupported and likely to lead to difficulties with future releases.  They may ignore your warning, but that is their problem.  Maybe if they get bitten a few times they will learn.  Trying to keep them from shooting themselves in the foot is an exercise in futility ... there are just too many ways for them to do it.

Posted by Admin on 06-Jan-2012 11:39

tamhas schrieb:

What you are describing sounds like a classic case of a small subsystem of classes with a common purpose accessed through a facade object.  One documents the facade and posts warning signs that using any of the contained classes other through the facade is unsupported and likely to lead to difficulties with future releases.  They may ignore your warning, but that is their problem.  Maybe if they get bitten a few times they will learn.  Trying to keep them from shooting themselves in the foot is an exercise in futility ... there are just too many ways for them to do it.

That does not convince me! It's not you that has to deal with those support requests. In other modern OO languages you have package level protection - for classes and individual members. And it would not be a bad thing for the ABL as well.

Nobody would force you to use it - if you think it's a bad thing to do.

Nachricht geändert durch Mike Fechner

Posted by Thomas Mercer-Hursh on 06-Jan-2012 11:58

Package level protection **might** be a solution, but only if it somehow captured the state of the package at compile time.  Otherwise, what is to prevent that devious developer from adding his or her own class to the package and gaining access that way.

Frankly, I wonder why all the concern.  If you give people source, then you ultimately have no control and they can do what they want.  If you give them only compiled code and document only the facade, how many people are even going to try to diagnose the internal structure of the package ... especially given the limited introspection possible in ABL?   And, if they are going to ignore your instructions and program to something other than the facade you have provided for them, then there are all kinds of bad habits they are likely to exhibit ... is it your job to prevent that? 

It is possible, Mike, that your practice is a bit different than most shops because you are producing frameworks that are being used by other shops to do development.  In the ABL world, I think that level of development on the other end is not typical.  But, even so, I think that one has to treat people as adults.  If you are providing support and discover they have gone around the facade, you tell them "This is not supported usage.   Fix it and then come back to me if you still have the problem.".  If they are a full fledged development shop and you are supplying a framework, you can't control the whole of their development anyway.  So, somewhere you need to draw the line.

Posted by Admin on 06-Jan-2012 12:06

With that argumentation, I'd also question the presence of PROTECTED members of ABSTRACT classes.

I am glad I have both.

And developers are using the class browser to look for classes and using code completion to look for methods, and when they find them, they use them. Both tools don't show my "not intended to use comments". So when I change what was not intended to be used they get disappointed. So would I.

Posted by Peter Judge on 06-Jan-2012 12:10

jmls wrote:

ok, here's the why :

background

I'm creating a set of sms classes, and as each provider has a

different API, I want to write a class for each provider, and then

write an interface class to allow the end-user to switch providers

without having to change their code.

This is all well and good - and it works fine.

So, I have a model with from, to , and message. You create a new sms

message, queue it and send when all messages are queued.

This works well for all providers. With the exception of one. They

require an xml message structured like

problem

and the problem is that they require that there is only one sender

number per submission, whereas all other providers allow you to mix &

match the sender number.

This problem is easily solved by making this providers API class a

"child", and the top level API is now a placeholder, and instantiates

a new child API  for each unique sender. When :Send() is called in the

top-level API, it now loops through all "child" API instances, calling

the :Send() method in them.

I don't want people to be able to use the child API classes, because

they can mix&match senders, and only the messages for the first sender

added are sent.

There are other ways round this, including rewriting the interfaces,

but I would rather not have to do that.

If I add the "secret" code (top level passes a code to the child

class), then this will prevent accidents from happening, and if

someone then deliberately alters the code to make it possible to

create a child class, then that is there problem

I don't understand why you'd want parent/child classes here. So you have an ISMSProvider  interface and SingleSenderSMSProvider and DontCareSMSProvider classes that implement this. Why wouldn't the Send() method is those classes be implemented differently, with the former class allowing one and only one sender, and the other not caring how many there are? This to me is the whole point of polymorphism. Alternatively or in addition to this, SingleSenderSMSProvider could inherit from DontCareSMSProvider.

Similarly, if you have a series fo fluent Builders, and you anticipate only allowing a single sender in a particular case, make the builders return interface types, not class types, and you can have validation earlier.

How you decide which class to instantiate is for a Factory of some sort.

Or am I missing the point completely?

-- peter

Posted by Peter Judge on 06-Jan-2012 12:13

And developers are using the class browser to look for classes and using code completion to look for methods, and when they find them, they use them. Both tools don't show my "not intended to use comments". So when I change what was not intended to be used they get disappointed. So would I.

"It's easier to ask for forgiveness than permission." Developers will use what they can, however they can get away with it, and after the fact ask you to change your behaviour/API. Saw it with ADM2 and Dynamics, and it happens with the ABL, and no doubt with every other language out there.

-- peter

Posted by jmls on 06-Jan-2012 12:19

in my case the problem for support may be

"It didn't send all the messages. It lost some"

"I only used the documented API. Honest"

On 6 January 2012 17:58, Thomas Mercer-Hursh

Posted by jmls on 06-Jan-2012 12:24

you may be - one of the methods of the interface is to import messages

from a spreadsheet and send them.

These records have from:to:message

depending on the "to" I may want to do a LCR , and send the message to

a different provider. Even if I don't, the import process should not

care about the "minor" problem of one provider with a different

requirement.

The client will not care about the provider issues. They just "want to

import the file" and send the messages.

We do have a factory to determine the provider. However, we may

require several providers during a batch process, and that's why the

child class structure works well. For the provider with the "problem",

the interface portion works exactly the same as all the other

providers, just internally it creates a separate "provider" instance

for each sender.

Posted by jmls on 06-Jan-2012 12:29

+1

Posted by jmls on 06-Jan-2012 12:29

yup .. I plead "Guilty""

Posted by Thomas Mercer-Hursh on 06-Jan-2012 12:35

Point being that they have no reason to ever look at the contents of the package.  The facade is the only thing they should ever look at.

Posted by Thomas Mercer-Hursh on 06-Jan-2012 12:42

So spend the effort that you are expending on trying to prevent them from accessing the stuff behind the facade on providing logging capabilities to tell you what happened.  That will tell you if they have gone around the facade as well as being useful for debugging a host of other problems ... including ones that might actually be your fault ... not that there would ever be any of those.

Posted by Peter Judge on 06-Jan-2012 12:44

you may be - one of the methods of the interface is to import messages

from a spreadsheet and send them.

These records have from:to:message

depending on the "to" I may want to do a LCR , and send the message to

a different provider. Even if I don't, the import process should not

care about the "minor" problem of one provider with a different

requirement.

Given that there's clearly a fair amount of complexity here, you may want to break apart your SMSProvider interface/classes some more. It sounds like there are a number of discrete pieces of functionality you're dealing with here, within a single provider. Maybe having smaller, composable pieces that you stich together to form a single Provider may be a worthwhile avenue to pursue.

For instance, it doesn't seem out of the realm of possibility that there will only ever be one single-sender provider. Furthermore, decomposition may (should, I'd say) make it easier to change the current single-sender to a multi-sender in the future.

However, that flexibility comes at the cost of more abstraction and complexity in your object graph construction and the infrastructure you use to manage that (IoC/DI or Facades or XML or whatever).

We do have a factory to determine the provider. However, we may

require several providers during a batch process, and that's why the

child class structure works well. For the provider with the "problem",

the interface portion works exactly the same as all the other

providers, just internally it creates a separate "provider" instance

for each sender.

Ah, OK, so parent/child here is more container/item. The problem child (as it were) is a problem regardless (and that is as it should be).

-- peter

Posted by Thomas Mercer-Hursh on 06-Jan-2012 12:44

Is there something actually parent child here or is it facade?

Posted by jmls on 06-Jan-2012 12:49

facade would probably be the correct term.

I was just being lazy ... as is my wont ...

On 6 January 2012 18:44, Thomas Mercer-Hursh

Posted by jmls on 06-Jan-2012 12:49

You are wrong on so many levels here.

1) there's not much effort behind

ClassA : foo = new B("magickey")

ClassB: constructor: if magickey ne "magickey" then return new

AppError("not allowed",0).

2) I never have faults in my code. It's the users that make the code not work.

On 6 January 2012 18:42, Thomas Mercer-Hursh

Posted by Admin on 06-Jan-2012 12:51

tamhas schrieb:

Point being that they have no reason to ever look at the contents of the package.  The facade is the only thing they should ever look at.

I don't get that.

If everything is PUBLIC, it shows up in the class browser.

There is no way to distinguish. So how can they tell if what they are using is not intended to be used.

Posted by Admin on 06-Jan-2012 12:53

jmls schrieb:

2) I never have faults in my code. It's the users that make the code not work.

On 6 January 2012 18:42, Thomas Mercer-Hursh

I'd love to be you

Posted by Peter Judge on 06-Jan-2012 12:54

2) I never have faults in my code. It's the users that make the code

not work.

That’s because they don't have your computer.

-- peter

Posted by Tim Kuehn on 06-Jan-2012 13:08

jmls wrote:

You are wrong on so many levels here.

+10


The book "Implementation Patterns" (http://tinyurl.com/6mmpk7z) has a whole section on framework development, and letting framework users get access to the guts of your framework -will- result in a number of serious problems as the framework evolves over time.

Heck, even writing a well-thought framework can be a real problem as the framework evolves, particularly if one wants to keep it backwards compatable with prior releases.

Posted by Thomas Mercer-Hursh on 06-Jan-2012 13:47

Do you supply source?   If so, whatever you do, they can get around it if they want to.

Posted by jmls on 06-Jan-2012 13:54

yes, so no amount of logging would work, either.

I just want to stop the "casual" coder using the class browsing to

find the child classes and using them for nefarious purposes. Like

I've just done with the json array

On 6 January 2012 19:47, Thomas Mercer-Hursh

Posted by Thomas Mercer-Hursh on 06-Jan-2012 14:03

Have you considered something as simple as a naming convention for classes which are not meant to be used except by the facade?

Posted by Admin on 06-Jan-2012 14:42

tamhas schrieb:

Have you considered something as simple as a naming convention for classes which are not meant to be used except by the facade?

Uh, yes! Naming conventions. Why didn't I think of that earlier :-)

Probably because it's equally effective as a speed limit. As long as it's not enforced. Other OO-Languages enforce that by a wider variety of protection levels. ABL should do so too. Everything else is just a workaround. Like the need to build your own OO-serialization.

Posted by Thomas Mercer-Hursh on 06-Jan-2012 14:57

If you are supplying source *nothing* is going to prevent them from doing what they want.

The naming convention would at least tend to prevent accidentally using one.

Posted by Thomas Mercer-Hursh on 07-Jan-2012 13:41

I have had a couple of exchanges on this topic with H.S. Lahman.  While he is sympathetic with wanting to keep a developer from shooting him or herself in the foot, his inclination is to think that kludging up the code with run time checks is going to obscure the clarity of the functioning of the code, so if one is going to do anything at all, it needs to be a compile time check.  The problem with the usual suggestions for compile time checks is that they are either not really secure (package security can be violated by putting a new class in the directory) or subject to abuse (friend).  His preference ... which hasn't been implemented in any language, afaik ... would be for a declarative solution, i.e., one would define a subsystem and then explicitly list the members of the subsystem.  The compiler would then reference this to enforce membership ... although it isn't entirely clear how that would work.

Even if one were to convince a vendor to implement such a thing, it would obviously do no good if one was supplying source.

And, if one had such a thing and it worked, it would have questions and compromises.  Among other things, it would limit code reuse so one would have to be careful not to include in such a structure any class which had potential other utility.

Frankly, I still think the benefits are dubious.

Posted by jmls on 07-Jan-2012 13:49

I've put a runtime check in (one line of code. Well commented ) to

stop the casual "wonder what would happen if I were to use this

class".

If they then want to go and alter the source, then as far as I am

concerned , they have bought the gun, loaded the bullets and shot

themselves in the foot with no help from me at all

On 7 January 2012 19:42, Thomas Mercer-Hursh

Posted by Admin on 07-Jan-2012 13:53

His preference ... which hasn't been implemented in any language, afaik ... would be for a declarative solution,

Then this theoretical approach wouldn't help anyone in practice.

Friend, package or library level protection has proven to be useful in the real world, and your continous remark of potential abuse seems very abstract and irritating to me. Same with your repeatedly remark that all this makes no sense with open source. To shoot yourself in the foot when all intended to be used interfaces were public and all internally intended interfaces where library or friend protected a developer would have to modify my source code. In this case he's aware that any new code drop from my will have serious consequences to his work. Developers are not stupid. They usually know what they are doing.

BUT all things that we intended for our internal use, but required for communication between two of our classes would not need to be PUBLIC. So without modification of our source, he won't have acces to them. For me that's a practical (and in currently very successful and widely used OO languages implemented) declarative approach!

Of course library level protection will only make sense when the role of a .pl and how it's created will change: It should be integrated into the build/compiler process.  But that's certainly something the vendor of the ABL might implement in the near future.

Message corrected by Mike Fechner

Posted by Admin on 07-Jan-2012 13:53

If they then want to go and alter the source, then as far as I am

concerned , they have bought the gun, loaded the bullets and shot

themselves in the foot with no help from me at all

That's exactly what I think.

Posted by Admin on 09-Jan-2012 11:43

Can't help not to ask why do you have that provider with the "problem" in the first place?

If you have a provider interface then every provider should fully implement it, well it's true that even if the interface specify multiple senders should be supported a 'single-sender' provider can still exist proving that all interface methods that try to set multiple senders will throw an exception... but as you seems to already have a 'facade' that work around this single-sender problem then I would let that 'single-sender' object as-is and don't force it into a 'provider' even if it does almost the same things and only let the facade object to implement the provider interface. That way the half-way provider doesn't even get to be considered as a 'provider' since it does not implement the interface, the factory will probably be happy with it while it can still be used standalone proven not being abused (when something is not supported it should throw an exception)... who knows, at some point you might even need it in some other place

Not very fun of package or friend visibility, what would be nice to see is the evolution of pl libraries is something similar to Eclipse plug-ins... that way one can control 'visibility' by exporting only certain 'packages'.

Posted by Thomas Mercer-Hursh on 09-Jan-2012 11:50

Even the ability to freeze a pl would be a positive move.   That any library level security would be pretty effective, although it bothers me that the library issue is run time instead of compile time.   Add selective outside visibility to the library and it seems like one would have covered a lot of ground.

Not that I would ever use any of it, mind you.

This thread is closed