TweetFollow Us on Twitter

OOP World
Volume Number:7
Issue Number:9
Column Tag:Developer's Forum

OOP in a non-OOP World

By Steve Sheets, Herndon, VA

[The following article was based on a presentation at the 1990 MacWorld conference in Boston. The speech was given as part of a panel discussion on Object Oriented Programming.]

OOP in a non-OOP World.

“Object Oriented Programming.” These words congure up images of new programming langauges like Smalltalk and C++. Visions of programmers everywhere, throwing out their old compilers and sample code, and starting to program exclusively in an Object Oriented Programming Language are called to mind. Jokes about those who can only program in C or Pascal (“how many Pascal Programmer does it take...”) begin to appear.

Time will tell if the above situations are more spoof than prediction. However, the thinking that inspired them has a very specific fallacy that I would like to address. First, let it be known that I am one of the world’s biggest fans of Object Oriented Programming. I honestly believe that object oriented programming is likely to become the next step in the evolution of programming languages. However, I do not agree that this next step in programming style will be the exclusive domain of the new Object Oriented Programming Langauges. This article intends to show how to make use of object oriented techniques while programming in a non-object-oriented environment. It is possible to use the ideas contained in Object Oriented Programming Languages without using the traditional object oriented constructs like objects, classes, inheritances, or methods. To understand how to go about using the techniques, but not the items, a little history lesson is in order. Even though this lesson is on a slightly different subject, it does have a point.

Back in the long ago, when Computers were first created and introduced to the swarms of eager programmers, the majority of the programming languages were line oriented. Languages like Basic and early versions of Fortran and Cobol usually paralleled Assembly language, where almost every line of code did a specific task and every line was followed by another line of code.

The flow of the program was usually from one line to another. The GOTO or Conditional GOTO statements (or their equivalents) were used to control the flow by branching, but in general, the flow was linear. This technique could cause problems. For one thing, a programmer had to understand practically every single line of the code before he could work with any piece of it. Otherwise, he would never know for sure if the portion of the code he was modifying had an effect on some other line of code. If a person were to try to work with code he had not created, he would likely have to spend a great deal of time studying it before he could work with it.

A second, nastier problem with the linear flow arrangement became apparent as programs became more complex. A simple piece of code might be modified again and again, with different branches at many points in the code. Soon programmers started getting Spaghetti Code, where the code was so complex, and the flow so strange, that no one, perhaps not even the original programmer, could understand how the code worked.

To solve this problem, more Procedural oriented languages were introduced. Languages like Pascal, C, and later expanded versions of line oriented languages were given syntax and commands that allowed a programmer to divide the code into more manageable procedures. People stated talking about the joys of this new Procedural Programming style.

Now a interesting thing started happening. Programmers who used the procedural languages, and who learned the joy of procedural programming, would sometimes still have to program using the older languages. However, they had gotten used to the advantages of the newer languages. So they added some of those new ideas to their programs, even though they were using the old languages. They wrote the programs so that they had separate subroutines and functions. Even Basic could be written in this procedural style, once the programmer understood the concept.

Notice that just because a program is written in a good procedural base programming language, it does not necessarily follow that the program is good code. Some of the worst examples of spaghetti code have been written in the so called higher level languages. These languages just made it easier to learn a new idea, like procedural programming. The languages helped to teach programmers good programming habits. Once the style was learned, it was not necessary to use the new language or features in order to implement the technique.

That’s the end of the history lesson. Now, to come back to the present; nearly everyone has been exclaiming the joys of these new Object Oriented Programming Languages. Notice any possible parallel to my history lesson? Just like those who years ago promoted Procedural Programming, they are right. There are many benefits to using object oriented programming. This new type of programming will allow you to code better. But you do not have to use Smalltalk or C++ or Object Pascal in order to implement the benefits of object oriented programming.

Many programmers would probably like to use an object oriented approach, or at least they would be interested in learning about it. However, these people may be unable, for perfectly valid reasons, to start using an Object Oriented Programming Language today. Many may have to work in a language specified by their company, or one that is specified by their job contract. Some may have to work on a computer that does not have a Object Oriented Programming Language. Or some might have to program with co-workers who have not yet “seen the light” of Object Oriented Programming. And many may have already invested a large amount of time and energy a piece of non-object-oriented code, resources they might not have to reinvest in order to convert over to an object oriented approach.

