views:

47

answers:

1

Sorry my title is not great, this is my first real punt at moving 100% to OO as I've been procedural for more years than I can remember. I'm finding it hard to understand if what I'm trying to do is possible. Depending on people's thoughts on the 2 following points, I'll go down that route.

The CMS I'm putting together is quote small, however focuses very much on different types of content. I could easily use Drupal which I'm very comfortable with, but I want to give myself a really good reasons to move myself into design patterns / OO-PHP

1) I have created a base 'content' class which I wish to be able to extend to handle different types of content. The base class, for example, handles HTML content, and extensions might handle XML or PDF output instead. On the other hand, at some point I may wish to extend the base class for a given project completely. I.e. if class 'content-v2' extended class 'content' for that site, any calls to that class should actually call 'content-v2' instead. Is that possible?

If the code instantiates an object of type 'content' - I actually want it to instantiate one of type 'content-v2'... I can see how to do it using inheritance, but that appears to involve referring to the class explicitly, I can't see how to link the class I want it to use instead dynamically.

2) Secondly, the way I'm building this at the moment is horrible, I'm not happy with it. It feels very linear indeed - i.e. get session details > get content > build navigation > theme page > publish. To do this all the objects are called 1-by-1 which is all very static. I'd like it to be more dynamic so that I can add to it at a later date (very closely related to first question).

Is there a way that instead of my orchestrator class calling all the other classes 1-by-1, then building the whole thing up at the end, that instead each of the other classes can 'listen' for specific events, then at the applicable point jump in and do their but? That way the orchestrator class would not need to know what other classes were required, and call them 1-by-1.

Sorry if I've got this all twisted in my head. I'm trying to build this so it's really flexible.

+1  A: 

For your question about working with Content and Content-v2 (which are horrible names, btw)...

  1. Go read more about encapsulation and polymorphism
  2. I think you need a BaseContent class that is an abstract class that is never consumed in any way other than by inheriting.
  3. From your BaseContent class, you should then have an HtmlContent, PdfContent, MSWordContent, etc. classes. And if you want, you can even extend these, such as with a HtmlReportContent (extends HtmlContent), MSWord2010Content, etc.
  4. Doing it this way, you can declare and store variables all of type BaseContent but when you instantiate them, you instantiate them as your specific type. When you do this, even if you have a Render() method implemented in your BaseContent class, your children classes can opt to use the base Render() method or override it and provide their own implementation, or even override it, provide a little custom implementation, and then call the base implementation to benefit from that implementation as well.

If you have shared implementation or behavior (for example, a Chihuahua and a GermanShepard class would both have a Bark() function but they would be implemented differently), then abstract that shared implementation or behavior to a base class. You don't necessarily have to provide an implementation in your base class - that's why it's an abstract class - it then forces your child classes to implement it (Bark() would be defined in your BaseDog class but not implemented).

As for the flow of your system... For your Object-Oriented design, think of designing a state machine and your objects are essentially filling out that state machine's purpose and every change from one state to another state. Yeah, there is probably one way to walk through your state-flow diagram that covers a good 50% of scenarios, but we all know that users will deviate from that. So you need to define the ways that a user can deviate from it and allow those but restrict to those. You may or may not actually draw out a flow diagram, depending on how complex your system is. But sometimes it helps to visualize some of the possibilities by drawing out part of it. Typically, though, this diagram would be way too large for most OO systems to actually create. If you were doing a system for NASA, you would probably have it, though. :-P

Here is some code to help illustrate some of these things (and to address some of your questions in the comments). However, I'm NOT a PHP guy so I'm going to give you C# examples but I'll keep them simple enough that you should be able to translate into PHP easily enough.

public interface IAnimal
{
    string GetName();
    string Talk();
}

public abstract class AnimalBase : IAnimal
{
    private string _name;

    // Constructor #1
    protected AnimalBase(string name)
    {
        _name = name;
    }

    // Constructor #2
    protected AnimalBase(string name, bool isCutsey)
    {
        if (isCutsey)
        {
            // Change "Fluffy" into "Fluffy-poo"
            _name = name + "-poo";
        }
    }

    // GetName implemention from IAnimal.
    // In C#, "virtual" means "Let the child class override this if it wants to but is not required to"
    public virtual string GetName()
    {
        return _name;
    }

    // Talk "implementation" from IAnimal.
    // In C#, "abstract" means "require our child classes to override this and provide the implementation".
    // Since our base class forces child classes to provide the implementation, this takes care of the IAnimal implementation requirement.
    abstract public string Talk();
}

public class Dog : AnimalBase
{
    // This constructor simply passes on the name parameter to the base class's constructor.
    public Dog(string name)
        : base(name)
    {
    }

    // This constructor passes on both parameters to the base class's constructor.
    public Dog(string name, bool isCutsey)
        : base(name, isCutsey)
    {
    }

    // Override the base class's Talk() function here, and this satisfy's AnimalBase's requirement to provide this implementation for IAnimal.
    public override string Talk()
    {
        return "Woof! Woof!";
    }
}

public class SmallDog : Dog
{
    private bool _isPurseDog;

