PDA

View Full Version : Is this a good technique for implementing an upgradable/replaceable class?




PrismaticRealms
May 2, 2012, 12:49 PM
Hi all.

I am writing an application in Objective-C (using the Cocoa frameworks.) At one point, I needed to come up with a technique to allow a class to be extended in the future without affecting any other classes that reference it. The requirements are:


The class (herein referred to as ClassA) will only have class methods, and cannot be instantiated.
Define a protocol that ClassA adopts, and in which future derived classes or completely new replacements classes must adopt.
Need the ability for me or others (as painlessly as possible) to either provide a new derived class using ClassA as a base, or a completely new replacement class not derived from ClassA, but which adopts the protocol.
Need a way for the owner of the project (me, in this case) to (at any time) select either ClassA or any of the future derived or replacement classes, as the class to use in the application. This selection is done at compile-time, not at run-time, via twiddling some #define somewhere or what not.
The other classes used throughout the project that use the selected class should not need to be modified in any way, or be concerned about which version it is using.


Ok, by no means are these requirements completely written in stone, but they are just a way for me to convey what I'm trying to do.

So, what I did works, but I have a feeling there is probably a better way. How I did it is this:


I created a protocol defining the class methods that are required and put it in it's own .h file.
I created the ClassA class which adopts the protocol and defined all the required class methods.
As a test I created a new class called ClassB which inherits from ClassA and defines only the class methods that it wants to replace.
As another test, I created yet another class called AlternateClass which inherits from NSObject and adopts the protocol. It defines all of the required classes.
Finally, and this is the key, I created a .h file (mylib.h) that contains nothing but some pre-processor macros that #defines some symbols representing the different classes in existence and an #if to import the required file and define which class is being used.


Here is my lib.h:
//Define the libraries.
#define MY_CLASSA_LIB 1
#define MY_CLASSB_LIB 2
#define ALT_CLASS_LIB 3

// Twiddle this value to select a library to use.
#define LIB_TO_USE ALT_CLASS_LIB

#if LIB_TO_USE == MY_CLASSA_LIB
#import "ClassA.h"
#define MyClass ClassA
#elif LIB_TO_USE == MY_CLASSB_LIB
#import "ClassB.h"
#define MyClass ClassB
#elif LIB_TO_USE == ALT_CLASS_LIB
#import "AltClass.h"
#define MyClass AltClass
// Add new elif sections here for other future classes.
#endif

This works great. And throughout the rest of the application, all I'd need to do to use a method from the selected class is use the MyClass macro defined above like this:

[MyClass callMethod1];

As good as it works, I'm not a huge fan of having to use a Macro masquerading as a class name. I am wondering if there is a way to maybe use a factory class to give me the correct class name. Something like:

// Remember, the class only contains class methods,
// so a factory class that returns an object won't work here.
class MyClass = [MyLib theChosenClass]; // Not a valid Obj-C statement.
[MyClass callMethod1];

Or maybe have each of the classes compiled into it's own external library and then choose which library file to use via the project compilation options. Though not sure if this is possible or how to begin approaching this.

Anyone have any suggestions?
Thanks in advance!



chown33
May 2, 2012, 05:29 PM
I think focusing on the class is misguided. Focus on the object.

Step 1: Define the rules for using the methods.
There must be an object that implements a protocol (an object, not a class). That object is stored in a global variable. Any code that wants to use any of the methods in the protocol must use that object.

Step 2: Define the object's inheritance relationships.
There can be any number of classes that provide an implementation of the protocol. It need not be a single class or one of its subclasses. Therefore, there are no required inheritance relationships. That's one reason for using a protocol: subclass/superclass isn't required. You can define a base class, but it's not required for things to work.

Step 3: Define how the object is instantiated.
In this assignment:
someObjectVariable = [[SomeClass alloc] init];
the SomeClass expression can be a static class name, or it can be a Class object obtained some other way. There is a function that returns a Class object when given an NSString. Example:
someObjectVariable = [[NSClassFromString(@"NameHere") alloc] init];