That last reason was the one that kept me from using an Object Oriented Programming Language on my project, America Online. There was already an Alpha version of the code written in Think Pascal, without Object extensions. Neither MacApp or any other Object Oriented Programming Language was an option for America Online when I came onto the project. While there is almost nothing left of the original Alpha code in the current version of America Online, there never was a time when we could take the time that would have been needed to convert the entire code to use Object Pascal extensions or MacApp. So instead, what we did was to look at various parts of America Online and decided what we could improve using what we had learned about America Online.

Since I’m going to refer frequently to my project in this article, perhaps I’d better give you a little information about it. For those who are not familiar with it, America Online is a telecommunication service which connects our host mainframe to personal computers using proprietary protocols and software. In my case, we are using Macintoshes connected over a serial modem line. Using a low-level protocol, packets of data are transferred, error free, from the host to the Mac or visa versa. When a data packet comes in, the first few bytes of data designate what task that packet is intended to preform. A group of tasks related to a specific online function is called an “Engine”. Engines include the Form Engine (to create the windows), the Chat engine (to handle online chat rooms), Async Engine (to handle other actions), File transfer engine (to handle uploads & downloads), the LogOn Engine (to handle the log on process), and the LogOff engine (which handles the log off process).

When a packet of information comes in from the Host, America Online has to decide which engine handles that packet. Originally, America Online used procedural styles. Each Engine had a Packet Handling routine that decided whether or not that packet was handled by that engine. If it did, the packet was handled and the function returned TRUE. If it did not, the function did nothing but return FALSE. Every engine accessed the global variable space of the program in order to store states.

The original code segment that parsed a packet looked something like this.

{1}

FUNCTION Form_Engine(thePacket:TDataType);   FORWARD;
FUNCTION Chat_Engine(thePacket:TDataType);   FORWARD;
FUNCTION File_Engine(thePacket:TDataType);   FORWARD;
FUNCTION LogOn_Engine(thePacket:TDataType);  FORWARD;
FUNCTION LogOff_Engine(thePacket:TDataType); FORWARD;

PROCEDURE ParsePacket(thePacket:TDataType);
BEGIN
 IF NOT Form_Engine(thePacket) THEN
 IF NOT Chat_Engine(thePacket) THEN
 IF NOT Async_Engine(thePacket) THEN
 IF NOT File_Engine(thePacket) THEN
 IF NOT LogOn_Engine(thePacket) THEN
 IF NOT LogOff_Engine(thePacket) THEN
 ;
END;

This piece of code just simply bothered me. It looks cumbersome and is difficult to manipulate. Each engine is always called, no matter whether or not the engine is on. There is no sense of which packet might effect what global data. Adding more engines to the code made the code segment even more awkward.

So I decided to apply a few of the techniques that I learned in Object Oriented Programming classes. I created some new data structures. These new structures included a new record type (TEngineRec), a pointer to this record (TEnginePtr) and a handle to the record (TEngineHdl). TEngineRec consisted of a TEngineHdl, a normal data Handle and a ProcPtr.

{2}

TYPE  TEngineRec = RECORD
 EngineData: Handle;
 EngineProc: ProcPtr;
 NextEngine: TEngineHdl;
 END;
 TEnginePtr = ^TEngineRec;
 TEngineHdl = ^TEnginePtr;

For those unfamiliar with ProcPtr, it is a pointer to an actual procedure or function in memory. Depending on your language or development system, it allows the procedure it indicates to be invoked. To do the actual invoking of this procedure, I defined an Inline call that places the correct parameters on the stack and then jumps to the procedure the pointer is aimed at and executes it.

In order to make this code work, I also created one global variable, GEngineList, a TEngineHdl type.

VARGEngineList: TEngineHdl;

Obviously the idea was to try to create a linked list of Engines. GEngineList points to the first Engine in the list, and each engine points to the next one. A set of routines to add and subtract Engines to the the list was also needed. I therefore declare the following call.

{3}

FUNCTION AddEngine(theProc:ProcPtr;theData:Handle):TEngineHdl;
FORWARD;

PROCEDURE RemoveEngine(theEngine:TEngineHdl);
FORWARD;

These routines are uses to add and remove an engine from our list of active engines. The AddEngine call would be invoked like this:

{4}

VARtheFormEngine:TEngineHdl;
 theFormData:Handle;

theFormData :=...{Code to initilize data}...

theEngine:=AddEngine(@Form_Engine,theFormData);

Notice that when an engine was added, the pointer was passed to the Engine’s Packet_Handling routine. That routine will be executed later, when a packet comes in.

Now, let us revisit the original piece of code. The original code will pass a packet to all engines until it finds one that wants the packet. The new code only passes packets to engines that have been installed.

{5}

PROCEDURE New_ParsePacket(thePacket:TDataType);
VARHandled:BOOLEAN;
 theList:TEngineHdl;
BEGIN
 Handled:=FALSE;
 theList :=GEngineList;
 WHILE (NOT Handled) and (theList<>NIL) DO BEGIN
   IF Call_To_Proc(thePacket,theList^^.theData, theList^^.theProc)
 THEN Handled:=TRUE
 ELSE theList:=theList^^.NextEngine
 END;
END;

Call_To_Proc is a glue routine that actually jumps to the Procedure pointed to at theList^^.theProc with thePacket and theList^^.theData as parameters.

Now, imagine what happens during a normal logon sequence. As a user first goes online, a LogOn Engine is created. Once the person is completely online, the LogOn Engine is removed and it’s handle and data handle are deallocated from memory. Then the Forms Engine and the Async engines are added to the Engine List. These Engines normally run for the life of an online session to handle most normal forms and asynchronous events.

At some point the user might decide to go to a Chat room. The Chat engine is added when, and only when, the user enters a Chat room. When he leaves the Chat room, the engine is removed. The same can happen to a File transfer engine.

Some of the advantages of this approach become apparent if you consider how we can use the new code to create new situations. Since the data for each engine is completely contained in that engines data handle, the protocol could be changed so that a user can be in more than one chat room at the same time. This would mean a chat room packet would have to have some information in it to determine whether it pertained to room “A” or room “B”, but that information would only require a couple of bytes of data and would be easy to include.

There is no reason why File transfer, or any other engine that could be conceived, could not be added and invoked any number of times, at any point in an online session. This gives us an amazingly powerful online asynchronous environment. What we are really seeing is an example of the Object Oriented concept of encapsulation of data and the concept of instance of an object.

More than one instance can easily be added to the code at any time. Until an instance is added, additional memory need not be allocated. If an engine is changed, or even if a new one is added, only routines related to the new engine need to be added. The parser code segment used to send a packet to the correct engine is never changed.

I have always considered Data Encapsulation and Instance of objects to be two of the three most important ideas associated with Object Oriented Programming. The third important idea is that of Inheritance. And this function of an Object Oriented Programming Language can be adopted to non-Object Oriented Programming Languages, too.

Actually we are already halfway there in our example. ProcPtrs can be manipulated so that certain data structures have pointers to some procedures, while other instances use other procedures. This simulates the idea that a method can be overridden or inherited. To see this more easily, lets consider another example that has ProcPtrs.

America Online has a very complete Forms engine that allows us to create almost any form using packets sent from the host. Each form packet has information describing windows and different fields on these windows. Different field types that can be on a window might include buttons, icons, pictures, non-editable text, editable text, lists, and so on. All the fields have certain tasks that they have to be able to perform. For example, each field must be able to be drawn, it must be able to handle a mousedown in it’s area, it must be able to be hilited, it must be able to handle a keystroke, it must be able to invert itself and it must have an action associated with it. Following this idea, a field data structure might look like this.

{6}

FieldRec = RECORD
  fArea : Rect;
  fActionNum : INTEGER;
  fText: Str255;
  fData : Handle;
  pMouseDown,pUpdate,pActivate,pHilite,pKeydown,pAction : ProcPtr;
 END;

FieldRec = ^ FieldPtr;
FieldHdl = ^FieldPtr;

