Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Professional C++ [eng].pdf
Скачиваний:
284
Добавлен:
16.08.2013
Размер:
11.09 Mб
Скачать

Chapter 26

cout << inPlayer->getName() << “ wants to play again” << endl; return true;

}else {

//The player said no, or is offline.

cout << inPlayer->getName() << “ does not want to play again” << endl; return false;

}

}

The Adapter Pattern

The motivation for changing the abstraction given by a class is not always driven by a desire to hide functionality or protect against performance concerns. Sometimes, the underlying abstraction cannot be changed but it doesn’t suit the current design. In this case, you can build an adapter or wrapper class. The adapter provides the abstraction that the rest of the code uses and serves as the bridge between the desired abstraction and the actual underlying code. You’ve already seen adapters in use by the STL. Recall that the STL provides container adapters, such as the stack and queue, which are wrappers around other containers like deque and list.

Example: Adapting an XML Library

In Chapter 24, you read about the Xerces XML parsing library. Xerces is a great general-purpose tool — it implements many obscure XML standards and provides much flexibility. However, there are several reasons why you might want a wrapper around Xerces. Your use case might be simple enough that you require only a subset of Xerces’ functionality. By writing a wrapper, you can maximize ease of use for the features that are relevant to you. Also, putting a wrapper around Xerces gives you the freedom to switch between different XML libraries. Perhaps you foresee a move to custom XML code down the road, or wish to allow users to write their own XML parsing code. As long as their code supports the same interface as your wrapper, it will work.

Implementation of an Adapter

The first step in writing an adapter is reading and understanding the class or library that you’re going to adapt. If you are unfamiliar with Xerces, you should review Chapter 24 before continuing.

The next step is to define the new interface to the underlying functionality. For this example, we will assume that users only need the Xerces features that were discussed in Chapter 24 — the ability to read XML elements, attributes, and text nodes. A single class, ParsedXMLElement, serves as an adapter to Xerces. The client creates a ParsedXMLElement from a file, which represents the root node. All subelements of that element are also represented as ParsedXMLElements. The following class definition shows the public functionality of ParsedXMLElement:

// ParsedXMLElement.h

#include <string> #include <vector>

class ParsedXMLElement

{

768

Applying Design Patterns

public:

ParsedXMLElement(const std::string& inFilename); ~ParsedXMLElement();

std::string getName() const; std::string getTextData() const;

std::string getAttributeValue(const std::string& inKey) const; std::vector<ParsedXMLElement*> getSubElements() const;

};

Because the adapter will be using Xerces behind the scenes, some additions are needed to this class definition. The ParsedXMLElement will be responsible for initializing the Xerces library when the first ParsedXMLElement root object is created and terminating the library when the last root object is deleted. In order to implement this functionality, the ParsedXMLElement needs to keep a static count of the number of root element objects in existence. Additionally, each ParsedXMLElement will contain a pointer to a Xerces DOMElement, which is used to actually obtain the parsed data. The XercesDOMParser object will need to remain in existence as long as associated DOMElements exist. The parser will live in the root object, so a comment warns clients that subelements are invalid once the root element is destroyed. Here is the modified definition of ParsedXMLElement:

// ParsedXMLElement.h

#include <string> #include <vector>

#include <xercesc/util/PlatformUtils.hpp> #include <xercesc/parsers/XercesDOMParser.hpp> #include <xercesc/dom/DOM.hpp>

XERCES_CPP_NAMESPACE_USE

/**

*Note: If the root element is deleted, subelements become

*invalid.

*/

class ParsedXMLElement

{

public:

ParsedXMLElement(const std::string& inFilename); ~ParsedXMLElement();

std::string getName() const; std::string getTextData() const;

std::string getAttributeValue(const std::string& inKey) const;

//The caller is responsible for freeing the ParsedXMLElements

//pointed to by the elements of the vector. std::vector<ParsedXMLElement*> getSubElements() const;

protected:

// This constructor is used internally to create subelements. ParsedXMLElement(DOMElement* inElement);

XercesDOMParser* mParser;

DOMElement* mElement;

769

Chapter 26

static int

sReferences;

private:

// Disallow copy construction and op=. ParsedXMLElement(const ParsedXMLElement&); ParsedXMLElement& operator=(const ParsedXMLElement& rhs);

};

The implementation of the wrapper is very similar to the examples in Chapter 24, so we won’t go into too much detail here: the code below should speak for itself. The important point is that every public method of ParsedXMLElement is really fronting calls to Xerces. We hope you agree that ParsedXMLElement provides a friendlier interface to this subset of Xerces functionality:

#include “ParsedXMLElement.h”

#include <xercesc/util/XMLString.hpp>

#include <iostream>

XERCES_CPP_NAMESPACE_USE using namespace std;

// No references by default

int ParsedXMLElement::sReferences = 0;

ParsedXMLElement::ParsedXMLElement(const std::string& inFilename)

{

if (sReferences == 0) {

// First element--initialize the library XMLPlatformUtils::Initialize();

}

sReferences++;

mParser = new XercesDOMParser(); mParser->parse(inFilename.c_str());

DOMNode* node = mParser->getDocument();

DOMDocument* document = dynamic_cast<DOMDocument*>(node); if (document == NULL) {

cerr << “WARNING: No XML document!” << endl; return;

}

mElement = dynamic_cast<const DOMElement*>(document->getDocumentElement()); if (mElement == NULL) {

cerr << “WARNING: XML Document had no root element!” << endl;

}

}

ParsedXMLElement::~ParsedXMLElement()

{

if (mParser != NULL) {

// This is the root element.

770

Applying Design Patterns

delete mParser;

sReferences--;

if (sReferences == 0) {

// Last element destroyed XMLPlatformUtils::Terminate();

}

}

}

string ParsedXMLElement::getName() const

{

char* tagName = XMLString::transcode(mElement->getTagName()); string result(tagName);

XMLString::release(&tagName);

return result;

}

string ParsedXMLElement::getTextData() const

{

//We assume that the first text node we reach is the one we want. DOMNodeList* children = mElement->getChildNodes();

for (int i = 0; i < children->getLength(); i++) {

DOMText* textNode = dynamic_cast<DOMText*>(children->item(i)); if (textNode != NULL) {

char* textData = XMLString::transcode(textNode->getData()); string result(textData);

XMLString::release(&textData); return result;

}

}

//No text nodes were found.

return “”;

}

string ParsedXMLElement::getAttributeValue(const std::string& inKey) const

{

XMLCh* key = XMLString::transcode(inKey.c_str());

const XMLCh* value = mElement->getAttribute(key); XMLString::release(&key);

char* valueString = XMLString::transcode(value); string result(valueString); XMLString::release(&valueString);

return result;

}

vector<ParsedXMLElement*> ParsedXMLElement::getSubElements() const

{

771