寻找装载机设计模式

Let's say I have the following classes whose instances store their property somewhere for e.x. in a JSON file or a database:

Class Foo

abstract class Foo
{
    protected $ID;
    protected $title;

    // getters and setters
}

Class Bar (extends Foo)

class Bar extends Foo
{
    protected $text;

    // getters and setters
}

Class Baz (extends also Foo)

class Baz extends Foo
{
    protected $tabs = array();

    // getters and setters
}

What's the best way to load them from the data source?

How I do it now (not satisfying)

My abstract class Foo has a method load($ID, PDO $pdo). This method is overwritten by Bar and Baz which extends the general loading of the title property in Foo class with their own properties which have to be loaded.

So in code this would look something like this:

Class Foo

abstract class Foo
{
    protected $ID;
    protected $title;

    public static function load($ID, PDO $pdo)
    {
        $result = null;

        // SQL query for getting $title property into $result

        return $result;
    }

    // getters and setters
}

In the class Bar I do this:

class Bar extends Foo
{
    protected $text;

    public function __construct(stdClass $data)
    {
        $this->ID = $data->ID;
        $this->text = $data->text;
    }

    public static function load($ID, $pdo)
    {
        $generalInfo = parent::load($ID, $pdo);

        $result = null;

        // PDO query for getting $text property into $result

        $generalInfo->text = $result;

        return $generalInfo;
    }

    // getters and setters
}

So this let me call $dataToCreateInstance = Bar::load(4, $pdoInstance) and returns the needed information to instanciate the specific Bar object with ID 4.

The probleme here is (as you can see) my classes are bound to PDO also it's really ugly to implement a load method for each data source so it's not generic at all.

What I want to achieve

I'm looking now for a pattern which let me load those classes from any source. I thought about a Loader class which looks like this:

class LoaderManager
{
    /**
     * @var Loader[]
     */
    protected $loaders = array();

    public function registerLoader($className, $loaderClass)
    {
        $this->loaders[$className] = $loaderClass;
    }

    public function loadClass($class, $ID)
    {
        return $this->loaders[$class]->load($ID);
    }
}

and with an abstract Loader class

abstract class Loader
{
    public abstract function load($ID);
}

So now I have decoupled the thing. The problem with this attempt is that I have always to provide an additional Loader for the class itselfs. So for the class Bar I have to provide at least one of BarLoaderPDO or BarLoaderJSON. Which is somehow not that elegant and feels a bit "wrong".

Also there is the problem that I have to store somewhere the mapping which class in the current application has to use which loader.

But it is the only attempt I can think of which leads me to that what I want to achieve.

I like now to discuss and hear if there are other (better) attempts and how to realize them.

I would say for a good desing you will end up will different desing patterns in your code, so ussually, template method, dependency injection, factory pattern and concepts such as encapsulation and data hiding is the trick. Try to encapsulate the behavior and use data hiding, dont let the object leak internal information, so take a look at Demeter´s Law. Here some sample design:

namespace DesignPatterns.LoaderPattern
{
public interface IBusiness
{
    void Operation1();
}

public abstract class BusinessTemplate : IBusiness
{
    protected readonly string title;
    protected readonly int id;

    protected BusinessTemplate(int id, string title)
    {
        this.id = id;
        this.title = title;
    }
    public abstract void Operation1();
}

public class FooBusinessConcrete : BusinessTemplate
{
    public FooBusinessConcrete(int id, string title) : base(id, title)
    {

    }

    public override void Operation1()
    {
        throw new NotImplementedException();
    }
}

public class BarBusinessConcrete : BusinessTemplate
{
    private readonly string text;

    public BarBusinessConcrete(int id, string title, string text) : base(id, title)
    {
        this.text = text;
    }

    public override void Operation1()
    {
        //Do something
    }
}

public sealed class BazBusinessConcrete : BarBusinessConcrete
{
    private readonly string[] arrayString;

    public BazBusinessConcrete(int id, string title, string text, string[] arrayString) : base(id, title, text)
    {
        this.arrayString = arrayString;
    }

    public override void Operation1()
    {
        //Do something
    }
}

/// <summary>
/// Create an itnerface for a factory that will return a single object
/// </summary>
public interface IBusinessFactory
{
    IBusiness CreateBusiness();
}

/// <summary>
/// Create a factory that will create the concrete type via constructor injection, 
/// so for each type you will create a single constructor
/// </summary>
public sealed class ConcreteFactory : IBusinessFactory
{
    protected IBusiness business;
    public ConcreteFactory(int id, string title)
    {
        business = new FooBusinessConcrete(id, title);
    }

    public ConcreteFactory(int id, string title, string text)
    {
        business = new BarBusinessConcrete(id, title, text);
    }

    public ConcreteFactory(int id, string title, string text, string[] arrayString)
    {
        business = new BazBusinessConcrete(id, title, text, arrayString);
    }

    public IBusiness CreateBusiness()
    {
        return business;
    }
}

/// <summary>
/// In Application code use dependency injection of the encapsulated business object
/// </summary>
public class ApplicationCode
{
    private readonly IBusiness business;

    public ApplicationCode(IBusiness business)
    {
        this.business = business;
    }

    public void Execute()
    {
        business.Operation1();
    }
}

/// <summary>
/// So from your client code you can use a factory that create the business object with encapsulated interface, behavior
/// </summary>
public class ClientCode
{
    public void RunType1(int id, string title)
    {
        var business = new ConcreteFactory(id, title).CreateBusiness();            
        new ApplicationCode(business).Execute();
    }

    public void RunType2(int id, string title, string text)
    {
        var business = new ConcreteFactory(id, title, text).CreateBusiness();
        new ApplicationCode(business).Execute();
    }

    public void RunType3(int id, string title, string text, string[] arrayString)
    {
        var business = new ConcreteFactory(id, title, text, arrayString).CreateBusiness();
        new ApplicationCode(business).Execute();
    }
}

}

You can achieve it. Please see the Loader class implementation in CodeIgnitor. You also need to use auto loading class and create the instance of the object dynamically from the variable.

Here is the link for possible implementation. https://github.com/bcit-ci/CodeIgniter/blob/develop/system/core/Loader.php

Good Luck.