Factory Patterns
The Factory and Abstract Factory are creational design patterns used to create objects without exposing the creation logic to the client. The main difference between the Factory and Abstract Factory is that the Factory pattern is a single method that creates objects, while the Abstract Factory is a super-factory that creates other factories.
Although Metalama can be used to create a Factory aspect that generates a factory method for a class (or even an Abstract Factory class), it’s generally not worth the effort unless you have a large number of highly trivial factories that just call the constructor.
Instead, you can use Metalama Architecture Verification to verify that no code except the factories directly calls the constructor. By doing this, you’re making the design intent explicit and executable, reporting warnings in real-time in case of violation.
Example
Suppose we have a base interface IShape
and want to verify that only the ShapeFactory
class can create instances of the interface. We can add a [UseFactory]
aspect to it.
[UseFactory( typeof(ShapeFactory) )]
public interface IShape
{
public Color Color { get; }
}
It automatically applies to derived types:
public class Circle : IShape
{
public Circle( double radius, Color color )
{
Radius = radius;
Color = color;
}
public double Radius { get; }
public Color Color { get; }
}
We now get a warning whenever we try to directly use the constructor:
Drawing CreateDrawing()
{
// WARNING! Use ShapeFactory to get an instance of this class.
var circle = new Circle( 1.1, Color.Red );
return new Drawing { circle };
}
Show me how it works!
The UseFactoryAttribute
class is an inheritable aspect that verifies that constructors of derived types are used only from a given type or from a unit test.
[Inheritable]
internal class UseFactoryAttribute : TypeAspect
{
private readonly Type _factoryType;
public UseFactoryAttribute( Type factoryType )
{
this._factoryType = factoryType;
}
public override void BuildAspect( IAspectBuilder<INamedType> builder )
=> builder.Outbound.SelectMany( t => t.Constructors )
.CanOnlyBeUsedFrom(
scope => scope.Namespace( "**.Tests" )
.Or()
.Type( this._factoryType ),
$"Use '{this._factoryType.Name}' to get an instance of this class." );
}
Metalama benefits
- Reduce human errors. You can be confident that no rogue code is calling the constructor directly instead of getting an instance from the DI container.
Resources
- Blog post: The Factory design pattern in C#