A class is nothing more than a possible state of an object (this is the short-form description). Choosing which lives in the software is a software-design matter. MichaelFeathers wrote up this example:
"Is there really a difference? This is a silly but possible abstract base:
class BornOnTheFourthOfJuly { protected: const Date m_birthDate; BornOntheFourthOfJuly () : m_birthDate(7,4) {} };"If Person inherited BornOnTheFourthOfJuly:
BornOnTheFourthOfJuly *punkSam = new Person("Sam");"And, couldn't we also speak of the class of all people born on the fourth of July if we did this?
class Originated { protected: const m_date; Originated(Date date) : m_date(date) {} };-- AlistairCockburn
The only problem with this approach is that your statically typed language makes it very difficult. And even in a dynamic language, there are practical limitations about what properties you can represent with type. So the choice of what to represent using type boils down to answering this question: on what properties do you want to be able to do dynamic dispatch?
If you think it somehow helps your program's design to be able to specialize methods to people born on the fourth of July, then by all means try it. But I suspect that the coincidence of being born on a particular date is not significant enough to give rise to a type category that is practically useful.
Any subset can be a subtype, but in programming, a type is something special; we reserve it for certain properties only, for various reasons.
It would be nice to have a language in which any predicate can be treated as a type. In fact logic programming languages are like this, essentially. You don't need a class bornOnTheFourthOfJuly because you can have that as a predicate, and that is good enough.
You can specialize a method to objects for which that predicate is true, and the dispatch mechanism simply has to evaluate the predicates as part of its method search. You then need ways of expressing conflict-resolving rules that a certain predicate is more important than another. (Under ordinary classes, we have rules like precedence among multiple bases).
Why don't we just treat arbitrary predicates as types? Probably because it's expensive to base method dispatch on the evaluation of properties.
-- KazKylheku
Don't you mean that every (instantiable) class defines the set of all possible states for its direct instances?
The other way around. For any piece of state, there are many objects that could have that state. So they all belong to the class of things that have that state. ergo, you could just have created the class for that, and instantiated from that.
More real example. I had American Express card. Was "green" card holder. Got promoted to "gold". Never got promoted to "platinum". Was briefly "delinquent". The question for the designer is, should green/gold/platinum/delinquent be states of a card or cardholder, or are they classes, with their own protocol?If they are states, then it becomes really easy to change states, but awkward to handle the different protocols they support. If they are classes, then it becomes awkward to change classes, but easy to deal with the different protocols. Either choice is valid, and which you choose is not a matter of "analysis", it is a matter of "software design", i.e., a valid way to choose is whichever way makes the software work out better.
I was struck by how common this class changing stuff is in business software, compared to uncommon it is in scientific software. --AlistairCockburn
A type is not a set of potential states for an object. A type is a set of domain values. If objects are mutable, they can hop from one value to another in the same domain, or they can change to a different domain (type). Various programming languages impose various restrictions on the theory, in order to make some operations faster, or for ease of implementation.
This is the point behind JamesOdell's approach of DynamicClassification.
Yes, Alistair! The class isn't the thing, the thing is the thing. The class is just a programmer's tool to represent the idea that the software embodies. Someone wiser than I once said "inheritance is just a programmer's hack".
A place where SmalltalkLanguage shines, among so many, is that it has no confusing "type inference" rules tangled up with its classes. Of course, like a chain saw, this has to be used with caution. And goggles. But the tool can't get in the way as much because it doesn't pretend to know what you meant. --RonJeffries
In a programming language, type is just a special way of representing the predicate that an object belongs to a set. Usually, the representation is efficient, and under the control of the language implementor. Therefore operations which depend on the predicate, like dispatching a method, can be made to run fast---or at least faster than if users had to invent their ad-hoc type system. Secondly, a type system provides standard functionality that users would re-invent. In large programs, there would probably arise multiple incompatible reinventions of type systems. A type system serves as a kind of standard library for classifying objects, validating set membership, and specializing operations based on the classification. Because it's built into the language, it can be subject to aggressive, program-wide optimizations. Other than that, type is nothing more than some bits that are part of the object. A few bits might mark an object as an integer, yet another bit as a negative integer, and a few more bits as the magnitude 3. All of these are just properties of the object that arise upon interpretation of its bit pattern under the guidance of a representation scheme.
In the American Express card example cited earlier, I don't see an either/or situation. Quoting the author's question:
The first mentioned discriminant of the cardholder's state is the type of card (e.g., green/gold/platinum) which could be represented simply as an one of N mutually exclusive choices with an enumerated instance variable. The variable could contain mutually exclusive strings like "green" or "gold", or encoded values like 1, 2, or 3, and these could be used procedurally to make protocol decisions. However, the more obvious alternative is to bestow the cardholder class with a cardtype attribute that refers to an object which understands the associated protocol. One solution is for the cardtype attribute to accept any object that implements the cardtype interface, such that separate and otherwise unrelated classes (e.g., green/gold/platinum, plus new ones) could be responsible (as delegatees) for implementing the appropriate protocols. (I would envision the cardtypes as further delegating to underlying protocol objects, especially if such protocols could be reusable on a mix n'match basis among cardtypes). In any case, the class of the cardholder doesn't need to change as the cardholder is "promoted" -- it just takes on an alternate behavior via a simple change in the referenced cardtype implementation.
Next, the second discriminant mentioned about the cardholder's state has to do with whether the cardholder is "delinquent" (which could be simple boolean, or a more complex indication of degree and/or frequency). Although a case could probably be made for an "is-a" relationship (a particular cardholder IS A DELINQUENT), it seems more appropriate to describe this bit of state in terms of an historical behavior discovered by the card issuer. Thus, a "has-a" relationship seems more appropriate (the cardholder HAS A DELINQUENT BEHAVIOR of some sort, which should also include a reasonable representation of HAS NO DELINQUENT BEHAVIOR). Again, the state could be modeled with a simple attribute (say, a boolean), or it could refer to a delinquency object that implements an interface which encapsulates more complex delinquency information, including predictive information. (Note that this means the delinquency object could have carte blanche capability to link to external services for risk evaluation and assessment against scoring criteria, etc.).
Finally, in an implementation where the cardholder is modeled as an object containing references to "cardtype" and "delinquent" objects, I could envision a bit of cross pollination. That is, it wouldn't be hard to imagine that the protocol associated with a cardtype would find the delinquency status (or predictions) useful. Similarly, it would seem likely that the predictive processing associated with delinquency could benefit from knowing the cardtype. One way to achieve information sharing would be to keep both "cardtype" and "delinquent" as attributes (object references) of cardholder, explicitly passing bits of information to their respective interface methods. Another way would be embed one of the attributes in the other, such as embedding the "delinquent" attribute into the "cardtype" class (where it can be called on directly for protocol decisions, making the cardtype methods responsible for passing cardtype info as required). In this latter scenario, if the cardholder class actually needs the delinquent attribute occasionally, it could be accessed through the cardtype object (i.e., available via some method of the cardtype interface, which could be particularly useful if the concept of delinquency varies according to cardtype).
I apologize for my long-winded illustration (more writing time => shorter). Getting back to states vs. classes, the bottom line here is that I think there's a danger in asking which is right, A or B, especially if we neglect (or simply forget) to inquire about the possibility of A+B, or C or D, etc.
Just to check my understanding, does the below code correctly implement the idea discussed on this page. And by the way, wouldn't both state-classes anyway inherit from a common parent or implement some common interface like Account in the example below?
class Active implements Account { Attributes attributes; public Active(Attributes attributes) { this.attributes = attributes; } public Account Close() { return new Closed(this); } } class Closed implements Account { Attributes attributes; public Closed(Active beingClosed) { this.attributes = beingClosed.attributes; } }-- AlexeyVerkhovsky
The problem comes when you look at identity. One of the 'attributes' is the id of the account. Now how do you change the instance from Closed to Open?
You have a problem with identity. I have to change all references to the account to reflect the new type of account, because I have to a new instance because the type has changed.
Not if the object doesn't change. You only run into this problem if you try to replace an old object with a new object. Which means in static languages, you use the StatePattern. In a prototype language, couldn't you just change the objects prototype, and it would auto delegate to the new prototype, which is effectively changing the class without messing up the identity of the object?
Well, I don't want to change the object. I want to change the type of the object. Take the card example above.
I want my green card to change to a gold card. The id or reference of my card object I want to keep. I don't want to go and update all references. I don't want to have a lookup every time I reference the card as I would do with a db. The object keeps its id, but changes the available attributes and behaviour because it changes class.
Now, I would expect that both green and gold cards inherit from some base class, card. There needs to be a mechanism that static controls what changes in class are allowed. Probably dynamically too via some sort of assertion mechanism.
You can change objects all you want with #become:. Of course, that doesn't work in a broken language.
What about
originally myCard.prototype = GreenCard;then to upgrade
myCard.prototype = GoldCard;Of course, this only works in prototypical languages!
I suspect the other author is worried about a non-reproducible ad hoc referenceID in the object. If every Card object gets a new reference id on creation, then you can't change the class of the object without changing its reference id. The solution is simple; change the way that reference IDs work ... or don't use them in the first place.
Thanks for the last two replies. I can get around the reference problem, there are various ways. I'll have to go and look at Self. My OO background is more on type safe languages, Eiffel being the best.
The follow on question then is are prototypical languages type safe?
The next one, what restrictions can you place on the change of prototype? I don't want to be able to change a dishwasher to a car.
But what about a car into a dishwasher? http://www.halfbakery.com/idea/Roof_20Rack_2c_20Dish_20Rack_2c_20Carwash_20Dishwasher
All non-broken languages are type safe. Self is a highly dynamic language where a type violation causes runtime exceptions. Now, if you mean, statically typed then that's a very different thing. Self is not, but BetaLanguage is.
Self doesn't have any security. I mean, none whatsoever. You might think that's a bad thing but that's not the case. You see, the security in all other languages is crap. So for someone looking to modify a language to add security, Self is a perfect candidate.
Now, IF Self had security then you could restrict change of prototype any way you wanted to. You could make your object into a finite state (prototype) machine, with a message sent at the right point switching the object to a particular state (prototype). You can still do so now, you just can't guarantee that users of your code will stick to its public interface.
The same is true of SmalltalkLanguage by the way; incredibly powerful reflection means that anyone can bypass all of the high-level abstractions. The only way to add security to the language without lobotomizing it is simply beyond most language experts.
[Aren't there any existing add-on security libraries even if the core SmalltalkLanguage does not have any?]
You don't get it. The core of the language is so powerful that it would let anyone bypass any library you can imagine. This is because all abstraction layers leak through to the higher level. There's even a wiki page about layer leakage; not LeakyAbstraction. As it stands, the only way to make your SmalltalkLanguage code secure is to barricade the entire image it runs in, only letting in predefined messages from a completely different Smalltalk image. OpenTalk? (Smalltalk's remote execution framework) does this.
There is a project, see SmalltalkSecurity, to repackage the entire language so it can be made secure. The scheme they're using is inelegant and depends on Smalltalk having hidden variables. IOW, it wouldn't work for Self. For Self, you have to redefine what an object reference means in the object memory and how the virtual machine handles them.
(Along the subject of which objects can converted into which and security issues of having the language performing the casting, is there one of the CreationalPatterns that effectively(per the example) allows a 'green' card object to become a 'gold' card object programatically? I guess this would be similar to PrototypePattern but would recreate the object to the new type and perform the necessary converstion/migration of state data, similar to the toString function except converting to a specific object instead of a string.)