Memory manipulation is possible

5.5 Static and dynamic classification

So far we have assumed that an object does not change its class affiliation during its entire existence. The relationship between an object and its class could not be changed - the classification of the object was static.

Change of class affiliation

However: Sometimes in practice we have scenarios in which the class membership of an object changes during its existence. A prospective customer can become a customer or an external consultant can become an internal employee. In such cases we speak of a dynamic classification.

The dynamic classification mostly plays a role in the conceptual models, in programming it is seen less often. One reason for this will probably be the fact that the class-based programming languages ​​such as Java, C #, C ++, Python or Ruby do not support them. [In C ++ the class membership of a polymorphic object is realized by the pointer to the table of its virtual methods. With direct memory manipulation it is therefore possible to dynamically change the class membership of an object in C ++. However, it is a daring game with fire to do something like this. Games with fire are exciting and interesting, and those who like them should consider a career as a stuntman or circus performer rather than a software developer. ]

5.5.1 Dynamic change of class membership

However, there are also languages ​​that explicitly support a change in class membership. One of these languages ​​is the Common Lisp Object System, CLOS. We will therefore first use the example of CLOS to show how we could make such an adaptation of the class membership if the language supports it. Then we will show how we can implement a corresponding mechanism by using strategies in the more common languages ​​such as Java.

In a language such as CLOS, mechanisms are provided that allow the type of an object to be changed in a defined way during the runtime of a program.

We turn an A into a B.

If we make object x (an instance of class A) an instance of class B, all attributes that appear in A and B are taken over. In addition, an update-instance-for-different-class method is called (if available) in which data elements to be newly initialized that are defined in B but are not provided by A can be assigned values. The rest is simple, since x is now an instance of B, the corresponding calls to operations are also assigned to B's methods.

Business partners: guests, interested parties and customers

As an example, let's take an application in which business partners are classified as guests, prospects, or customers. In doing so, they can turn from guest to prospect and ultimately to customers. The corresponding hierarchy is shown in Figure 5.70.

Figure 5.70Business partner hierarchy

The definition of the classes in CLOS then looks like below.

;; Definition of the classes involved (defclass business partner () ((name: initarg: name: accessor name) (first name: initarg: first name: accessor first name))) (defclass interested (business partner) ((expected sales: initarg: sales: accessor sales ))) (defclass guest (business partner) ((time budget: initarg: time budget: accessor time budget))) (defclass customer (business partner) ((classification: initarg: classification: accessor classification)))

Listing 5.33Definition of a class hierarchy in CLOS

All classes involved have different implementations of the display operation. Since further attributes are also added in the specific classes, the representation of a business partner is different in all derived classes.

;; Display methods for the specific classes (defmethod display ((gp business partner)) (princ (string-concat "business partner" (firstname gp) "" (name gp)))) (defmethod display ((gp interested)) (call- next-method) ;; calls the method of the base class (princ (string-concat "is a prospect with expected sales" (sales gp)))) (defmethod display ((gp gast)) (call-next-method) ;; calls the method of the base class (princ (string-concat "is a guest with a time budget" (time budget gp))))) (defmethod display ((gp customer)) (call-next-method) ;; calls the method of the base class (princ (string-concat "is a customer with classification" (classification gp))))

Listing 5.34Different presentations for business partners

Guest becomes a prospect.

Now we want to output the description of copies of the respective classes. In doing so, we simply let a guest change his class to interested. This is done in CLOS using the change-class method.