If the string is a literal, then there's not much difference than using a static class name (there is some difference, though). If the string is a variable, however, then the result is a dynamically chosen class that is instantiated. That means you can instantiate different classes simply by changing what the string is.

A compile-time choice of class is as simple as defining one string-literal (three examples, for three different compiles):
#define MAGIC_CLASS @"MagicalClass"
#define MAGIC_CLASS @"MysticalClass"
#define MAGIC_CLASS @"MundaneClass"

...

globalMagic = [[NSClassFromString(MAGIC_CLASS) alloc] init];


4. Define the global variable.
id<YourProtocolName> globalMagic;

This isn't limited to a single protocol. YourProtocolName can be a comma-separated list of protocols (in case it matters to the design).

Going thru your list of requirements, #1 isn't necessary (because globalMagic is an object, not a Class), and #2-5 are met.


Summary: Use a dynamically chosen class name (which you're currently using a #define for), that implements a protocol (which you haven't shown or described in any detail), to provide the implementation for some globally available functionality.

For comparison, in Java, this could be done with the Class.forName() static method, passing it a classname string. The resulting Class object then has an instance made from it, and that instance is stored in a global variable. A suitable Class must implement a particular interface, because the instance obtained from the class will be cast to the type defined by that interface. It is that specific interface that is used by the entire rest of the program.


The above assumes I've correctly understood the problem you're really trying to solve, by reading the description of the solution you implemented. Frankly, I read your post through a couple of times, and I'm not entirely certain that what I describe above is solving the same problem as your solution. I think it does, but you have a couple of requirements that aren't really required (like it being a class with class methods), so I could be wrong.

PrismaticRealms
May 2, 2012, 07:04 PM
chown33, many thanks for the time you spent reading and replying to my post. I'm sorry, I tried to explain it as concise as possible, which caused the post to be more verbose than I intended. Not to mention some typos (that I've now corrected in red) that probably hindered your understanding a bit, but I believe you understood it perfectly!

Your response does open up a couple of doors for me. Specifically the NSClassFromString() function which I must have completely forgotten about. That is what I think I was getting at with the "class MyClass = [MyLib theChosenClass];" pseudo code I wrote in my original post. After peering into its docs and branching into other related commands' docs and sample projects, I am now informed about it. So thanks for that! Now I can do this:

Class libClass = NSStringToClass(chosenClassString)
[libClass callClassMethod]; // Still calling a class method here.


which works great, however Xcode's code completion feature doesn't seem to work with this approach which is kind of annoying.

Moving on... you also suggested using a global variable to store the entry point into the library class (which needs to be instantiated), however, I really wanted to keep ClassA as a non-instantiatable class only for the sake of simplicity. ClassA has no state, and therefore has no attributes, and therefore does not really need to be instantiated. The class methods provide all of the needed behaviour. The protocol only defines class methods (no instance methods). Plus, I really dislike using global variables. :)

But you've got me thinking... Maybe I should allow ClassA to be instantiated as it MAY have attributes in the future (through some derived class, or maybe even at some point in my development of ClassA which isn't complete yet.) And it is perhaps a bit more in style with how Cocoa is written? I'd hate to realize in the future that I'm going to need attributes in the class and then change all the code that sends messages to this class to instead instantiate it (or use your global variable idea) and call instance methods.

Not sure, I'll have to ponder that a bit more. If you have any further words of wisdom, I'd love to hear 'em.

chown33
May 2, 2012, 07:31 PM
Now I can do this:

Class libClass = NSStringToClass(chosenClassString)
[libClass callClassMethod]; // Still calling a class method here.


which works great, however Xcode's code completion feature doesn't seem to work with this approach which is kind of annoying.

Xcode can't do code completion because the Class type doesn't have your protocol methods. What would happen if the type were declared as id<YourProtocol>?


Moving on... you also suggested using a global variable to store the entry point into the library class (which needs to be instantiated), however, I really wanted to keep ClassA as a non-instantiatable class only for the sake of simplicity. ClassA has no state, and therefore has no attributes, and therefore does not really need to be instantiated. The class methods provide all of the needed behaviour. The protocol only defines class methods (no instance methods). Plus, I really dislike using global variables. :)

