Let's say I have an object, Car, and it has sets of methods that are... disparate? Maybe getting Blob-like? (as in the antipattern, "blob") These methods operate in distinct enough sets and don't really criss-cross functionally.
Car
# methods related to suburban driving
->foo1
->foo2
# methods related city driving
->bar3
->bar4
Now let's say I wanted to add "off road driving" as one of the cases that my car object can support. If I add methods to the Car object, isn't that making it more blob like?
Car (augmented)
# methods related to suburban driving
->foo1
->foo2
# methods related city driving
->bar3
->bar4
# methods related off road driving
->blah5
->blah6
Should I:
A: Subclass Car. I can make an OffRoadCar extends Car object. But what if Car and OffRoadCar share the same data/state, and only differ by methods? OffRoadCar only "acts" more specifically than car, but isn't described any more specifically nor does it have unique fields. So instantiating an OffRoadCar is meaningless.
OffRoadCar extends Car
# methods related off road driving
->blah5
->blah6
B: Consume Car with a Static Method. Like OffRoadAdventure->Embark(Car). So the OffRoadAdventure class takes a Car object and has its way with it.
In C# this would be called an extension method.
I guess put another way is what is the solution to the Blob antipattern? Is it subclassing? Is it extension methods?
Also, another reason I wanted to silo out these methods is what if their implementation incurs some cost, like the inclusion of other classes/packages? I wouldn't want users of the core class to constantly pay for something they would never use. I figure making the cost explicit for the minority is worth the savings for the majority. And it makes the dependency graph more precise (and not blob-like).
PS - Implementation language is Perl.
With Perl you should be taking a good look at Moose and roles.
package Suburban;
use Moose::Role;
requires 'wheels';
sub 'foo1' {
...
}
sub 'foo2' {
...
}
package City;
use Moose::Role;
requires 'wheels';
sub 'bar3' {
...
}
sub 'bar4' {
...
}
package Offroad;
use Moose::Role;
requires 'wheels';
sub 'blah5' {
...
}
sub 'blah6' {
...
}
package UltraCar;
use Moose;
with qw/ Offroad City Suburban /;
has 'wheels' => ( is => 'rw', isa => 'Int', default => 4 );
package RentalCar;
use Moose;
with qw/ City Suburban /;
has 'wheels' => ( is => 'rw', isa => 'Int', default => 4 );
package Tricycle;
use Moose;
with qw/ Offroad /;
has 'wheels' => ( is => 'rw', isa => 'Int', default => 3 );
You can even add and remove roles at runtime.
I think you are in the right track. An elegant solution is to have OffRoadCar inherit from Car and override its Drive() method. That way, you can say:
Car myCar = new OffRoadCar();
myCar.Drive();
This will call the overridden Drive() method in the subclass, and you don't end up with a gazillion methods in a single "blob". Here's how the Drive() method in OffRoadCar may look like:
#Override
public void Drive()
{
/* code */
}
Instead of subclassing the Car class, you could use some form of Decorator pattern.
Here, the base ORM class and the decorators (say OffRoadDecorator) implement some common interface, and the behaviour of each decorator can be dynamically added to the original class.
The key difference here is that multiple decorators can be used on the same Car class, and it's up to your implementation to say how those should interact. There's nothing to stop you decorating your class using the OffRoadDecorator as well as the SuburbanDecorator, and you can decide exactly what that means for the car within the decorators.
(P.S. sorry I don't provide any code examples; my Perl is simply terrible. Have a look at this page for some other examples.)
Suburban, City and Offroad Driving sound like algorithms. Strategy Pattern might help here with Context being Car.
Not really an answer Mark and I'm not sure how deeply you want to go into this, but you could look into the idea of Method Resolution Order. Instead of the standard depth first search for methods, using mro allows you to perform a breadth first search. See this page on CPAN and brian d foy's article Use the C3 method resolution order in multiple inheritance for more.
Related
I have a small (soon to be large) set of Moose roles that interact with a database. They have methods like create_foo_record, update_foo_record, delete_foo_record, etc. (They aren't simple wrapper for database access, as they do things like translate and normalize data, log actions, etc. In some cases, they may create and update related records too.)
This code is pretty generic, so rather than have two dozen modules of cut-and-paste code, I would like a base module that implements these methods, using configuration parameters telling them what tables, columns, normalization functions, etc.
My instinct is to write something "higher-order" that generates these functions, but I think it's more appropriate to do that the Moose way. (I am a Moose novice, however...)
The naive way would be to have the base class be a role, and it's methods take configuration arguments, but this seems messy.
Ideally, I'd like the base class to have private configuration that the roles that use it set up, something like
__PACKAGE__->config( foo => 'bar' );
but I am unsure how to do this using Moose. My attempts have gotten errors complaining about extending a class into a role. Or the config parameters turns out not to be private, and interferes with other roles or is being interfered with by the corresponding config in Catalyst controllers that use the roles.
If it's possible to make use of 'Catalyst::Component' in the base class but somehow extend that to roles, that would be ideal. But I don't know how to do that, or even if it is possible.
Thanks.
Leaving aside that this sounds an awful lot like DBIx::Class::SchemaLoader, if I understand you correctly you want...
A thing to store system configuration information.
It has defaults.
Those defaults can be overridden on a per class basis.
One way to do it is to create a role and take advantage of builder methods. The role defines the defaults via the builder methods, and each class can override.
use v5.10;
{
package MyConfig;
use Moose::Role;
has user => (
is => 'ro',
lazy => 1,
builder => '_build_user',
);
sub _build_user {
return "default_user";
}
}
{
package MyApp;
use Moose;
with 'MyConfig';
sub _build_user {
return "some_user";
}
}
{
package DefaultApp;
use Moose;
with 'MyConfig';
}
say MyApp->new->user; # some_user
say DefaultApp->new->user; # default_user
Another is to take advantage of attribute inheritance.
use v5.10;
{
package MyConfig;
use Moose::Role;
has user =>
is => 'ro',
lazy => 1,
default => "default_user",
;
}
{
package MyApp;
use Moose;
with 'MyConfig';
has '+user' =>
default => 'some_user'
;
}
{
package DefaultApp;
use Moose;
with 'MyConfig';
}
say MyApp->new->user; # some_user
say DefaultApp->new->user; # default_user
They're basically the same thing. The former is done in a more traditional OO style and is more mechanistic. The latter is done using a bit of Moose magic and is more declarative and compact. Each has minor advantages and disadvantages, use which ever is comfortable to you.
I think what I need are parameterized roles http://metacpan.org/pod/MooseX::Role::Parameterized
I have a Moose::Role that I would like to call some extra subs on the class when that role is applied to the class.
Is there an easy way to modify what happens when the role is applied, without having to dig too much into Moose::Meta::Role type coding? Ideally, I'd just like to after 'apply' => ... to add the extra stuff.
Edit:
I'm specifically using this with a DBIx::Class::Core result definition to create something like a component that also modifies the constructor. I would just write it as a component if I could get at BUILDARGS and BUILD subs for the result, but I can't seem to do. So, instead of doing load_component, I doing with 'role', but some of the effects of the component are to add belongs_to relationships to the class. Hence, I was thinking the best way to do that is during application of the role to the class.
In a briefly-lived comment I referred you to this question, which discusses how to access the metaclass of the class the role is being applied to (e.g. so you can build onto the class conditionally). However, that's a really stinky use of MooseX::Role::Parameterized providing you that information, and it also won't work if the role is being applied to another role, not to a class.
As an alternative, you could write a sugar function which receives the meta information, and build onto the class in that way:
sub foo
{
my ($meta, %options) = #_;
# based on what is present in %options, add additional attributes...
$meta->add_attribute(...);
}
See Moose::Cookbook::Extending::Recipe4 for an example of writing sugar functions.
You could use a parameterized role. There is an example on how to access the consuming class in the tutorial. That being said, I would advise you to join the Moose and DBIx-Class IRC channels or mailing lists to look for best-practices in this regard.
What I found that works, is compact, and seems in keeping with intent in the docs is to use a trait to modify the meta role used by my particular role:
package DBIx::Class::Meta::Role::MyRole;
use Moose;
BEGIN { extends 'Moose::Meta::Role'; }
after 'apply' => sub {
## ..my mods to add extra relationships to DBIx::Class::Core result
};
no Moose;
package DBIx::Class::MyRole;
use Moose::Role -metaclass => 'DBIx::Class::Meta::Role::MyRole';
How would you name a package who's sole purpose was to extend another module so you could apply roles to it? I need a package that extends (sub classes) Template::Context with Moose So I can then create roles and traits to apply to it, but I don't know what to name this package (class). Any advice?
Since its Moose-specific role-ification, I'd have Moose in the name. Template::Context::Moosified. Or Template::Context::WithAntlers.
But having an intermediate subclass just so you can stick roles onto it is weird. You can skip that middleman and simply declare composed classes directly.
package Template::Context::ForBreakfast;
use Moose;
extends "Template::Context";
with "Bacon", "Eggs", "Toast";
The class name should fall out of the role composition.
I'm not sure this is approved but you can always try applying the Role directly.
package R;
use Moose::Role;
sub f { say 42 }
package main;
use URI;
R->meta->apply( Moose::Meta::Class->initialize( 'URI' ) );
URI->new->f
Granted this needs some sugaring up, has absolutely no guarantees to work long term, and is probably totally unsupported. This is however how the MOP unsugared effectively works.
I want to create a generic class, whose builder would not return an instance of this generic class, but an instance of a dedicated child class.
As Moose does automatic object building, I do not get to understand if this something possible, and how to create a Moose class with Moose syntax and having this behaviour.
e.g.:
The user asks: $file = Repository->new(uri=>'sftp://blabla') .... and is returned an `Repository::_Sftp`` instance
User would use $file as if it is a Repository instance, without the need to know the real subclass (polymorphism)
Note:
As requested, maybe i should have been more clear about what i was trying to achieve:
The purpose of my class is to be able to add new Repository schemes (eg over sftp), by simply creating an "hidden" Repository::_Stfp class, and adding a case in the Repository constructor to factory the correct specialized object depending of url. Repository would be like a virtual base class, providing an interface that specialized objects would implement.
All of this is for adding new repository schemes without having the rest of the program to be modified: it would unknowingly deal with the specialized instance as if it is a Repository instance.
new builds the builder. You want some other method to actually return the built object.
Here's an example:
class RepositoryBuilder {
has 'allow_network_repositories' => (
is => 'ro',
isa => 'Bool',
required => 1,
);
method build_repository(Uri $url) {
confess 'network access is not allowed'
if $url->is_network_url && !$self->allow_network_repositories;
my $class = $self->determine_class_for($url); # Repository::Whatever
return $class->new( url => $url );
}
}
role Repository { <whatever }
class Repository::File with Repository {}
class Repository::HTTP with Repository {}
Here, the builder and the built object are distinct. The builder is a
real object, complete with parameters, that can be customized to build
objects as the situation demands. Then, the "built" objects are
merely return values of a method. This allows you to build other
builders depending on the situation. (A problem with builder
functions is that they are very inflexible -- it's hard to teach them
a new special case. This problem still exists with a builder object,
but at least your application can create a subclass, instantiate it,
and pass this object to anything that needs to create objects. But
dependency injection is a better approach in this case.)
Also, there is no need for the repositories you build to inherit from
anything, they just need a tag indicating that they are repositories.
And that's what our Repository role does. (You will want to add the
API code here, and any methods that should be reused. But be careful
about forcing reuse -- are you sure everything tagged with the
Repository role will want that code? If not, just put the code in
another role and apply that one to the classes that require that
functionality.)
Here's how we use the builder we created. If, say, you don't want to
touch the network:
my $b = RepositoryBuilder->new( allow_network_repositories => 0 );
$b->build_repository( 'http://google.com/' ); # error
$b->build_repository( 'file:///home/whatever' ); # returns a Repository::Foo
But if you do:
my $b = RepositoryBuilder->new( allow_network_repositories => 1 );
$b->build_repository( 'http://google.com/' ); # Repository::HTTP
Now that you have a builder that builds the objects the way you like,
you just need to use these objects in other code. So the last piece
in the puzzle is referring to "any" type of Repository object in other
code. That's simple, you use does instead of isa:
class SomethingThatHasARepository {
has 'repository' => (
is => 'ro',
does => 'Repository',
required => 1,
);
}
And you're done.
No (not directly). In general in Moose, calling CLASS->new where CLASS isa Moose::Object will return an instance of CLASS.
Can you describe in more detail what you are trying to achieve, and why you think this is what you want? You probably want to build a factory class -- when you call a method on it, it will call the appropriate class's constructor and return that object to you, without you having to be concerned with the particular type you get back:
package MyApp::Factory::Repository;
sub getFactory
{
my ($class, %attrs);
# figure out what the caller wants, and decide what type to return
$class ||= 'Repository::_Sftp';
return $class->new(attr1 => 'foo', attr2 => 'bar', %attrs);
}
my $file = MyApp::Factory::Repository->getFactory(uri=>'sftp://blabla');
After creating a metaclass using Moose::Meta::Class->create, how do I instantiate a real Moose class with that class as a metaclass?
(I need to create the metaclass also because I also want to apply some roles to it.)
The metaclass is the class, of course. If you want an instance of that class, just do:
my $instance = $meta->name->new
You might also need to make sure that $meta doesn't get collected too soon. Generally, you do this:
$meta->add_method( meta => sub { $meta } );
That will keep the metaclass around, but you're going to leak the class if you aren't careful. If you only do this once, it won't matter; if you do it thousands of times, you could get yourself into trouble.
Much better to use something higher-level like Moose::Meta::Class::create_anon_class or MooseX::Traits.
Not sure this answers this or your other SO question How do I build a Moose class at runtime, add a method to it, apply a role to it and instantiate it once? How would you approach this? at Building a Moose class at runtime and tuning it but have a look at:
MooseX::SingletonMethod
It may do what you want. Or you may find it useful to peer into our it works.
The documentation does provide links to blog posts I made while coming to grips with building this module so you may find those helpful also.
Here is an brief code example of MooseX::SingletonMethod:
{
package Foo;
use MooseX::SingletonMethod;
sub bar { say 'bar' }
}
my $baz = Foo->new;
my $bar = Foo->new;
$baz->add_singleton_method( baz => sub { say 'baz' } );
$baz->bar; # => bar
$bar->bar; # => bar
$baz->baz; # => baz
$bar->baz; # Throws can't find baz error
/I3az/