Besides the data, a number of ProcPtrs are declared, one for each major task. Some of the tasks might be do nothing tasks. For instance, a non-editable text field would ignore keystrokes, or a picture would not need to have an action associated with it. Others are more specific for the given field type (the keystroke routine for an editable text field).

For example, when the window needs to be updated, the code might find the field list for that window. Then it would one by one cycle through each of the fields, invoking the draw command so that that portion of the window is drawn. That code would look something like the following example. Notice if the ProcPtr is nil, that means there is a do-nothing task, and it should be skipped.

{7}

PROCEDURE UpdateDisplay(theDisplay:DisplayHdl);
VAR theField:FieldHdl;
BEGIN
 theField:=theDisplay^^.FirstField;
 WHILE (theField<>NIL) DO BEGIN
   IF theField^^.pUpdate<>NIL
   THEN Invoke_Update(theField,theField^^.pUpdate);
   theField:=theField^^.NextField;
 END;
END;

If a mousedown occurs, a similar scan is done. In that case, each field is checked to see if the mousedown occurs in it. The Mousedown ProcPtr will only be invoked if the mousedown has occurred in the relevant field. Notice that no matter which type of field is used, these pieces of code will never change.

Somewhere in the code, there will be a segment of code that creates these fields; this procedure is called New_Field. Each kind of field will have its own declaration section in the procedure. For example, a picture might look like this.

{8}

FUNCTION New_Field(theKind:INTEGER;
 theBox:Rect;
 theID:INTEGER):FieldHdl;
VAR theField:FieldHdl;
BEGIN
 ...
 CASE Kind OF
 ...
 kPicture: BEGIN
   theField^^.fArea:=theBox;
   theField^^.fData:=GetPicture(theID);
   ...
   theField^^.pUpdate:=@Draw_Pic_Field;
   theField^^.pHilite:=@Invert_Area_Field;
   theField^^.pAction:=NIL;
 ....
 END;
 OTHERWISE
 New_Field:=theField;
END;

You can see that this kind field basically does nothing but draw itself. A more interesting kind of field might be a clickable Icon field.

{9}

 kClickIcon: BEGIN
   theField^^.fArea:=theBox;
   theField^^.fAction:=theAction;
   theField^^.fData:=GetIcon(theID);
   ...
   theField^^.pUpdate:=@Draw_Icon_Field;
   theField^^.pHilite:=@Invert_Icon_Mask_Field;
   theField^^.pAction:=Do_Action_Number;
   theField^^.pMouseDown:=@Track_Invert;
   ....
 END;

This type of field has it’s own draw and invert calls. It also uses the Track_Invert call in order to handle a mousedown. That call keeps track of whether or not the mouse is inside the field area. It invokes the Invert ProcPtr repeatedly to hilite or unhilite the field depending on if the mouse is in or out of the area. If the user releases the button while the mouse is over the field, that call invokes the Action ProcPtr. The action ProcPtr is the most generic one, the Do_Action_Number procedure which does an action defined in the ActionNumber field.

If an entirely new kind of field needs to be created, say for example a clickable Picture, some of the previously defined routines could be used (inherited) to define this kind of object. This code would be added to the new field.

{10}

 kClickPict: BEGIN
 theField^^.fArea:=theBox;
 theField^^.fAction:=theAction;
 theField^^.fData:=GetPicture(theID);
 ...
 theField^^.pUpdate:=@Draw_Pic_Field;
 theField^^.pHilite:=@Invert_Area_Field;
 theField^^.pAction:=Do_Action_Number;
 theField^^.pMouseDown:=@Track_Invert;
 ....
 END;

We have created a new type of field, with it’s own specific appearance and function, without adding any new code except for the creation routine. Even if we had to create a new field with a new special task, (such as a new way to handle drawing,) only a single new procedure would need to be written (in this case, Draw_Bitmap_Icon). We can use the old procedures/tasks for the rest of the new field.

Notice that this Clickable PICT field is related to a non-Clickable PICT field. These fields don’t quite function as a superclass and a subclass, but there is a relationship involved. This might not be true inheritance, but it sure does make it easier to create new fields when they are needed.