    // This constructor is unique from all of the other constructors.
    // Rather than the second boolean representing the "isCutsey" property, it's entirely different.
    // It's entirely a coincidence that they're the same datatype - this is not important.
    // Notice that we're saying ALL SmallDogs are cutsey by passing a hardcoded true into the base class's (Dog) second parameter of the constructor.
    public SmallDog(string name, bool isPurseDog)
        : base(name, true)
    {
        _isPurseDog = isPurseDog;
    }

    // This tells us if the dog fits in a purse.
    public bool DoesThisDogFitInAPurse()
    {
        return _isPurseDog;
    }

    // Rather than using Dog's Talk() implementation, we're changing this because this special type of dog is different.
    public override string Talk()
    {
        return "Yip! Yip!";
    }
}

public class Chihuahua : SmallDog
{
    private int _hatSize;

    // We say that Chihuahua's always fit in a purse. Nothing else different about them, though.
    public Chihuahua(string name, int hatSize)
        : base(name, true)
    {
        _hatSize = hatSize;
    }

    // Of course all chihuahuas wear Mexican hats, so let's make sure we know its hat size!
    public int GetHatSize()
    {
        return _hatSize;
    }
}

public class Cat : AnimalBase
{
    // This constructor simply passes on the name parameter to the base class's constructor.
    public Cat(string name)
        : base(name)
    {
    }

    // This constructor passes on both parameters to the base class's constructor.
    public Cat(string name, bool isCutsey)
        : base(name, isCutsey)
    {
    }

    // Override the base class's Talk() function here, and this satisfy's AnimalBase's requirement to provide this implementation for IAnimal.
    public override string Talk()
    {
        return "Meoooowwww...";
    }
}

public class Lion : Cat
{
    public Lion(string name)
        : base(name)
    {
    }

    // Rather than using Cat's Talk() implementation, we're changing this because this special type of cat is different.
    public override string Talk()
    {
        return "ROAR!!!!!!!!";
    }
}

public class ThisIsNotAGoodExampleOfObjectOrientedCoding
{
    public string DoStuff()
    {
        // To keep the C#-to-PHP translation easy, think of this as an array of IAnimal objects.
        List<IAnimal> myAnimals = new List<IAnimal>();

        IAnimal strayCat = new Cat("Garfield", false);
        Cat myPet = new Cat("Katrina");
        IAnimal myMothersDog = new Dog("Harley");
        Dog myMothersOtherDog = new Dog("Cotton");
        IAnimal myNeighborsDog = new SmallDog("Roxy", false);
        Dog movieStarsDog = new SmallDog("Princess", true);
        Dog tacoBellDog = new Chihuahua("Larry", 7);
        Lion lionKing = new Lion("Simba");

        myAnimals.Add(strayCat);
        myAnimals.Add(myPet);
        myAnimals.Add(myMothersDog);
        myAnimals.Add(myMothersOtherDog);
        myAnimals.Add(myNeighborsDog);
        myAnimals.Add(movieStarsDog);
        myAnimals.Add(tacoBellDog);
        myAnimals.Add(lionKing);

        string allAnimalsTalking = "";

        // Create a string to return.
        // Garfield says "Meow". Fido says "Woof! Woof!" etc...
        for (int i = 0; i < myAnimals.Count; i++)
        {
            allAnimalsTalking = allAnimalsTalking + myAnimals[i].GetName() + " says \"" + myAnimals[i].Talk() + "\" ";

            if (myAnimals[i] is SmallDog)
            {
                // Cast the IAnimal into a SmallDog object.
                SmallDog yippyDog = myAnimals[i] as SmallDog;

                if (yippyDog.DoesThisDogFitInAPurse())
                {
                    allAnimalsTalking = allAnimalsTalking + " from a purse.";
                }
            }
        }

        return allAnimalsTalking;
    }
}

I hope this helps some.

Jaxidian
Although dated, his looks like a good resource (free PHP OO eBook) to get started with some of the basics: http://www.pdf-word.net/Tutorial-Programming-PHP/Object-Oriented-Programming-In-PHP5.html
Jaxidian
Thanks so much for your brilliant answer, very much appreciated! I thought I understood polymorphism, but clearly not as I did not believe it to be a solution...Ps those class names were not the real ones, just an attempt to illustrate my question :0)
CitrusTree
So what's confusing me is illustrated here: http://en.wikipedia.org/wiki/Polymorphism_in_object-oriented_programming#PHPI understand the Cat/ Dog thing, but Cat and Dog are still referred to explicitly toinstanciate them. How do we call Cat or Dog depending on the current state. Let's say each record in thecontent table recorded it's content type. Could you use something like:$content = new $row['type'];$content->publish;... where $row is the current recordset row and type is a (valid) class name?
CitrusTree
Ah, and what if the constructor requires parameters? (mine does)
CitrusTree
Okay, I've modified my answer with some code. Unfortunately I'm not a PHP guy so I hope you can understand this from some C# code. I think I addressed everything you asked about with that code. And the next thing that I think you're going to ask about (about more intelligently instantiating the specific animal types but still keeping it more generic with your code), I'm going to suggest you look into either Dependency Injection or the Factory Pattern. Both are far too large a topic for me to cover here, though. :-)
Jaxidian
Thanks so much. I'll do exactly that!
CitrusTree