Then make it a singleton. That's a standard Cocoa pattern (https://developer.apple.com/library/mac/#documentation/cocoa/conceptual/CocoaFundamentals/CocoaDesignPatterns/CocoaDesignPatterns.html). A global object was just the simplest way to explain it, and to point out that it doesn't need to be class methods and a Class object. In other words, a single object suffices; forcing it to be a Class with class methods is an artificially imposed restriction.

I don't see how a non-instantiable class makes things simpler. You're requiring an implementation to be a Class, when that's not necessary to implement the protocol.

NSFileManager is an example of a class that has both a shared singleton, and useful per-instance behavior. NSFileManager used to be only a shared singleton (non-instantiable class). So learn from that and don't take that, um, path.


But you've got me thinking... Maybe I should allow ClassA to be instantiated as it MAY have attributes in the future (through some derived class, or maybe even at some point in my development of ClassA which isn't complete yet.) And it is perhaps a bit more in style with how Cocoa is written? I'd hate to realize in the future that I'm going to need attributes in the class and then change all the code that sends messages to this class to instead instantiate it (or use your global variable idea) and call instance methods.

If you think a Class can't have attributes or state, you've forgotten about associated objects, aka associative references (http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/objectivec/Chapters/ocAssociativeReferences.html). You're also neglecting static variables.

There is no way to prohibit this. If you prohibit it by forcing the object to be a Class, someone who needs state will simply work around your intended prohibition, probably using some horrible kluge. So attempting to prohibit it by forcing the object to be a Class is just an unnecessary restriction on the implementation. Why? Exactly what horrific consequences are you trying to prevent?

Personally, I try to design extensible things with as few barriers to extensibility as possible. No unnecessary restrictions, no unnecessary features. Start simple, extend later.

If extensibility is important enough to consider in the design, then it means you haven't foreseen every use. This suggests exploration is one of the goals. So why impose unnecessary restrictions on exploration, when you're admitting up front that not everything is foreseen?

One consequence of exploration is that people do unexpected things, even stupid or dangerous things. That's what happens. Accept it.

Mac_Max
May 2, 2012, 09:04 PM
When this is a concern I try to make the class as generic as possible and don't expose too many internal data structures. One time I ran into a problem where I had originally used a dictionary to store values and I exposed that dictionary to my app. It turned out that later demands made a class for the value pair more appropriate. Replacing that class was rather difficult compared to simply defining a key/value pair getter/setter setup for the public interface. I created an adapter for the class and smoothed out the differences with it until I could replace old implementation dependent code.

Otherwise if you create an interface/protocol/abstract base class thats simple/generic enough and really captures the essence of what your data or logic is supposed to do, you usually won't run into too many problems that can't be fixed with a little bit of code. When you run into trouble, its usually because you didn't understand the problem at the start or the requirements dramatically changed. In the latter case, I'm hesitant to force any sort of inheritance/pragma based fix.

PrismaticRealms
May 2, 2012, 11:00 PM
chown33, once again, thanks for your response.

Xcode can't do code completion because the Class type doesn't have your protocol methods.
Oh, I completely understand and agree with why code completion doesn't work in that case. But it's still annoying and makes me not want to do it that way. :)

What would happen if the type were declared as id<YourProtocol>?
As expected, code completion works in this case.

I don't see how a non-instantiable class makes things simpler. You're requiring an implementation to be a Class, when that's not necessary to implement the protocol.
I understand that a non-instantiable class is not required to implement the protocol, but that's not the reason why I self-imposed that rule. I was hoping it would make things as "simple" as possible for the user. And by simple, I meant that the user of the class does not need to create instances of the class in order to benefit from it's behavior. I wanted it to work similarly to how the atoi() and abs() functions were just "there" without needing any prior setup.

Then make it a singleton.
I am familiar with the singleton design pattern and this is a good idea for this class... however it means that any programmer providing a replacement class for ClassA would need to implement singleton behaviour in his class too... and I would need to provide an interfacing class (with a constant name) that acquires the singleton's class instance (which is dynamic as which class was used depends on which library was compiled.) Ups and downs. Always ups and downs.