Inheritance, instancing of objects and data encapsulation are possibly the three most important ideas in object oriented programming, and they have all been simulated in a non-object-oriented procedural language. And I would like to add that this this is not merely sample code that is never used in a real-world application. These three ideas are used over and over again inside of America Online.

The code would have never been designed the way it was if I had not aware of the object oriented concepts and had not seen them used in a object oriented world. America Online is a better product because of the object oriented lessons learned. The code is more flexible, easier to work with and more powerful. Even if a programmer is never to use MacApp in a major project, or he is never to use an Object Oriented Programming Language in a job, the project or job he is working on, the code he creates may be better if he uses the object oriented concepts he learns.

 
AAPL
$119.00
Apple Inc.
+1.40
MSFT
$47.75
Microsoft Corpora
+0.28
GOOG
$540.37
Google Inc.
-0.71

MacTech Search:
Community Search:

Software Updates via MacUpdate

HoudahSpot 3.9.6 - Advanced file search...
HoudahSpot is a powerful file search tool built upon MacOS X Spotlight. Spotlight unleashed Create detailed queries to locate the exact file you need Narrow down searches. Zero in on files Save... Read more
RapidWeaver 6.0.3 - Create template-base...
RapidWeaver is a next-generation Web design application to help you easily create professional-looking Web sites in minutes. No knowledge of complex code is required, RapidWeaver will take care of... Read more
iPhoto Library Manager 4.1.10 - Manage m...
iPhoto Library Manager lets you organize your photos into multiple iPhoto libraries. Separate your high school and college photos from your latest summer vacation pictures. Or keep some photo... Read more
iExplorer 3.5.1.9 - View and transfer al...
iExplorer is an iPhone browser for Mac lets you view the files on your iOS device. By using a drag and drop interface, you can quickly copy files and folders between your Mac and your iPhone or... Read more
MacUpdate Desktop 6.0.3 - Discover and i...
MacUpdate Desktop 6 brings seamless 1-click installs and version updates to your Mac. With a free MacUpdate account and MacUpdate Desktop 6, Mac users can now install almost any Mac app on macupdate.... Read more
SteerMouse 4.2.2 - Powerful third-party...
SteerMouse is an advanced driver for USB and Bluetooth mice. It also supports Apple Mighty Mouse very well. SteerMouse can assign various functions to buttons that Apple's software does not allow,... Read more
iMazing 1.1 - Complete iOS device manage...
iMazing (was DiskAid) is the ultimate iOS device manager with capabilities far beyond what iTunes offers. With iMazing and your iOS device (iPhone, iPad, or iPod), you can: Copy music to and from... Read more
PopChar X 7.0 - Floating window shows av...
PopChar X helps you get the most out of your font collection. With its crystal-clear interface, PopChar X provides a frustration-free way to access any font's special characters. Expanded... Read more
Carbon Copy Cloner 4.0.3 - Easy-to-use b...
Carbon Copy Cloner backups are better than ordinary backups. Suppose the unthinkable happens while you're under deadline to finish a project: your Mac is unresponsive and all you hear is an ominous,... Read more
ForeverSave 2.1.3 - Universal auto-save...
ForeverSave auto-saves all documents you're working on while simultaneously doing backup versioning in the background. Lost data can be quickly restored at any time. Losing data, caused by... Read more

Latest Forum Discussions

See All

