PHP: Design pattern for classes offering static and non-static methods
Asked Answered
A

3

6

My objective is to create class that can be used both static and non-static way. Both ways have to use the same methods, but in different way

Non-static way:

$color = new Color("#fff");
$darkenColor = $color->darken(0.1);

Static way:

$darkenColor = Color::darken("#fff", 0.1);

So in this example method darken can be used both on existing object and as static method of Color class. But depending on how it's used, it uses different parameters.

How should such class be designed? What is good pattern for creating such types of classes?

Class will have a lot of different methods so it should avoid massive checking code in beginning of each method.

Appendectomy answered 19/4, 2015 at 9:14 Comment(7)
is the color object immutable? Your code would suggest this, but it's not clear.Unpractical
I'm not sure what you mean. But I'd say it's not. You set some color and then modify it - it's basic objective of class - so object is changing it's propeties. You could use something like $color->darken()->darken()->saturate() etc.Appendectomy
Ok, so basically, just calling $color->darken(0.1); changes the state of the $color$ object? Yes, then it's not immutable. It was just unclear, because you assign the return value to $darkenColor, suggesting that you are returning a new object, instead of changing the state of the existing one (and possibly returning itself).Unpractical
Ok I get your point. Yes, state of object changes.Appendectomy
What is (the static) Color::darken supposed to return? An object instance or a string? If an object instance, there's basically no advantage to having a static method, as the same can be had with (new Color('#fff'))->darken(0.1). That's virtually the same code with ever so slightly different syntax.Dispassion
The best way is to have it return instance as instance can have magic method __toString that will allow it to be echoed etc.Appendectomy
So it really seems like there's very little reason to offer a static API on top of an OO API...!?Dispassion
U
1

PHP doesn't really support method overloading, so it's not all that simple to realize, but there are ways.

Why provide static and non-static?

What I would ask myself first though is if it is really needed to offer both static and non-static approaches. It seems overly complicated, possibly confusing to the users of your color class, and doesn't seem to add all that many benefits. I would just go with the non-static approach and be done with it.

Static Factory Class

What you basically want are static factory methods, so you could create an extra class that realizes this:

class Color {

    private $color;

    public function __construct($color)
    {
        $this->color = $color;
    }

    public function darken($by)
    {
        // $this->color = [darkened color];
        return $this;
    }
}

class ColorFactory {
    public static function darken($color, $by) 
    {
        $color = new Color($color);
        return $color->darken($by);
    }
}

An alternative would be to put the static method inside Color and give it a different name, eg createDarken (this should be the same each time, so all static factory methods would be called createX for user convenience).

callStatic

Another possibility is to use the magic methods __call and __callStatic. The code should look something like this:

class Color {

    private $color;

    public function __construct($color)
    {
        $this->color = $color;
    }

    // note the private modifier, and the changed function name. 
    // If we want to use __call and __callStatic, we can not have a function of the name we are calling in the class.
    private function darkenPrivate($by) 
    {
        // $this->color = [darkened color];
        return $this;
    }

    public function __call($name, $arguments)
    {
        $functionName = $name . 'Private';
        // TODO check if $functionName exists, otherwise we will get a loop
        return call_user_func_array(
            array($this, $functionName),
            $arguments
        );
    }

    public static function __callStatic($name, $arguments)
    {
        $functionName = $name . 'Private';
        $color = new Color($arguments[0]);
        $arguments = array_shift($arguments);
        // TODO check if $functionName exists, otherwise we will get a loop
        call_user_func_array(
            array($color, $functionName),
            $arguments
        );
        return $color;

    }
}

Note though that this is somewhat messy. Personally, I would not use this approach, because it's not that nice for the users of your class (you can't even have proper PHPDocs). It is the simplest for you as programmer though, because you do not need to add a lot of extra code when adding new functions.

Unpractical answered 19/4, 2015 at 11:5 Comment(0)
R
1

According to your code examples and your comments, I understand that your method ->darken() will modify a property in the $color object, based on the current value of that property. On the other hand, the static ::darken() method will return the value of the darkened color...

If I understood it correctly, you may do something like:

class Color {

    protected $value;

    public static function darkenColor($value, $coef) {
        $darkened = //Darken $value using $coef somehow...
        return $darkened;
    }

    public function darken($coef) {
        $darkened = Color::darkenColor($this->value, $coef);
        $this->value = $darkened;
        return $darkened;
    }
}

With this approach, you don't need to repeat the code of darkening the color, and you offer both the dynamic and static methods from the class.
The static method "does the job", and can be called independently, and the dynamic method just uses the static one to make the calculations and assign the result to the object property.
Note that you have to use different names for the methods as PHP does not support overloading methods...


That said, personally I'd move the static methods to a different class(es), e.g., ColorManager or ColorCalculator, or even better, if the code to darken a color is complex enough and you'll have more operations like that, I'd create a ColorDarkener class with just that method...

class Color {

    protected $value;

    public function darken($coef) {
        $darkened = ColorDarkener::darken($this->value, $coef);
        $this->value = $darkened;
        return $darkened;
    }
}

class ColorDarkener {

    public static function darken($value, $coef) {
        $darkened = //Darken $value using $coef somehow...
        return $darkened;
    }
}
Ruffle answered 20/4, 2015 at 10:34 Comment(0)
V
1

Since you specifically ask about Design:

How should such class be designed?

They shouldn't be.

You're providing two ways of doing the same exact thing. This is almost never a good idea. It violates the Single Responsibility Principle as well as other SOLID principles.

Instead, keep your object stateful, and provide helper functions:

class Color {
    public function __construct($color) {/**/}
    public function darken($by) {/**/}
}
function darken($color, $by) {
    return (new Color($color))->darken($by);
}

Making it static just isn't necessary since PHP supports first-class functions.

As far as Design Patterns, no OOP design pattern exists for static methods, because static methods are not OOP. They are functional or procedural. Therefore, just use a function...

Vasiliki answered 21/4, 2015 at 18:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.