preventing a child class from being created - Forum - OpenEdge Development - Progress Community

preventing a child class from being created

 Forum

preventing a child class from being created

  • 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.

    Consulting in Model-Based Development, Transformation, and Object-Oriented Best Practice  http://www.cintegrity.com

  • 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.

    Consulting in Model-Based Development, Transformation, and Object-Oriented Best Practice  http://www.cintegrity.com

  • 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.

    Consulting in Model-Based Development, Transformation, and Object-Oriented Best Practice  http://www.cintegrity.com

  • 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