Channels ▼
RSS

C/C++

Dynamic Design Patterns in Objective-C

Source Code Accompanies This Article. Download It Now.


Dr. Dobb's Journal August 1997: Dynamic Design Patterns in Objective-C

Dynamic run times affect how programs are designed and built

William currently works for Stanford University as part of the section on Medical Informatics. He can be reached at grosso@smi.stanford.edu.


Many software designers view patterns as a form of language-independent design. Pattern-Oriented Software Architectures: A System of Patterns, edited by Frank Buschmann (John Wiley & Sons, 1996), for instance, divides patterns into three main groups -- architectural patterns, design patterns, and idioms. Only the idioms (defined as "low-level patterns specific to a program language") are language dependent -- the other patterns (and the implicit pattern language) rise above the level of programming language, much as the unified modeling language (UML) provides a common way to express designs.

This idea of language independence is seductive. It is also misleading. While patterns are language independent, language choice limits the patterns that are possible, easily supported, and useful. Language also affects how applications are structured.

To illustrate, I'll examine some patterns often used with Objective-C, but not usually used (or used differently) with C++. Both languages are object-oriented extensions of C. The main difference between them is that C++ has compile-time binding and fairly strong typing while Objective-C uses the Smalltalk object model of dynamic binding and weak typing. I'll start by examining two idioms commonly found in Objective-C programming. While simple, these idioms illustrate some of the key Objective-C programming techniques (although I use NextStep classes and frameworks to illustrate points, what I say also holds true for more generic Objective-C environments). I'll also examine how three standard design patterns -- Visitor, Command, and Facade -- are implemented in Objective-C.

Objective-C Programming Idioms

Idiom 1. Use Weakly Typed Delegation to Implement Secondary Roles of Framework Objects. A framework is a set of objects designed to help users create a specific type of application or a specific part of an application. For most problem domains, there is a set of core abstractions that are independent of the framework. For example, consider a framework that helps you create a GUI. Almost every such framework includes an "Application" object. In NextStep and it is an Application, in Delphi's VCL it is TApplication, but the basic abstraction is the same. Similarly, all such frameworks contain some form of "Window," "Subview," "Button," and "Textfield." These framework objects are often the leaves on the framework's object graph.

Framework objects have a clearly defined role within any application. Application objects route events, and Window objects provide a place to draw. However, programmers often subclass objects to allow them to play a secondary role within an application. For example, Textfields are often subclassed within an application, giving them the ability to validate user input.

Standard object-oriented design principles suggest that secondary roles be implemented via the Strategy pattern. Since frameworks are usually developed separately and implemented as shared libraries, using a strongly typed language results in Figure 1. However, Figure 1 can cause problems over the application life cycle. NextStep's Application object, for example, acquired nine new delegate methods between versions 2.0 and 3.3 of the framework. These were gradual modifications brought about by changes in how people use computers, as well as changes made to remedy oversights in the original design. In Figure 1, each of these changes would have required a recompilation of the application and a new release. Moreover, existing copies of the application installed on users' machines would need to be updated.

One solution to this problem is to implement a strict versioning system. For example, the Windows machine I use has three distinct versions of MSVCRT**.DLL, and five versions of MFC**.DLL. If I try to launch my copy of Visual Eiffel, an alert dialog tells me I have the wrong version of CT13D32.DLL.

At best, strict versioning is a stop-gap solution. Depending on my applications, I might load all five versions of MFC**.DLL into memory, defeating the point of shared libraries. Additionally, older applications won't automatically inherit new functionality (or have bugs repaired) -- they still use the older version of the framework. Moreover, minor updates to an application can become complex; if you want to use a feature found in the new version of the framework, you must update every object that depends on the framework (including all the strategy objects).

The Objective-C approach removes the abstract base class, giving the framework object a more complex setDelegate method. The framework object defines a set of strategic methods it will call if they are defined. Then, inside the setDelegate method, run-time type information (RTTI) determines which methods the delegate has implemented. If the delegate doesn't implement the strategic method, the framework object doesn't call it. This is illustrated in Listing One where the new delegate object is queried about each function. If the new delegate implements the function, the method pointer is obtained.

This approach eliminates the versioning problem -- older applications work with new frameworks. For example, most applications compiled under NextStep 2.0 can run using the shared libraries of NextStep 3.3. If the application needs to be updated to take advantage of a new feature in the framework, the necessary code change is minimal because only the object getting updated needs to be modified.

The essence of this idiom is the principle that the framework and application should evolve independently. It is not easy to see how to do this in a statically typed language.

Idiom 2: "Code" Is Nothing but "Resource" Misspelled. A typical application can display a large number of images, but rarely displays them all, or even a significant percentage of them. Because images are large, application developers frequently adopt a policy of lazy fetching, getting images from the disk only when they are about to be displayed. This lets you include a 600-KB GIF image in the "About" panel without forcing users to load a 600-KB image every time the application is launched.

In the era of sprawling programs, there are benefits to being able to do this with object code as well. Microsoft Word 7.0, for instance, includes functions to draw pictures, talk to databases, create forms, and perform sophisticated word-processing tasks such as mail merge. None of these features were used in writing this article. When I launch Word, however, it loads the object code for these functions -- and Word subsequently takes longer to launch and consumes more resources when it is running.

A better approach would be to define a core program consisting of the tasks that are performed by most users each time they execute the application. When users try to use functions outside the core program, it would load a module containing code to perform the requested task.

Doing this properly requires that the loaded module and already-running code be dynamically linked, so that there exists only one object namespace between them. This way, the newly loaded code will be able to automatically locate Singleton objects, tie into existing event loops, and access existing objects with a minimum of programmer effort (objects can be used as arguments without explicitly passing function pointers).

Fortunately, the Objective-C run time allows this functionality. You can message the run time to find out whether or not the required classes exist; if they don't, tell the run time to load them from a file and link them into the application. From then on, the loaded classes are first-class objects (exactly as if they had been part of the initial executable). This is illustrated in Listing Two, where the NXBundle class (provided by NeXT as a generalized "resource loader") loads in an obscure class.

The Visitor Pattern

The Visitor pattern is one of the most commonly used behavioral patterns. The idea is that an object represents a thing. It encapsulates attributes (facts about the thing) and methods (what the thing can do). However, objects frequently acquire extra methods. In particular, an object's methods often start to include "what can be done to the thing." The Visitor pattern is a common way of representing "what can be done to things" as separate objects. This lets you expand program functionality, while keeping the basic object model intact.

A typical line of action for implementing the Visitor pattern is to create objects for your elements, container objects for your objects, and objects that encapsulate common complex operations on the data (see Figures 2 and 3). One thing that developers often overlook is that visitors can store cross-element states; for example, a visitor can create a report about the elements it has visited. This sort of operation must be external to the elements and is easily implemented as a visitor.

Visitor has the potential of being a dynamic pattern that supports piecemeal code growth. As you discover operations that need to be supported, you add visitors that embody the operation and "fire them" at the appropriate data objects.

But the standard implementation of the Visitor pattern contains a flaw. Over the lifetime of a project, new visitors and elements (the objects that visitors act on) will be needed. Separate hierarchies for visitors and elements should enable painless modification of the software. (Consider Figures 2 and 3.) Unfortunately, separate hierarchies are not easy to set up in the standard (strongly typed) version of Visitor.

Figure 2 implies that every concrete element must know about every visitor (to know whether to accept a particular visitor). And, of course, the visitor base class must know about every type of element (the visitConcreteElement methods must encompass every possible type of element). When there are many visitors and many elements, implementing and modifying the Visitor pattern becomes tedious and error prone. The overall effect is poorly understood and brittle software that has bugs.

This problem is partially caused by double dispatch: Elements accept (or reject) visitors, then call a method in the visitor and the ultimate function called is dependent on both the element and visitor. But this also means that elements are coupled to the visitors they accept. Eliminating double dispatch simplifies the implementation.