But anyway, you are correct that if a single object will suffice, then forcing it to be a Class is not necessary. The upside to forcing the Class is that there is less effort on the part of the user of the class. Not much effort, I agree, but still less... and maybe not worth spending too much more time on it.

If you think a Class can't have attributes or state, you've forgotten about associated objects, aka associative references (http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/objectivec/Chapters/ocAssociativeReferences.html).
I actually haven't forgotten about this... I didn't even know about it! So thanks, I'll be reading up on this.

You're also neglecting static variables.
I dismissed static variables without much thought, but you are correct, state can be added to a class using these especially if the primary purpose of the class is mostly static. I'll consider it.

There is no way to prohibit this. If you prohibit it by forcing the object to be a Class, someone who needs state will simply work around your intended prohibition, probably using some horrible kluge. So attempting to prohibit it by forcing the object to be a Class is just an unnecessary restriction on the implementation. Why? Exactly what horrific consequences are you trying to prevent?
The thing is, I wasn't even treating my approach as a restriction, so I wasn't really trying to prevent anything. I was treating it as more of a helper. Specific guidelines to make it easy to provide overridden behaviour by other programmers while making it easier for the user of the class to use it. If the programmer wants to wander outside of these guidelines then it's on him. But I understand what you're saying... without the foresight of knowing why the programmer wandered in the first place, why not just give him all the tools available...

One thing I noticed with the way I have it setup now is that if a programmer wanted to provide a completely new replacement class for ClassA, he need not even implement a class that conforms to the protocol I defined! This is not good. I like the way defining the global variable as id<MyProtocol> enforces this.

I know that I need to modify my approach in order to better balance flexibility, functionality, cleanliness, conformity and ease of use in my library because right now I'm not completely satisfied with it. I think I've been given a few options to work it out, so many thanks for that!

chown33
May 2, 2012, 11:12 PM
I am familiar with the singleton design pattern and this is a good idea for this class... however it means that any programmer providing a replacement class for ClassA would need to implement singleton behaviour in his class too...

Not true. That's what NSStringToClass is for. All the user needs to do is supply a different string.

There is exactly one singleton object, which your code enforces. The single instance is your responsibility. The exact class that is instantiated is configurable. That is the user's responsibility/option.

and I would need to provide an interfacing class (with a constant name) that acquires the singleton's class instance (which is dynamic as which class was used depends on which library was compiled.)
No constant name is needed. You simply have a string whose name is used to make a Class. That class is used to instantiate an object.

PrismaticRealms
May 2, 2012, 11:17 PM
When this is a concern I try to make the class as generic as possible and don't expose too many internal data structures.
...
Otherwise if you create an interface/protocol/abstract base class thats simple/generic enough and really captures the essence of what your data or logic is supposed to do, you usually won't run into too many problems that can't be fixed with a little bit of code.
Hi Mac_Max. I completely agree, and that is always my intent as well. However, I would say not to expose ANY internal data structures (that are vital to the stability of your class). The only things you'd want to expose are the attributes that the user should be allowed to change, and only then through controlled getter/setter methods. In my case, my class has no attributes and at this point in time, I do not think it will ever have.. but you know the saying. Never say never.

When you run into trouble, its usually because you didn't understand the problem at the start or the requirements dramatically changed. In the latter case, I'm hesitant to force any sort of inheritance/pragma based fix.
Are you referring to the way I am trying to make my routines flexible by allowing either this class or that one to be plugged in? If so, don't think of it as a fix. I am not providing this flexibility as a fix, but as a means to enhance the functionality in the future. Think of it as replacing the engine in your car. At some point in the future, someone may end up inventing a new engine that fits your car that is faster and more efficient, so you may want to replace that engine (or parts of it) with the newer pieces. That is the goal in my approach. Not because requirements have changed.

thundersteele
May 3, 2012, 12:46 AM
This might be useful:
http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/chapters/occategories.html

PrismaticRealms
May 3, 2012, 01:30 AM
This might be useful:
http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/chapters/occategories.html

Not sure they can... any ideas how?

gnasher729
May 3, 2012, 05:21 AM
Looks like you are looking for the "factory pattern"?