;; ... (frieda (make-instance 'guest: first name "Frieda": name "Müller": time budget "100 minutes")) (gerd (make-instance' interested party: first name "Gerd": name "Müller": sales ") 200 Euro ")) (anne (make-instance 'customer: first name" Anne ": name" Müller ": classification" medium "))) ;; ... (display frieda) (display gerd) (display anne) (change-class frieda 'interested) (display frieda) ;; ...

We get the following output from the interpreter:

Business partner Frieda Müller is a guest with a time budget of 100 minutes. Business partner Gerd Müller is an interested party with an expected turnover of 200 euros. Business partner Anne Müller is a medium-sized customer. Business partner Frieda Müller *** - SLOT-VALUE: The slot ERWARTETER-UMSATZ of # has no value

Presetting of new attributes

Oops, we promoted Frieda Müller from guest to prospect, but without giving her a value for the required attribute expected sales. This means that when a class is changed, special new initializations are necessary for an object. CLOS provides the generic function update-instance-for-different-class for this.

(defmethod update-instance-for-different-class ((gast gast) (interested person) & rest initargs) (setf (turnover interested person) "100 Euro"))

If we override the method as listed above, it will be called when an object changes from the guest class to the interested class. Our output then looks like this:

Business partner Frieda Müller is a guest with a time budget of 100 minutes. Business partner Gerd Müller is an interested party with an expected turnover of 200 euros. Business partner Anne Müller is a medium-sized customer. Business partner Frieda Müller is an interested party with an expected turnover of 100 euros.

We can do that. Shall we too?

Although the programming language here provides simple and intuitive mechanisms for making an object change class, such a procedure can increase the complexity of programs. Even if a programming language allows such an approach: It is usually better to use the strategy design pattern. This can also be used in languages ​​that do not support dynamic classification.

There is also a fundamental conceptual problem when changing the class assignment at runtime. As with any other change to an object, if we change its class, we must ensure that the object continues to fulfill all the promises it has made. The principle of substitutability requires just that.

What does that mean? In the statically typed programming languages, the class affiliation determines the type of the object. The class affiliation thus determines whether a variable can contain or reference the object. If the object's class membership changes, we must ensure that only those variables reference the object that are compatible with its new type.

Subtype migration

But there is a way to ensure this and still achieve the desired change in behavior. You have to ensure that the variables can only have as their type a superclass of all possible classes to which the object can mutate in the course of its existence.

The real class affiliation of the object must therefore not be visible, only its class affiliation to a superclass can be known. And this does not change. Only the subtype of the object, which is invisible from the outside, changes. The object doesn't change its interface, it just changes the implementation.

In the following section we will see how we can achieve exactly this behavior by applying the "strategy" design pattern.

5.5.2 "Strategy" design pattern instead of dynamic classification

If the externally visible class membership of an object does not change, the change in the actual class membership can also be implemented in a programming language that does not support dynamic classification.

The method of choice in this case is the "strategy" design pattern.

"Strategy" design pattern

If part of the behavior of the instances of a class can change depending on their state, the different behaviors can be moved to separate strategy classes.

Each instance of the main class has an instance of one of the strategy classes at any point in time, to which it delegates the implementation of its behavior. If the state of the object changes in such a way that a change in behavior is necessary, the object exchanges its strategy object. By using this pattern, one decouples the different behavior variants of the specimens in the main class without having to resort to dynamic classification.

Let's pick up a slightly modified version of our example from Figure 5.70 and assume we are writing an e-commerce Internet application.

Visitors to our site can register and order goods. The content of the page is specially prepared for each user. Users who have not yet ordered are presented with different advertising and promotions than existing customers. New customers can only pay in advance, premium customers are offered installment payments. The membership of each user in these categories can change over time.

One possible implementation is a User class, which knows the current status of the user and evaluates it in its methods. Listing 5.35 shows a Java implementation of such a class.

public class User {private enum status {PROSPECT, NEW_CUSTOMER, ORDINARY_CUSTOMER, VIP_CUSTOMER} private status status; public void displayAds () {switch (status) {case PROSPECT: // Advertising for prospective customers break; case NEW_CUSTOMER: // Advertising for new customers break; ... // and so on} ...}

Listing 5.35Problematic solution: Evaluation of customer status in the »User« class

We have to assume that such switch commands appear not only in the displayAds method, but in many other methods of the User class. This is not particularly clear and leads to a lot of effort if the categorization of the visitors changes.

In the future, we could assign certain customers who like to order but are reluctant to pay to a new category, which should cause the website to behave in a new way. This would mean that we would have to sift through and adjust many methods and many switch commands.

Figure 5.71 shows one way to solve this problem by using the "strategy" design pattern.

Figure 5.71"Strategy" design pattern applied

Strategy classes

Our application becomes clearer if we place the behavior of the different user categories in different classes - in strategy classes such as ProspectStrategy and NewCustomerStrategy. In Section 5.4.2 we described the concept of delegation. Policy class instances are objects to which the invocation of operations is delegated. In addition, we can exchange these objects at defined times in order to change the behavior of an object.

If you apply the "strategy" pattern to our example, each instance of the User class has exactly one instance of one of the subclasses of UserStrategy at any given time and delegates the calls for the status-specific operations to the strategy object.

If the status of the user changes, for example a prospect becomes a new customer, the user object receives a new strategy object. This changes its behavior, but the type of the user object does not change externally.

Example of strategy classes

Listing 5.36 shows the implementation of this concept for our example in Java.

public class Guest {private abstract class Strategy {// displays the advertisement public abstract void displayAds (); // processes an order and returns the visitor's new // strategy object public abstract Strategy makePurchase (...); ... other methods of the strategy objects} // the current strategy object of the visitor private Strategy strategy; private void displayAds () {// delegate the call to the strategy object strategy.displayAds (); } public void makePurchase () {// delegate the call and remember the new // strategy object strategy = strategy.makePurchase (); } private class ProspectStrategy extends Strategy {@Override public void displayAds () {// Show advertisements for prospective customers} @Override public Strategy makePurchase () {// process the first order ... // A prospect now becomes a new customer return new NewCustomerStrategy (this); } ...} private class NewCustomerStrategy extends Strategy {public NewCustomerStrategy (ProspectStrategy strategy) {// possibly take over the information from // the previous status} @Override public void displayAds () {// Show advertisements for new customers} @Override public Strategy makePurchase () {// process further orders from a new customer //. // This does not change the status of the customer, only the // receipt of payment turns a new customer into an // ordinary customer return this; } ...} ...}

Listing 5.36Implementation of strategies for customers

On the one hand, the respective strategy determines which advertisements the customer gets to see by implementing the displayAds operation. On the other hand, through the implementation of the makePurchase operation, it also determines how an order is to be handled. The makePurchase operation can also result in a customer being promoted from prospect to customer. The makePurchase method of the ProspectStrategy class therefore returns a new strategy object that will then be used in the future. ProspectStrategy practically replaces itself, very unselfishly.

The design pattern presented here is particularly suitable for all cases in which you want to change the behavior of instances of a class at runtime.

your opinion

How did you like the Openbook? We always look forward to your feedback. Please send us your feedback as an e-mail to [email protected]