14

I saw this code on a different question - 2nd answer link and the first comment was that it is a static factory anti-pattern and that it violates SRP:

class User {
    public static function create($userid) {
        // get user from the database

        // set $isPartner to true or false
        // set $isClient to true or false
        // set $isModerator to true or false

        if ($isPartner) {
            return new Partner($userid);
        } elseif ($isClient) {
            return new Client($userid);
        } elseif ($isModerator) {
            return new Moderator($userid);
        } else {
            return new User($userid);
        }
    }
}

$person = User::create($userid);

Now I can understand why it violates SRP - because it deals with connecting to the database as well as building the new class, but besides that I'm not sure if I understand why it is an anti-pattern.

I wanted to write a bit of code that seemed quite similar to this so I am now wondering whether to avoid it, this is my code (in pseudo-code):

class DatabaseClass()
{
...deals with getting a result from the database...
}

abstract class User()
{
...base class for all users...
}

class AdminUser extends User(){}
class StaffUser extends User(){}
class BasicUser extends User(){}

class UserFactory()
{
    function createUser($privilege)
    {
        if($privilege=="high")
            return new AdminUser($privilege);
        else if($privilege=="med")
            return new StaffUser($privilege);
        else
            return new BasicUser($privilege);
    }

$db=new DatabaseClass($username,$password);
$result=$db->getUser();
$userfactory=new UserFactory();
$user=$userfactory->createUser($result);

Now at the moment I am not using a static method but would my oop still be considered an anti-pattern?

Especially since I don't really see any difference in then doing something like this instead and it being pretty much the same thing:

$result=DatabaseClass::getUser($username,$password);
$user=UserFactory::createUser($result);
Community
  • 1
  • 1
mrmryb
  • 1,479
  • 1
  • 12
  • 18
  • Could you add a link to the other question? – Kevin Brydon Jan 24 '13 at 15:32
  • Added the link to the beginning of my question. The code example I give is the 2nd answer to the problem the OP was having, and the criticism was in the comments written by the accepted answer poster. – mrmryb Jan 24 '13 at 15:37

4 Answers4

10

No, it's not an anti-pattern. Personally, I take the term "anti-pattern" with a grain of salt whenever I see it. It's far too easily tossed around by people who don't like your code but can't really articulate why.

The problem with a static factory is that any class which uses the factory must explicitly depend on it. That violates the principle that we should depend on abstractions rather than concretions (the 'D' in SOLID). This makes code using the factory harder to re-use and unit test. But keep in mind that the approach has benefits too. It's easier to write and easier to understand.

Your code is equivalent to a static factory method. The problem in both cases is that the caller must know the concrete class of the factory.

Peter Ruderman
  • 12,241
  • 1
  • 36
  • 58
  • So if I were to go with the code I wrote, would it be better to instantiate each class first and then call the method, rather than doing my shortcut way with the statics? I don't quite understand why it depends on the factory, if we're talking about being able to test each class separately to make sure it's methods work, I can instantiate the class and give it it's dependencies still without the need for the factory..? And would there be a better way to write it so that it is not dependent on the factory? I can't imagine a way where the user doesn't need to know the concrete factory class. – mrmryb Jan 24 '13 at 15:48
  • The problem is in testing the code that uses the factory. For example, the line `$userfactory=new UserFactory();`, creates a concrete dependency on the UserFactory class. If you're ok with that (and really only you can judge if it's acceptable), then you can use either the static or instance approach. It makes no difference. Just be consistent across your codebase. – Peter Ruderman Jan 24 '13 at 15:52
  • The way you get around having to know the concrete class of the factory is by passing in an abstract factory interface to the consumer class. This is what's known as dependency injection. If you aren't using it already in your codebase, it will be a beast to add. I doubt it would be worth it. – Peter Ruderman Jan 24 '13 at 15:55
  • That makes sense, and I'm glad I'm not going completely the wrong direction with it, but I am very much wanting to stick to Dependency Injection if possible. I don't get what you mean though by 'passing in an abstract factory interface', an interface is just a blueprint for other classes to follow? I will have to look in to it but do you know of any good examples of 'passing in an abstract factory interface'? – mrmryb Jan 24 '13 at 16:01
  • The basic idea is that you create an abstract user factory class (IUserFactory or some such) from which all user factories will derive. You then change your consumer class to accept a factory instance in its constructor rather than creating one itself. In the consumer code, you must be careful to only call methods that are defined on the IUserFactory interface. – Peter Ruderman Jan 24 '13 at 16:10
  • Would that mean that instead of having AdminUser, StaffUser, BasicUser as extended classes I would just have the one class User that would build it's properties from whichever factory is passed in to it? What if I needed extra methods for the Admin that Staff did not have? – mrmryb Jan 24 '13 at 16:15
  • No. The code would remain largely the same. You would still have a UserFactory class. The difference is that the code that uses the user class wouldn't actually know about it. For example, instead of writing: `$db=new DatabaseClass($username,$password);` `$result=$db->getUser();` `$userfactory=new UserFactory();` `$user=$userfactory->createUser($result);` You would just write: `$user=$userFactory->createUser()` where $userFactory is a member variable of the calling class. – Peter Ruderman Jan 24 '13 at 16:21
  • Ok I think I'm starting to understand it a bit more, but still a lot of work to go. Thank you for all the extra explanations. Do you mean something like this? [Link](http://stackoverflow.com/questions/11369360/how-to-properly-setting-up-pdo-connection/11369679#11369679) – mrmryb Jan 24 '13 at 16:28
  • 1
    Yes. In your case, your UserFactory class would accept some sort of DB access interface in its constructor. – Peter Ruderman Jan 24 '13 at 16:31
2

Your factory is not the issue, is the conection to the db i guess. Other than that, you're using the factory method well.

Upon seen the edit, its for the best that you separate the data access from the factory. It's not so much an "anti-pattern" as a bad idea from the maintenance perspective.

I would still be ok (not so good but still ok) if you had your data access code inside the factory as long as that code was in a separate class (that is reused across the application for data access) and you just call it to get something you need. In that case it'd be a combination of two patterns, a factory and a facade.

What i'd really pay attenntion to is to find out if the data isn't going to change during the session, if so just go one time to the db an keep the results. If you only create the User (or derived classes) once, make damm sure you only do it once.

That i think is more important that respecting patterns blindly.

Carlos Grappa
  • 2,351
  • 15
  • 18
0

PHP has functionality of dynamical class instantiation where class name that should be instantiated could be a variable. The following code works fine:

$classname='User';

$Object=new $classname; //instantiates new User()

This code instantiates that class whose name is stored in $classname variable.

sbrbot
  • 6,169
  • 6
  • 43
  • 74
-2

I'm not so well with the Factory Pattern, however, if you want to have some benefit, it could abstract the creation based on persistence variation, e.g. if it's adapting the database for users or not.

class DatabaseUserFactory implements UserFactory
{
    private $dbClass;

    function __construct(DatabaseClass $dbClass)
    {
        $this->dbClass = $dbClass;
    }

    /**
     * @return user
     */
    function createUser()
    {
        $result = $db->getUser();
        return $this->createUserByPrivilege($result->getPrivilege());

    }

    private function createUserByPrivilege($privilege)
    {
        if ($privilege == "high")
            return new AdminUser($privilege);
        else if ($privilege == "med")
            return new StaffUser($privilege);
        else
            return new BasicUser($privilege);
    }
}

$db          = new DatabaseClass($username, $password);
$userfactory = new DatabaseUserFactory($db);

// ...

$user = $userfactory->createUser();
hakre
  • 193,403
  • 52
  • 435
  • 836