PrismaticRealms
May 3, 2012, 06:48 AM
Looks like you are looking for the "factory pattern"?

Not quite... the factory class is usually meant to provide instances of a class (although I don't see why it can't provide Class-types instead) but the issue wasn't really to have the ability to create objects on the fly (as there will only really be one object to get depending on which class I decide to compile in the project.)

The issue that was bugging me most was how to provide the users of the class an "easy" way of getting at the methods I want to expose, while making it easy for me, the programmer of the class, to change which class contains the methods to expose without making lots of changes to the rest of the application. So, say I initially wrote class A and then developed a new class B to replace it, I just wanted to plug that in, take class A out and then flip a switch somewhere indicating that class B is now the one being used, and that's it. All calls to the class methods will stay the same.

So, a singleton class I think makes more sense here. But even using a singleton there are other details to consider in order to make it easy to use. And I think I figured out something that works for me without being too messy. I'll post it sometime later today.

gnasher729
May 3, 2012, 09:11 AM
Not quite... the factory class is usually meant to provide instances of a class (although I don't see why it can't provide Class-types instead) but the issue wasn't really to have the ability to create objects on the fly (as there will only really be one object to get depending on which class I decide to compile in the project.)

Factory would usually provide instances of _some_ class, and you don't know and care which one. Of course any instance would have the same behaviour, like responding to the same messages, but it could be a completely different class on every call. You declare the factory method either to return an instance of some class, so it can return an instance of any derived class, or you declare it to return an id implementing some interface, in which case it can return an instance of any class implementing the interface.

Unfortunately you haven't said yet what you want to achieve, but how you plan to achieve it.

thundersteele
May 3, 2012, 12:18 PM
Not sure they can... any ideas how?

Honestly I don't fully understand what you are trying to do. I think others here are more qualified to help.

lee1210
May 3, 2012, 08:41 PM
You could obscure individual implementations via a Class Cluster:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/CocoaObjects.html%23//apple_ref/doc/uid/TP40002974-CH4-SW34

Having a switch for which concrete instance gets returned should be trivial.

-Lee

Mac_Max
May 3, 2012, 09:48 PM
Hi Mac_Max. I completely agree, and that is always my intent as well. However, I would say not to expose ANY internal data structures (that are vital to the stability of your class). The only things you'd want to expose are the attributes that the user should be allowed to change, and only then through controlled getter/setter methods. In my case, my class has no attributes and at this point in time, I do not think it will ever have.. but you know the saying. Never say never.

I tend to be fairly pragmatic and try to determine what code will likely see actually reuse and what will die with the project. Stuff that will get reused I spend more time on obviously. It's a balancing act between my the mantras of "Don't be lazy" and "Get $&*^ done." My example was a good case of getting it completely wrong haha.

Actually, as an aside, I ended up rewriting that class in C++ so I could share the codebase between the original C#/WPF Windows app and iOS version that came later. The customer requirements changed on me completely. I might end up writing a lot more C++ in future freelancing projects as a result.

Are you referring to the way I am trying to make my routines flexible by allowing either this class or that one to be plugged in? If so, don't think of it as a fix. ... Not because requirements have changed.

No just general advice since I don't know your level of expertise.

PrismaticRealms
May 4, 2012, 10:03 AM
You could obscure individual implementations via a Class Cluster:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/CocoaObjects.html%23//apple_ref/doc/uid/TP40002974-CH4-SW34

Having a switch for which concrete instance gets returned should be trivial.

-Lee

Thanks for the idea, I'll look into it to see if it can provide some benefits. I've never worked with clusters before.

Sydde
May 4, 2012, 02:27 PM
The point of methods is two-fold: they can be inherited, and the provide the function with a context frame (the object instance). In the case of class methods, there is no instance context, the only context that matters is the statics. In this respect, they amount to little more than an abstract way to reach functions.

So, if you want to make your class methods dynamic (replaceable), one option would be to have a collection of function vector statics that the methods would use to call the actual working functions and provide methods for changing those vectors. Thus, your class methods could be changed surgically: instead of a replacement class, you or the class user could replace only the methods (functions) that have been rewritten.

Just a thought.

