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!
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.
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
Yeah, looks like a kludge is the only way round.
Thanks.
Julian
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.
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?
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.
Perhaps you could withhold the interface definitions of those classes that
you don't want your customers to use.
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.
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
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.
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
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.
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.
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
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
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
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.
+1
yup .. I plead "Guilty""
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.
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.
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
Is there something actually parent child here or is it facade?
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
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
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.
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
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
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.
Do you supply source? If so, whatever you do, they can get around it if they want to.
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
Have you considered something as simple as a naming convention for classes which are not meant to be used except by the facade?
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.
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.
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.
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
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
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.
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'.
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.