Question

I am working on a simple project involving Prospects and Offers. This project will integrate with a third-party mailing list provider, which will use Prospect objects to manage email addresses on the list and Offer objects to manage campaigns.

One of my concerns is that any Mailing List provider (e.g., MailChimp) may decide to stop offering their service, or change the terms in the future. Rather than make my software reliant on the provider, I want to make it reliant on a generic interface, which could be reimplemented using a different class which uses a different Mailing List provider. That way, if such a thing did happen, I would simply write a new class and instantiate in place of the old class.

This seems to be easy enough the implement. My abstract class, included below, defines abstract methods which take either Prospect or Offer objects and do generic Mailing List related functions to them, returns true/false or integer values where required. This interface should meet the needs of my application quite well.

<?php
/**
 * MailingList file.
 *
 * Contains the class definition for the abstract class Monty_MailingList.
 * @author Lewis Bassett <lewis.bassett@bassettprovidentia.com>
 * @version 0.1
 * @package Monty
 */

/**
 * Represents the interface for all MailingList classes.
 *
 * Adhereing to this interface means that if a MailingList provider
 * (e.g., MailChimp) stops a service, a new class can extend this interface and
 * be replace the obsolete class with no need to modify any of the client code.
 *
 * @author Lewis Bassett <lewis.bassett@bassettprovidentia.com>
 * @version 0.1
 * @package Monty
 * @copyright Copyright (c) 2011, Bassett Providentia
 */
abstract class Monty_MailingList
{
    /**
     * Adds the passed prospect to the mailing list, or returns false if the
     * prospect already exists. Throws an error if the prospect could not be
     * added for any reason (other than it already existing).
     *
     * @param Monty_Prospect $prospect The prospect object to be added to the
     * mailing list.
     * @return bool Whether or not the prospect was added.
     */
    abstract public function addProspect(Monty_Prospect $prospect);

    /**
     * Updates the properties stored on the mailing list of the passed prospect,
     * or returns false if no data was updated. If the prospect is not found, a
     * they are added to the list. Throws an error if the prospect could not be
     * added or updated for any readon.
     *
     * @param Monty_Prospect $prospect The prospect object whose mailing list
     * data is to be updated.
     * @return bool Whether or not the prospect was updated.
     */
    abstract public function updateProspect(Monty_Prospect $prospect);

    /**
     * Returns true if the passed prospect object could be found on the mailing
     * list.
     *
     * @param Monty_Prospect $prospect The prospect object to be searched for.
     * @return bool Whether or not the prospect was found.
     */
    abstract public function findProspect(Monty_Prospect $prospect);

    /**
     * Deletes the passed prospect from the mailing list, or returns false if
     * the passed object is not found on the mailing list.
     *
     * @param Monty_Prospect $prospect The prospect to be deleted.
     * @return bool Whether or not the prospect was deleted.
     */
    abstract public function deleteProspect(Monty_Prospect $prospect);

    /**
     * Creates a campaign for the passed offer object, or returns false if the
     * campaign already exists. Throws an error if the campaign could not be
     * created for any reason (other than it already existing).
     *
     * @param Monty_Offer $offer The offer to be created.
     * @return bool Whether or not the offer was created.
     */
    abstract public function createOffer(Monty_Offer $offer);

    /**
     * Sends the campaign for the passed offer object, or returns false if the
     * campaign could not be sent for a reasonable reason (run out of credit or
     * something). If the campaign does not yet exist, it is created. Throws an
     * error if the campaign could not be created, or an was not sent for an
     * unknown reason.
     *
     * @param Monty_Offer $offer The offer to be sent.
     * @return bool Whether or not the offer was sent.
     */
    abstract public function sendOffer(Monty_Offer $offer);

    /**
     * Returns true if a campaign for the passed offer object could be found on
     * the mailing list.
     *
     * @param Monty_Offer $offer The offer to be searched for,
     * @return bool Whether or not the offer was found.
     */
    abstract public function findOffer(Monty_Offer $offer);

    /**
     * Returns the ammount of opens registered for the passed offer. Throws an
     * error if a campaign is not found for the passed offer.
     *
     * @param Monty_Offer $offer The offer in question.
     * @return int The ammount of registered opens for that offer.
     */
    abstract public function getOfferOpens(Monty_Offer $offer);

    /**
     * Returns the ammount of clicks registered for the passed offer. Throws an
     * error if a campaign is not found for the passed offer.
     *
     * @param Monty_Offer $offer The offer in question.
     * @return int The ammount of registered clicks for that offer.
     */
    abstract public function getOfferClicks(Monty_Offer $offer);

