Zydeco » Manual » 04_Factories

Factories to help your objects make other objects

Synopsis

  package MyApp {
    use Zydeco;
    
    class Person {
      has name = "Anonymous";
      has gender;
      
      factory new_man ( Str $name ) {
        return $class->new(name => $name, gender => 'm');
      }
      
      factory new_woman ( Str $name ) {
        return $class->new(name => $name, gender => 'f');
      }
    }
  }
  
  my $alice = MyApp->new_woman("Alice");
  my $bob   = MyApp->new_man("Bob");

Traditionally in Perl, if you define a class "MyApp::Person", you would instantiate objects using MyApp::Person->new(%args).

In Zydeco, you use MyApp->new_person(%args) instead. This means that there is one central package ("MyApp" in the example) which constructs all your objects.

Having all your objects instantiated through one package makes it easy to control which objects are constructed from one central place. For example, if all of your code is using MyApp->new_database_connection to get a connection to your database, you can override that method to insert a connection to a testing copy of your database when testing your app.

The Default Factory Method

When you define a class, a factory method is created for you by default.

  package MyApp {
    use Zydeco;
    class Person;
  }
  
  # Here's the factory method being used:
  my $bob = MyApp->new_person(%args);

The default factory method just passes on any arguments to your class's constructor. In this example, that would be MyApp::Person->new(%args). Because Zydeco uses Moo to build your class, you can use the standard Moo "BUILD" and "BUILDARGS" methods to alter tweak object construction.

Renaming the Default Factory Method

By default, the factory method is the same as the class name, but lower-cased, with "::" replaced by "_", and with "new_" as a prefix. So the class "Vehicle::Car::Electric" will have a factory method called "new_vehicle_car_electric". You may wish to rename the factory method:

  package MyApp {
    use Zydeco;
    ...;
    
    class Vehicle::Car::Electric {
      factory new_electric_car;
    }
  }

Defining Custom Factory Methods

The factory keyword can also be used with a block and optionally a signature to offer more control over object construction.

  package MyApp {
    use Zydeco;
    
    class Person {
      has name = "Anonymous";
      has gender;
      
      factory new_man ( Str $name ) {
        return $class->new(name => $name, gender => 'm');
      }
      
      factory new_woman ( Str $name ) {
        return $class->new(name => $name, gender => 'f');
      }
    }
  }

In this example, we create two factory methods for the "Person" class. If a class has custom factory methods, then the default one ("new_person") won't be made. The custom factory methods are defined instead of the default one, not as well as.

If you need the default one too, this is pretty simple.

  package MyApp {
    use Zydeco;
    
    class Person {
      has name = "Anonymous";
      has gender;
      
      factory new_man ( Str $name ) {
        return $class->new(name => $name, gender => 'm');
      }
      
      factory new_woman ( Str $name ) {
        return $class->new(name => $name, gender => 'f');
      }
      
      factory new_person;    # default factory method
    }
  }

Within these methods, the variable $class is defined, which holds the class being constructed as a string. The variable $factory is the factory package as a string. This allows you to easily construct other objects which may be needed by the current object.

  package MyApp {
    use Zydeco;
    
    class Car {
      has wheels = [];
      has colour;
      
      factory new_four_wheeler ( $colour ) {
        $class->new(
          colour => $colour,
          wheels => [
            $factory->new_wheel,
            $factory->new_wheel,
            $factory->new_wheel,
            $factory->new_wheel,
          ],
        );
      }
    }
    
    class Wheel;
  }

A key feature of custom factory methods is that although they are logically part of the factory package, they are lexically defined within the class, so have access to its lexical variables.

Factory Methods via a Proxy

It is possible to instead define additional methods in the class itself and then install factory methods that call those.

  package MyApp {
    use Zydeco;
    
    class Person {
      has name = "Anonymous";
      has gender;
      
      method new_man ( Str $name ) {
        return $class->new(name => $name, gender => 'm');
      }
      
      factory new_guy via new_man;
      
      method new_woman ( Str $name ) {
        return $class->new(name => $name, gender => 'f');
      }
      
      factory new_gal via new_woman;
    }
  }

Inside method blocks, a $factory variable is available, which is preset to $self->FACTORY.

One advantage of using a proxy is that subclasses can inherit the method and potentially override it.

Singletons

Factories make it pretty easy to implement the singleton pattern.

  package MyApp {
    use Zydeco;
    
    class AppConfig {
      ...;
       
      factory get_appconfig () {
        state $config = $class->new();
      }
    }
  }

Now MyApp->get_appconfig will always return the same AppConfig object. Because any explicit use of the factory keyword in a class definition suppresses the automatic creation of a factory method for the class, there will be no MyApp->new_appconfig method for creating new objects of that class.

(People can still manually call MyApp::AppConfig->new to get a new AppConfig object, but remember Zydeco discourages calling constructors directly, and encourages you to use the factory package for instantiating objects!)

Abbreviated Syntax

Like with regular methods, the abbreviated syntax is allowed.

  factory new_man   ($n) = $class->new(name => $n, gender => 'm');
  factory new_woman ($n) = $class->new(name => $n, gender => 'f');

Keywords

In this chapter, we looked at the following keyword:

factory
Next Steps

We have already looked at how to define methods, but now let's look at multimethods, which can sometimes be a more elegant way to define methods.