Make Way for Fat Chicken, from the Maker...
Make Way for Fat Chicken, from the Makers of Scrap Squad Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Relevant Games has announced they will be releasing their reverse tower defense game, | Read more »
Tripnary Review
Tripnary Review By Jennifer Allen on November 26th, 2014 Our Rating: :: TRAVEL BUCKET LISTiPhone App - Designed for the iPhone, compatible with the iPad Want to create a travel bucket list? Tripnary is a fun way to do exactly that... | Read more »
Ossian Studios’ RPG, The Shadow Sun, is...
Ossian Studios’ RPG, The Shadow Sun, is Now Available for $4.99 Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Mmmm, Tasty – Having the Angry Birds for...
The very first Angry Birds debuted on iOS back in 2009. When you sit back and tally up the number of Angry Birds games out there and the impact they’ve had on pop culture as a whole, you just need to ask yourself: “How would the birds taste... | Read more »
Rescue Quest Review
Rescue Quest Review By Jennifer Allen on November 26th, 2014 Our Rating: :: PATH BASED MATCH-3Universal App - Designed for iPhone and iPad Guide a wizard to safety by matching gems. Rescue Quest might not be an entirely original... | Read more »
You Can Play the Final Chapter of Lone W...
You Can Play the Final Chapter of Lone Wolf: Dawn Over V’taag Right Now Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Swords of Anima (Games)
Swords of Anima 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: A new tactical turn-based RPG experience. Command the Savior Rex Squad in an epic journey of courage and deception. Can you... | Read more »
Audio Defence: Zombie Arena
Audio Defence: Zombie Arena By Lee Hamlet on November 26th, 2014 Our Rating: :: DRAGS ITS FEETUniversal App - Designed for iPhone and iPad From the makers of Papa Sangre comes a defense game that forces players to listen carefully... | Read more »
Tales from the Borderland​s Will be Comi...
Tales from the Borderland​s Will be Coming to iOS by the End of the Year Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Telltale Games has announced | Read more »
Sunburn! Review
Sunburn! Review By Campbell Bird on November 26th, 2014 Our Rating: :: DON'T DIE ALONEUniversal App - Designed for iPhone and iPad Platform through the depths of space to make sure your entire crew dies together in this satisfying... | Read more »

Price Scanner via MacPrices.net

2014 1.4GHz Mac mini on sale for $449, save $...
 B&H Photo has the new 1.4GHz Mac mini on sale for $449.99 including free shipping plus NY tax only. Their price is $50 off MSRP, and it’s the lowest price available for this new model. Adorama... Read more
Early Black Friday pricing on 27-inch 5K iMac...
 B&H Photo continues to offer Black Friday sale prices on the 27″ 3.5GHz 5K iMac, in stock today and on sale for $2299 including free shipping plus NY sales tax only. Their price is $200 off MSRP... Read more
Early Black Friday sale prices on iPad Air 2,...
 MacMall is discounting iPad Air 2s by up to $75 off MSRP as part of their Black Friday sale. Shipping is free: - 16GB iPad Air WiFi: $459 $40 off - 64GB iPad Air WiFi: $559 $40 off - 128GB iPad Air... Read more
Early Black Friday MacBook Air sale prices, $...
 MacMall has posted early Black Friday MacBook Air sale prices. Save $101 on all models for a limited time: - 11″ 1.4GHz/128GB MacBook Air: $798 - 11″ 1.4GHz/256GB MacBook Air: $998 - 13″ 1.4GHz/... Read more
Why iPhone 6 Tablet/Laptop Cannibalization Is...
247wallst.com blogger Douglas A. McIntyre noted last week that according to research posted on the Applovin blog site the iPhone 6 is outselling the iPhone 6 Plus by a wide margin . Hardly a surprise... Read more
Worldwide Tablet Growth Expected to Slow to 7...
The global tablet market is expected to record massive deceleration in 2014 with year-over-year growth slowing to 7.2%, down from 52.5% in 2013, according to a new forecast from International Data... Read more
Touchscreen Glove Company Announces New Produ...
Surrey, United Kingdom based TouchAbility specializes in design and manufacture of a wide variety of products compatible with touchscreen devices including smartphones, tablets and computers. Their... Read more
OtterBox Alpha Glass Screen Protectors for iP...
To complement the bigger, sharper displays on the latest Apple devices, OtterBox has introduced Alpha Glass screen protectors to the iPhone 6 and iPhone 6 Plus. The fortified glass screen protectors... Read more
Early Black Friday Mac Pro sale, 6-Core 3.5GH...
 B&H Photo has the 6-Core 3.5GHz Mac Pro on sale today for $3499 including free shipping plus NY sales tax. Their price is $500 off MSRP, and it’s the lowest price available for this model from... Read more
Early Black Friday sale price: 15-inch 2.2GHz...
 B&H Photo has the 2014 15″ 2.2GHz Retina MacBook Pro on sale today for $1699.99. Shipping is free, and B&H charges NY sales tax only. Their price is $300 off MSRP, equalling Best Buy’s price... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.