If, however, double dispatch is necessary, you can still eliminate most of the compile-time dependencies in the code by having both the element's accept method and the visitor's operation use RTTI. In Listing Three, for example, the argument of the accept method is a pointer to a generic Visitor object. Inside the method, this argument is more carefully type checked (at run time) and certain types of visitors are accepted. This solution is similar to Robert Martin's Acyclic Visitor pattern in C++. The implementation of Visit is similar.

Consider how this code needs to change when seven new elements are added. Obviously, seven new element classes need to be written and some visitors might have to be modified. But we have isolated the code changes to those visitors that are directly affected. Similarly, when a new visitor is added, only those elements that accept the visitor need to be modified.

Using RTTI localizes the code changes (to inside specific methods), eliminates compile-time dependencies, minimizes the number of objects that need to be changed, and enables black-box reuse of most of the visitors and elements.

Object Serialization

As good object-oriented designers and developers, we actively practice information hiding (encapsulation). This means that at run time, a program is a huge graph of interconnected objects, each with their own private state. In addition, the connections between the objects are an important part of the application state. Object serialization is simply the idea that each object ought to be responsible for saving and restoring its own state and the connections it has (through instance variables). In other words, saving and restoring is best accomplished by traversing the object graph and invoking object-level save and restore operations.

This is a prime example of a simple implementation of the Visitor pattern. It also illustrates how loosely coupled the Visitor pattern can be in Objective-C, where there are three distinct types of objects: Serializers, Deserializers, and subclasses of Serializable. Both Serializers and Deserializers are visitors. Serializer visits each object in the object graph, getting a record of the object's state and saving the information out to a file. Deserializer takes a file and rebuilds an object graph from it. All the objects being serialized must inherit from Serializable.

Serialization and deserialization are simple. In either Objective-C or C++, it follows the same basic communication pattern (see Figure 3 and Listing Five). But hidden in deserialization is a crucial question: Who creates the objects being instantiated?

In Objective-C, Deserializer is responsible for object creation. In the readObject method, Deserializer either reads in a reference to an already-created object or it reads in enough information to create the object. In the first case, it simply returns the preexisting object. In the second, it creates the necessary object and calls an initialization method. This is code that doesn't need to change as the project grows.

Consider the C++ equivalent. Who creates the objects when deserialization occurs? Either Deserializer does (in which case Deserializer must know about each subclass of Serializable), or the subclasses of Serializable do by calling an initialization method (passing in the Deserializer). The second way eliminates code dependency, but has a potentially huge performance price (many extra objects will be created).

The Command Pattern

The Command pattern (which encapsulates a request as an object) is one of the most fundamental patterns in object-oriented programming (see Figure 4). It is easy to understand and, aside from a slight decrease in performance, has almost no drawbacks when used properly. It lets you reduce code dependencies (the invoker object no longer depends on the receiver object) and it allows for run-time configuration changes (by changing the command object used by the invoker).

Figure 5 illustrates the Command pattern with a Timer class -- Object A creates an instance of ConcreteCommand and passes it to an instance of CommandTimer (which only depends on the abstract superclass Command).

The standard implementation of Figure 4 in C++ uses templates. A template class is defined that takes the receiver object as a template argument. In addition, this class has a set method, which takes a pointer to a member function as its argument (in Listing Four, this information is passed in as part of the constructor). This template class is then used wherever a command object is needed.

Developers working in Objective-C use a slightly different solution: Instead of using pointers to member functions and strongly typed template arguments, they use selectors and pass in the receiver as an instance of type ID (see Listing Six).

At first glance, these approaches seem almost equivalent. Indeed, the C++ approach has the added benefit of strong typing. However, the Objective-C approach -- in particular, the use of selectors -- has a significant advantage that is hinted at in Listing Six: You can easily convert a selector into a string (which is the method name). This string also can be converted back into the original selector.

Why is this an advantage? Consider object serialization and the related ways of making parts of the object graph persistent. The Objective-C approach to command objects is simply much more flexible and adaptable.

The Facade Pattern

You might have noticed a common thread in the patterns just discussed. Both of the idioms involve ways to break out pieces of code and put them in shared libraries or bundles. The design patterns show how to decouple objects from each other for maximal flexibility. I'll now examine a commonly used architectural pattern that describes a way to structure applications to take advantage of this flexibility.

In the good old days of structured programming, saving an application's state was simple. Programs were divided into data and functions, and saving consisted of calling the function that wrote the data to some storage area. In object-oriented applications, things become a little more difficult. To the extent that you practice information hiding, object serialization is the natural approach to take.

Unfortunately, if m is the number of object references and n is the number of objects, serialization is O(mlog(n)). This is far too slow for serialization to be the saving mechanism in many applications.

But an Objective-C program that uses the four previous patterns will almost certainly use lots of facades as well. Notice in Figure 6 that Facades look a lot like cut points in the object graph. This leads to the natural Objective-C solution to the speed problem for object serialization: Serialize each facade to a separate Serializer (make each facade responsible for serializing the subsystem it abstracts).

This can get complicated. If an object outside a subsystem bypasses a facade (and messages an object in a subsystem directly), then extra care must be taken during serialization (to avoid serializing objects to more than one location). And, deserializing (opening) becomes trickier as well -- objects that bypass a facade will need to find objects within the subsystem. In practice, this comes down to making the facades used in serialization Singletons and making certain that all connections to objects in the subsystem are mediated by the facade (so that, during deserialization, the connection can be restored).

Conclusion

The addition of a dynamic run time enables substantial changes in how a program is designed and built -- changes that greatly ease your job over the application life cycle. Dynamic run times have another significant consequence, particularly when programs are designed using the aforementioned patterns. If you use bundles to encapsulate distinct pieces of object code, Facades to implement serialization, and loosely couple everything with Visitors and Commands (and other dynamic patterns that emerge in Objective-C programming), then you are, in essence, using a prototype-driven model of program design.

Acknowledgments

I'd like to thank Tom Lippincott and John Stytz for helpful comments on this article.


Listing One

//  From the header file -- define a pointer to member function for a method     bool appBecameActiveCanBeCalled;
    id (* appBecameActive) (id, SEL, id);