    /**
     * Returns the ammount of bounces registered for the passed offer. Throws an
     * error if a campaign is not found for the passed offer.
     *
     * @param Monty_Offer $offer The offer in question.
     * @return int The ammount of registered bounces for that offer.
     */
    abstract public function getOfferBounces(Monty_Offer $offer);

    /**
     * Returns the ammount of unsubscribes registered for the passed offer.
     * Throws an error if a campaign is not found for the passed offer.
     *
     * @param Monty_Offer $offer The offer in question.
     * @return int The ammount of registered unsubscribes for that offer.
     */
    abstract public function getOfferUnsubscribes(Monty_Offer $offer);
}

Here comes the dilemma.

In the future, the data that is passed between my application the Mailing List provider may change, however, I don't want to have to keep changing the interface everywhere. By passing objects into the methods, I can modify the methods to use new properties as they become available, without changing the interface anywhere. This seems like a very flexible solution.

But

I would like to use this class in other projects, that might not necessarily use Prospect or Offer classes. The interface above seems too tightly coupled, from the perspective of the class, in that the class is reliant on the objects being passed to it.

Does anyone have any suggestions about how I might keep the flexibilty of passing objects to the methods, but have a class that I can easily reuse for other projects?

Many thanks if you have read this far! I am always looking to improve my skills and I'll be very grateful for your insight into how I might make this better.

Was it helpful?

Solution 3

After some more thought, I've come up with what I think is the best solution, thanks to some inspiration from Design patterns : elements of reusable object-oriented software (Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides).

I now have two abstract classes:

MailingListRecipient - defines the interface for objects that will represent a recipient of the mailing list. All client code will be written for this interface, and won't care what child class of this abstract code implements it. It will have methods to set a forename, surname and email address and to add, update, remove and find the recipient on the mailing list.

MailingListMessage - defines the interface for objects that will represent a message on the mailing list, and will have some setter methods and some actions defined. Again, client code will be written for this interface, and won't care how a subclass implements it.

I will then have an abstract factory class:

MailingListFactory - this creates MailingListRecipient and MailingListMessage objects throughout my client code.

So, for a real implementation, I will create:

MailChimpRecipient - to represent a recipient on a MailChimp list. Code in here will adhere to the interface defined by MailingListRecipient, and the object will need an API key and ListId in its constructor.

MailChimpMessage - to represent a message on a MailChimp list. Code here will adhere to the interface defined by MailingListMessage, and this object too will need an API key and ListId in its constructor.

My client code won't interact with either of the two objects above. Instead, within one of my settings files, I'll create an object of:

MailChimpFactory - used to create MailChimp recipients and messages. The object will need the API key and ListId, and will in turn pass these to the constructors of the above two classes in order to create MailChimp specific objects.

So, in my settings code I'll create a factory object:

$mailingListFactory = new MailChimpFactory($apiKey, $listId);

Then, throughout my client code, new Recipients and Messages will be created thus:

$recipient = $mailingListFactory->createMailingListRecipient();

From then on, it will be able to set things and do actions:

$recipient->setForename('Lewis');
$recipient->setEmailAddress('lewis@example.com');
$recipient->add();

If MailChimp suddenly stop their service, or I decide to use another mailing list provider, I'll just create new child classes of MailingListRecipient and MailingListMessage that use a new provider - their interfaces will be the same, and the client code won't know or care that it's different.

I'll then create a new child class of MailingListFactory that will create new Recipient and Message objects of the new classes. All I'll need to do is change the instantiation in my settings file:

$mailingListFactory = new newMailingListProviderFactory($username, $password);

Because the rest of my code is written for the interfaces defined in my abstract factories, nothing else will need to be changed.

Using an abstract factory ensures that I won't ever get into a situation where code is using mailChimpRecipient objects and newMailingListProviderMessage objects.

This meets both my objectives:

Interchangeablity - I can swap my mailing list classes and the code will still work as before;

Reusability - I can take those classes and use them in other projects.

This seems the most elegant way to do this. If anyone else has a better way, I'd love to hear about it. Thanks for your replies everyone.

OTHER TIPS

If you want a more generic approach, create a class where you add people instead of prospects, and email messages instead of offers, i.e., a generic interface to (any) mailing list. Then make your Monty_MailingList inherit the generic list.

I agree with Emil in some part.

You are mixing the concerns here. Your class is called mailing list which should have nothing with the prospects and offers, but with people and content you want to send them.

Prospects and Offers are business logic models which can have one representation sent via email to users, as they can have the other representation when rendered to the web page. Collecting stats from mails is separate thing as well.

One thing I don't agree is on the inheritance point, as I never do usually. I would not inherit anything here but create separate classes that handle their part and use composition instead.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top