Leveraging the Power of Abstract Classes

20, Dec 2012

Contents

What is an Abstract Class?

You can create an Abstract class in just about every common language these days. An abstract class is unlike regular classes (Concrete Classes) in that it can not be instantiated. It may contain callable methods, but the class itself can never be created. If it can't be instantiated, then what good is it? Well, its purpose is to be used as a base class to provide common methods to a set of concrete classes that extend the abstract. This is incredibly useful in a large amount of circumstances, for example when you've got multiple different data sources to connect to, but you want to write the least amount of decision logic on how to handle the input sources.

In this example we'll work with the following situation: There's an application that needs to be able to connect to some sort of streaming data source, this connection can either be through JMS, or through a Network Link (polling a remote file over HTTP), or through some other yet to be decided protocol.

Obviouslly the connection methods for both of these approaches will be completely different, but we want to make it as seamless as possible for the application to use these methods. Abstract classes will simplify this greatly.

The Base Class

An abstract class serves as a base class, and all the properties or methods it implements will be passed on to any concrete classes that extend it. Additionally, the abstract class can also require that any concrete classes using it implement additional functions. Let's begin with a base for a Feed Reader; no matter what the connection protocol will be, we're going to need a property to store the source of the external data, something to store the retrieved message itself, and our concrete class needs to implement a read() function to perform it's own data access method. Additionally, let's say there's always some extra processing that has to be done with the retrieved message before we're ready to pass it on to the next stage of the application, but because the data format received is the same no matter the source, all concrete classes will need to be able to use this function.

<?php
/**
 * Any sort of Feed Reader must implement this class. Provides base
 * functions to require common access means.
 */
abstract class FeedReader
{
    /**
     * Where the message is coming from.
     */
    private $_source;
    /**
     * Stores the actual message received from read()
     */
    private $_msg = '';

    /**
     * An abstract public function requires every concrete class
     * that uses FeedReader to provide a __construct() function,
     * otherwise PHP will throw a Fatal error.
     */
    abstract public function __construct();

    /*
     * This abstract function is how all the different implementations
     * of a Feed will actually connect to its source and read a message.
     */
    abstract public function read();

    /**
     * This public function is accessible to all concrete classes
     * that extend FeedReader
     */
    public function processMsg() {
        // Transform the received message into some other format.
        // ....
        return $this->_msg;
    }
}
?>

Making Use of Abstraction

Network Links

Now that we have our Abstract class that dictates how a generic Feed works, it's time to implement the individual cases. Let's start with a Network Link. The way a Network Link works, is that it goes to some file accessible somewhere on the network, and downloads it, treating the contents there as a unique message.

<?php
class NetworkLink extends FeedReader
{
    /**
     * Our abstract class requires that we implement a __construct
     * function, in this case, the only thing a network link
     * takes in is where the remote file is located.
     */
    public function __construct($source) {
        $this->_source = $source;
    }

    /**
     * Again, the abstract class requires a read function to be provided.
     * To read a network link, we simply get the contents of some remote
     * file, and treat it as the message.
     */
    public function read() {
        $this->_msg = file_get_contents($this->_source);
        if ($this->msg != '') {
            return true;
        } else {
            return false;
        }
    }
}
?>

That's all there is to it! We now have a working Network Link, and we can use it as simply as:

<?php
$Reader = new NetworkLink('http://www.remote.com/file.txt');
if ($Reader->read()) {
    print $Reader->processMsg();
} else {
    print $Reader->error;
}
?>

JMS Feeds

Now though, it's time to deal with JMS Feeds. We know we want to implement these basically the same way, and our abstract class tells us exactly how to do it:

<?php
class JMSFeed extends FeedReader
{
    /**
     * A JMS feed requires some additional information to connect that
     * is unique to itself, so thse properties are declared here rather
     * than in the base class.
     */
    private $_user, $_pass, $_topic;

    /**
     * This private function helps us setup a JMS connection,
     * again, since it is unique to JMS feeds, it goes here
     * rather than in the abstract class.
     * We're going to be using the Stomp library to perform
     * our connection to the JMS broker.
     */
    private function _setupJMS() {
        $con = new Stomp($this->_source);
        $con->connect($this->_user, $this->_pass);
        $con->subscribe('/topic/' . $this->_topic);

        return $con;
    }

    /**
     * As required by the abstract class, we provide a __construct
     * function. This one takes in different parameters than the
     * NetworkLink class. Some JMS feeds don't require authorization,
     * so the user and pass parameters are made optional.
     */
    public function __construct($source, $topic, $user = '', $pass = '') {
        $this->_source = $source;
        $this->_topic = $topic;
        $this->_user = $user;
        $this->_pass = $pass;
    }

    /**
     * This is how to read a JMS Feed, we do a quick try/catch to
     * make sure the connection succeeded, and then carry on.
     */
    public function read() {
        try {
            $con = $this->_setupJMS();
        } catch (StompException $e) {
            $this->error = $e->getMessage();
            $this->msg = '';
            return false;
        }

        $startTime = time();
        // Sometimes it takes a bit for a JMS connection to be ready
        // give it a bit of time to wait for a message to come in.
        while (!$con->hasFrameToRead()) {
            if (time() > $startTime + 10) {
                // If there's nothing there after 10 seconds, then
                // we can give up.
                $this->error = 'No frame to read';
                $this->msg = '';
                $con->unsubscribe('/topic/' . $this->_topic);
                $con->disconnect();

                return false;
            }
        }

        // Read the message, acknowledge it, and cleanup.
        $msg = $con->readFrame();
        $con->ack($this->msg);
        $this->_msg = $msg->body;

        $con->unsubscribe('/topic/' . $this->_topic);
        $con->disconnect();

        return true;
}
?>

So, that was a little more complex, but it is implemented the same way. Let's see it in action:

<?php
$Reader = new JMSFeed('192.168.10:61613', 'TopicName', 'corey', 'coreypw');
if ($Reader->read()) {
    print $Reader->processMsg();
} else {
    print $Reader->error;
}
?>

Extending Further

That looks familiar right? Since the actual usage of these two classes is the same, it's easy to extend this capability to allow multiple different types of feeds to interact with the application through very minimal amounts of work:

<?php
if ($type == 'networklink') {
    $Reader = new NetworkLink($source);
} else if ($type == 'jms') {
    $Reader = new JMSFeed($source, $topic, $user, $pass);
} else if ($type == 'other') {
    $Reader = new OtherFeed($source, $param, $param2, $param3, $param4);
} else if ($type == 'yetanother') {
    $Reader = new YetAnotherFeed($source, $param, $param2);
} else {
    throw new Exception("Feed type $type unknown");
}

// We're guaranteed to have these two functions available because of the
// abstract function, no matter what kind of feed we're working with. Sure,
// we have to assume that the Concrete class will implement them correctly,
// but that's not too big of a leap of faith.
if ($Reader->read()) {
    print $Reader->processMsg();
} else {
    print $Reader->error;
}
?>

Wrap Up

That's the quick overview of Abstract classes and the benefits that they can provide you. Hopefully this was helpful; let me know!

Comments

This space intentionally left blank.

Have Something to Say?

Questions? Comments? Concerns? Let me know what you’re thinking.

  • You can use Markdown formatting here.