</p>
// From the .m (code) file (and heavily annotated)
- (void) setDelegate: newDelegate
{
    SEL tokenizedMethodName; // the @selector compiler directive tokenizes 
                             // the method name. 
//  First, we check to see if we have a delegate and, if so, whether the new 
//  delegate is from the same class; if both these are true, just return. 
    if ((nil!=_internalDelegate) && ([newDelegate cisMemberOf: 
                                              [_internalDelegate class]))
    {
        _internalDelegate = [newDelegate retain];   //delegate won't be freed
return;
    }
// Need to explicitly check which methods our new delegate has implemented. 
   _internalDelegate = [newDelegate retain]; //ensure delegate won't be freed
    tokenizedMethodName=@selector(appBecameActive:);
    if (YES==[ newDelegate respondsTo: tokenizedMethodName])
    {
//  syntax is [object memberFunction: argumentOne tag: argument2 ...]; We now
//  know that the new delegate object implements appBecameActive. We will get 
//  function pointer (for speed, might be calling this function often and 
//  and therefore want to avoid rebinding the selector).  Usually, we wouldn't
//  get the function pointer. 
AppBecameActive=(id (*) (id,  SEL, id) 
[_internalDelegate methodFor: tokenizedMethodName];
        appBecameActiveCanBeCalled=YES;
    }
else
{
appBecameActiveCanBeCalled =NO;
    }
//  ....
}

Back to Article

Listing Two

//  code in main application-  loadObscureClassFromDisk
{
//  Our goal is to find a requried class object (which we will return)


</p>
    Class classThatsNeeded;
//  Has it been loaded ? Try to find the class object using a wrapper 
//  function which encapsulates the run-time function objc_lookUpClass();
    classThatsNeeded =NXClassFromString("ObscureClassName");
    If (nil== classThatsNeeded)
    {
//  No class object. We need to load it. 
//  Build a path name, relative to where application was installed by 
//  concatenating a subdirectory name onto [[NXApp mainBundle] directory] 
        char * fullPathDirectory;
        ....
        NXBundle * bundleForClass =[[NXBundle alloc] initForDirectory: 
                                                          fullPathDirectory];
        classThatsNeeded=[ bundleForClass classNamed:"ObscureClassName"];
    }
    return classThatsNeeded;
}

Back to Article

Listing Three

//  code in an element- (bool) accept: aVisitor
{
//  Protocols are like interfaces in java_they define a set of methods. 
//  Objects, when declared (in their header files), inherit from one concrete
//  class and multiple protocols. This accept method checks whether the 
//  visitor conformsTo (inherits from) a certain protocol declared earlier 
//  or if the visitor is of a particular class. 
    if (([aVisitor conformsTo: MyProtocol]) !! 
                                       ([aVisitor isKindOf: acceptableClass]))
    {
        return [aVisitor visit: self];
    }
    return NO;
}
//  code in a visitor
- visit: anElement
{
//  again, do type checking. Check whether element is an instance of a 
//  specific class; if that fails, whether it inherits from some other class. 
//  classOne and classTwo are declared previously. 
    if ([anElement isMemberOf: classOne])
    {
//  perform some sequence of actions
    }
    else if ([anElement isKindOf: classTwo])
    {
//  perform some sequence of actions
    }
return NO;
}

Back to Article

Listing Four

//// File:  CommandTimer.h
//
// Defines two objectsEGConcreteCommand and CommandTimer.  
// This code written by Don Van Patten.


</p>
template <class ReceiverClass> class ConcreteCommand 
{
 typedef void  (ReceiverClass::*ReceiverAction)();
 private:
  ReceiverClass* mReceiver;
  ReceiverAction mAction;


</p>
 public:
  ConcreteCommand(ReceiverClass*, ReceiverAction);
  virtual ~ConcreteCommand();
  virtual void Execute();  
};


</p>
class CommandTimer
{
 private:
  AbstractCommand* mCommand;
  int mNumberOfSeconds;


</p>
 public:
  CommandTimer();
  virtual ~CommandTimer();


</p>
  virtual void SetCommand(AbstractCommand*);
  virtual void SetTimeInSeconds(int);


</p>
 private:
  virtual void Execute();
};


</p>


</p>
//
// File: CommandTimer.cpp
// Contains implementations for two objects: ConcreteCommand and
CommandTimer. 


</p>
#include "CmdTimer.h"


</p>
#define NULL  (void*)0;


</p>
// ConcreteCommand


</p>
template <class ReceiverClass>
ConcreteCommand<ReceiverClass>::ConcreteCommand(ReceiverClass* receiver,
ReceiverAction action)
{
 mReceiver = receiver;
 mAction = action;
}


</p>
template <class ReceiverClass>
ConcreteCommand<ReceiverClass>::~ConcreteCommand()
{
}


</p>
template <class ReceiverClass> void
ConcreteCommand<ReceiverClass>::Execute()
{
 (mReceiver->*mAction)();
}


</p>
//
// CommandTimer
//


</p>
CommandTimer:: CommandTimer()
{
 mCommand = (AbstractCommand*)NULL;
 mNumberOfSeconds = 0;
}


</p>
CommandTimer::~ CommandTimer()
{
 if (mCommand) delete mCommand;
}


</p>
void CommandTimer::SetCommand(AbstractCommand* command)
{
 mCommand = command;
}


</p>
void CommandTimer::SetTimeInSeconds(int number_of_seconds)
{
 mNumberOfSeconds = number_of_seconds;
// note that in a genuine implementation, this would fire off a timing
loop
// that would eventually call Execute()


</p>
}


</p>
void CommandTimer::Execute()
{
 mCommand->Execute();
}

Back to Article

Listing Five

//  headers  --  Serializer implements- (void) writeInt: (id) value;
- (void) writeFloat: (id) value;
- (void) writeDouble: (id) value
- (void) writeChar: (id) value
- (void) writeObject: (id) value


</p>
//  Serializable implements
- (void) serializeUsing: (Serializer *) serializerToUse;
- (void) initWithDeserializer: (Deserialier *) deserializerWithMyState;
//  Serializer implements
- (int) readInt;
- (float) readFloat;
- (double) readDouble;
- (char) readChar;
- (id) readObject;


</p>
//  from Serializer's Code
- (void) writeObject: (id) value
{
    if (YES==[self objectAlreadyWritten: value])
    {
//  The object thas already been serialized. We just store the 
//  internal uuid (so we can rebuild the object graph). 
        [self storeReferenceToObject: value];
    }
    else
    {
//  first log that we're storing the object.
        [self createReferenceFor: value];
        [self writeString: NSStringFromClass([[value class])];
        [value serializeUsing: self];
    }
}
//  from Deserializer's code
- (id) readObject
{
//  ....
//  Here we need to exhume a previously unknown object.
    Class classObjectForValue;
    id value;
classObjectForValue = NSClassFromString([self readNextString]);
    if (nil!= classObjectForValue)
    {
        value=[[ classObjectForValue alloc] initFromSerializer: self];
    }
    else
    {
//  Bad thing happened here. Raise an exception because our data is corrupt.
//      ...
    }
    return value;
}

Back to Article

Listing Six

//  header (.h) file#import <foundation/foundation.h>
 NSObject
{
    id target;
    id argument;
    NSString * nameOfMethod;
    SEL selectorForMethod;
}
- (void) dealloc;
- init;
- (void) setArgumentObject:  newArgument;
- (void) setTargetObject:  newTarget;


</p>

//  Function overloading would make the next two methods more pleasant. 
- (void) setTargetMethodByString: (NSString *) methodName;  
- (void) setTargetMethodBySelector:  (SEL) methodSelector;
- (bool) do;
- (void) initWithCoder: (NSCoder *) decoder;
- (void) encodeWithCoder: (NSCoder *) encoder;  
@end


</p>
//  code (.m) file
#import "CommandObject.h"
@implementation CommandObject
- (void) dealloc
{
//  retain, release, and autorelease are all part of reference counting.
//  ref counting is in NEXTSTEP, but not part of the Objective-C spec.
//  When an object's ref count goes to zero, it is deallocated.
    [target autorelease];
    [argument autorelease];
    [nameOfMethod autorelease];
    [super dealloc];
    return;
}
- init
{
//  init is like a constructor. The object has already been 
//  allocated and needs to have its variables initialized. 
    [super init];
    nameOfMethod=argument=target=nil;   //always safe cause messages to nil
                        //don't cause problems. 
    return self;        //note that the return value of init has to be 
                        //specified_, unlike the return value from a 
                        //C++ constructor. 
}
- (void) setArgumentObject:  newArgument
{
    if (nil!=argument)
    {
        [argument autorelease];
    }   
    argument=newArgument;
    [argument retain];
    return;
}
- (void) setTargetObject:  newTarget
{
    if (nil!=target)
    {
        [target autorelease];
    }   
   target=newTarget;
    [target retain];
    return;
}
- (void) setTargetMethodByString: (NSString *) methodName
{
    nameOfMethod = [methodName copy];   //make our own copy
                        //to guard against changes
    selectorForMethod= NSSelectorFromString (nameOfMethod);
    return;
}
- (void) setTargetMethodBySelector:  (SEL) methodSelector
{
selectorForMethod= methodSelector;
nameOfMethod= [NSStringFromSelector (selectorForMethod) retain];
    return;
}
- (bool) do
{
    if (YES==[target respondsTo: selectorForMethod])
    {
        [target perform: selectorForMethod];
        return YES;
    }
    return NO;
}
- (void) initWithCoder: (NSCoder *) decoder
{
//  From target, object, and nameOfMethod, we can get selector back (in fact,
//  we'll call function to do so). Hence, object graph can be fully restored. 
//  ....
}
- (void) encodeWithCoder: (NSCoder *) encoder
{
//  We write out three objects--target, argument,and nameOfMethod. 
//  this is just the "serialize" method discussed earlier.
//  ...
}
@end

Back to Article

DDJ


Copyright © 1997, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video