PrismaticRealms
May 9, 2012, 08:56 AM
The point of methods is two-fold: they can be inherited, and the provide the function with a context frame (the object instance). In the case of class methods, there is no instance context, the only context that matters is the statics. In this respect, they amount to little more than an abstract way to reach functions.

So, if you want to make your class methods dynamic (replaceable), one option would be to have a collection of function vector statics that the methods would use to call the actual working functions and provide methods for changing those vectors. Thus, your class methods could be changed surgically: instead of a replacement class, you or the class user could replace only the methods (functions) that have been rewritten.

Just a thought.

I'm not 100% sure what you mean by "function vector statics" but if I understood some of what you said, in order to provide state in a static-only class, I would need to provide class methods that get/set the static variables that I need to provide that state. I fully agree with this and will end up using this technique as I've decided to go with a completely static class. However, I don't think I fully understood everything you said, if you can explain a little more, I'd appreciate it.

----------

Hi all. Ok, I'm back. Thanks for all of your ideas. After trying a few solutions, including using a global variable, a singleton class, a full fledged instantiatable class, typedefs and Categories, I have decided to return to my initial design of using a #define directive to determine which class will be compiled into my application. That class will contain nothing but class methods and will not be instantiatable. This seems to work best for me in this case.

So, recall that I can have 1 or more class libraries that must conform to a defined protocol, but only ONE of which will be compiled into the application. So, I needed a way to easily select which one I wanted to compile and make it simple for the rest of the application to use that library seamlessly and without knowing which library was being used.

Step 1
Add the class library(ies) files to the project. This could be classes that I've developed, or 3rd party classes from others. Classes must conform to a defined protocol. The designated class within the library can have any name.

Step 2
Define a single way for the application to interface to whatever class library I've decided to compile into my application, without my application's code caring what library was used. This is what I wanted to make sure was simple and elegant.

// MyClass.h
// A little simpler than my initial #define design.
#import "SomeonesFunkyClassLibrary.h" // Change to whatever library I want to use.
#define MyClass SomeonesFunkyMainClass // Change to whatever the main class is within the library.

And that's it!

And throughout the rest of my application, I just refer to "MyClass". For example:

// test.m
#import "Test.h"
#import "MyClass.h"

@implementation Test
- (void)testMethod
{
[MyClass callMethod];
}
@end

Nice and simple. To address some of the concerns I had with using this approach:

Concern: I didn't like the fact that a macro was masquerading as a class.
Solution: I have learned to accept this. I find that I made this concern sound worse than it actually was. The compiler actually replaces the instances of "MyClass" with instances of "SomeonesFunkyMainClass" (or whichever class I've decided to put in there) during compilation so in actuality, my program code DOES refer to the designated class, and I'm fine with that line of thinking.

Concern: Someone (or I) can provide a replacement class library that does not conform to the protocol I've defined, and the above code I implemented does not enforce it.
Solution: Somewhere at the beginning of my application, maybe in the applicationDidFinishLaunching: method found in the AppDelegate class, I will send a conformsToProtocol: message to MyClass. This will alert me that the library I've chosen to include in my application doesn't conform to the protocol I've defined.

Concern: A 3rd party class replacement provider will do something kludgy in order to bypass the static class nature of the library.
Solution: I'm not so much concerned with this one. A 3rd party can be as kludgy as they want, but as long as they deliver a working bolt-on library, then I'm happy. Besides, this library is not so complicated to require major redesigns to provide the required functionality. Each method is designed to stand on it's own: to take the provided inputs, process them and provide a single output value. If the 3rd party really does need the use of an instantiated object, then they can provide their own helper classes to do so.

Concern: The class cannot be instantiatable and I'm worried that in the future it'll be difficult to extend.
Solution: In the past few days, I've actually come across a situation where attributes may be required going forward. But I've determined that any such requirement (i.e. state) can be solved with static variables. As there will only be one "instance" of this class (actually zero instances) there will be no need for class attributes (which by their very nature implies multiple objects.) Static variables will work nicely.

Well, that's it. It took a little while and I learned a few things along the way, but in the end, I am happy with this design. Thanks again!