简体   繁体   中英

How to implement factory design pattern

I am trying to apply the factory design pattern to a real world problem in an application. I am using data objects (kind of like ORM but not quite) where an object represents a row from the database and its properties are the columns. Having said this - I have 3 different types of users which all extend the base class User and are all kept in the same database table of users . I have an extra column type that I use to determine the type of user.

So to fetch a user with id 1 I would use this code:

$user = User::getByPK(1);

However the different types of users have different properties and methods and I need to get the appropriate instance, which is where I am stuck. I created a factory class but I am not quite sure how to call it. I need to have the information about the user from the database before I can determine what type of user it is, however I need to instantiate the appropriate class and these two things are kind of dependant of each other.

Here is what I made - I get the appropriate instance but all of the information for the user is kept in the base class.

class UserFactory {

    public function getUser($type) {

        switch($type) {
            case User::ORDINARY:
                return new OrdinaryUser();

            case User::MODERATOR:
                return new Moderator();

            case User::ADMINISTRATOR:
                return new Administrator();
        }
    }
}


$user = User::getByPK(1); // Has all the information

$factory = new UserFactory();

$object = $factory->getUser($user->type); // Is instance of Administrator

What is the proper way to use the factory pattern in this case?

The idea of the factory pattern is to decouple and encapsulate the logic required to create an instance of an object, from the object itself. This separates concerns.

You currently have a static method on your User class that is responsible for fetching the user information from the database. This couples your User object to your database. It's also very rigid.

  • What if your users are stored in a different type of database?
  • What if your users are stored in an XML file?
  • What if your users are stored in a key/value store?
  • What if you want to create a mock repository of users for writing tests?

The factory pattern allows you to abstract these concerns away, and provide alternate implementations of a factory depending on your needs (assuming you had a common interface for each implementation). Maybe the way you create a user changes, but you don't want to also change everywhere you've needed to create a user - just how the user is created. Isolating this code into a factory allows for this.

A factory might take an instance to your DAL as well, if you've abstracted your DAL. (I'll be using IDataAccessLayer as a placeholder for your preferred DAL)

class UserFactory {
    private IDataAccessLayer $dataAccessLayer;

    public function __constructor($dataAccessLayer) {
        $this->dataAccessLayer = $dataAccessLayer;
    }

    public function createUser($userId) {
        $userDataSet = $this->dataAccessLayer->someQuery($userId);

        switch ($userDataSet->type) {
            case UserType::Administrator:
                return createAdministrator($userDataSet);
            case UserType::Moderator:
                return createModerator($userDataSet);
            case UserType::Ordinary:
                return createOrdinaryUser($userDataSet);
        }
    }

    private function createAdministrator($userDataSet) { /* Create and return an administrator */ }
    private function createModerator($userDataSet) { /* Create and return a moderator */ }
    private function createOrdinaryUser($userDataSet) { /* Create and return an ordinary user */ }
}

You'd then use this like:

$factory = new UserFactory($dataAccessLayer);
$factory->createUser(1);

(Bear with me, I haven't coded PHP in some years, so some of my syntax may be off, but the idea is the same)

Now, personally, it looks like you need to get even more granular here. I'd create a UserRepository following the Repository pattern. This is because the factory shouldn't really be accessing the database in my opinion. It should just use the data from the database to create the user. The repository pattern should be responsible for getting the data from the database, and then feeding that data to the factory pattern to get an object.

class UserRepository {
    private IDataAccessLayer $dataAccessLayer;

    public function __constructor($dataAccessLayer) {
        $this->dataAccessLayer = $dataAccessLayer;
    }

    public function getUserById($id) {
        $userData = $this->dataAccessLayer->fetch(someQuery);

        $factory = new UserFactory();
        return $factory->createUser($userData);
    }
}

Then you'd have a simpler factory:

class UserFactory {
    public function createUser($userData) {
        switch ($userData->type) {
            case UserType::Administrator:
                return createAdministrator($userData);
            case UserType::Moderator:
                return createModerator($userData);
            case UserType::Ordinary:
                return createOrdinaryUser($userData);
        }
    }

    private function createAdministrator($userDataSet) { /* Create and return an administrator */ }
    private function createModerator($userDataSet) { /* Create and return a moderator */ }
    private function createOrdinaryUser($userDataSet) { /* Create and return an ordinary user */ }
}

And from your code, you'd have something like:

$userRepository = new UserRepository($dataAccessLayer); //This is probably done somewhere higher in your code.
$userRepository->getUserById(1);

You'll probably want to go ahead and create the following interfaces and implement these as well. This will allow you to enforce your contract, as well as set yourself up to be able to swap out implementations if you need.

public interface IUserRepository {
    function getUserById($userId);
}

and

public interface IUserFactory {
    function createUser($userData);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM