patterncsharpMinor
Pattern-matching-esque code
Viewed 0 times
codeesquepatternmatching
Problem
I've come across a need for code like this a few times - you have an object (or objects) that may or may not have certain properties and values within those properties. Consider a program with multiple types that have a different set of "Name" type properties on each of them. Your Contact type has all of them, however, maybe the user type only has a first name, and middle initial.
I once had a project that had 5 different types with different assortments of those types of properties on them, and in each, had a method to get the Name, that looked through each and attempted to construct a string with all of the relevant information, and no leading, trailing, or double spaces. But this logic was duplicated.
At the time, I ended up just making single property interfaces for the different properties,
But I have always been hesitant on it, for what I feel is the lack of specificity/declarative-ness of the solution. Thus I have come up with the idea of a 'Fulfillment' pattern (I can't think of a better name, and am open to suggestions).
The idea is you have an interface for fulfillers:
Now, I can make extension methods off of lists to find the closest match:
```
public static class IFulfillableExtensions
{
public static TGet Fulfill(this IEnumerable> fulfillers, TGive give)
{
foreach (var fulfiller in fulfillers)
{
TGet get;
if(fulfiller.TryFulfill(give, out get))
return get;
}
throw new ArgumentOutOfRangeException("The given object is unable to be fulfilled by any of the specified fulfillers");
}
publ
I once had a project that had 5 different types with different assortments of those types of properties on them, and in each, had a method to get the Name, that looked through each and attempted to construct a string with all of the relevant information, and no leading, trailing, or double spaces. But this logic was duplicated.
At the time, I ended up just making single property interfaces for the different properties,
IFirstName, ILastName, etc.. As well as an extension method off of a parent INamed interface that used whatever it had available to make the name. Applied this to the 5 types and their usages, and it definitely helped with reducing duplication of similar logic.But I have always been hesitant on it, for what I feel is the lack of specificity/declarative-ness of the solution. Thus I have come up with the idea of a 'Fulfillment' pattern (I can't think of a better name, and am open to suggestions).
The idea is you have an interface for fulfillers:
public interface IFulfillable
{
bool Fulfillable(TGive give);
TGet Fulfill(TGive give);
}Now, I can make extension methods off of lists to find the closest match:
```
public static class IFulfillableExtensions
{
public static TGet Fulfill(this IEnumerable> fulfillers, TGive give)
{
foreach (var fulfiller in fulfillers)
{
TGet get;
if(fulfiller.TryFulfill(give, out get))
return get;
}
throw new ArgumentOutOfRangeException("The given object is unable to be fulfilled by any of the specified fulfillers");
}
publ
Solution
Your current implementation is closed for extension a thus it violates the OCP (Open/Closed Priciple). You cannot extend it witout modifying the core
This task is another good exmaple for the usefulness of the composite pattern.
You first create an abstract class:
this will be the base for each property formatter.
Next you implement it for each interface (hier just two):
Now you need to be able to combine them so you create:
and a helper extension:
Having all this you can now add a new formatter anytime you want without touching the core. Hier is how you use it:
result:
FullNameFulfill class.This task is another good exmaple for the usefulness of the composite pattern.
You first create an abstract class:
abstract class PropertyFormatter
{
public static PropertyFormatter Empty = new CompositePropertyFormatter();
public string FormatString { get; set; } = "{0}";
public abstract string Format(object obj);
}this will be the base for each property formatter.
Next you implement it for each interface (hier just two):
class FirstNameFormatter : PropertyFormatter
{
public override string Format(object obj)
{
return (obj as IFirstName)?.FirstName;
}
}
class LastNameFormatter : PropertyFormatter
{
public override string Format(object obj)
{
return (obj as ILastName)?.LastName;
}
}Now you need to be able to combine them so you create:
class CompositePropertyFormatter : PropertyFormatter
{
private readonly PropertyFormatter[] _formatters;
public CompositePropertyFormatter(params PropertyFormatter[] formatters)
{
_formatters = formatters;
}
public override string Format(object obj)
{
return string.Join(string.Empty, _formatters.Select(x
=> string.Format(x.FormatString, x.Format(obj)))
).Trim();
}
}and a helper extension:
static class PropertyFormatterComposer
{
public static CompositePropertyFormatter Add(
this PropertyFormatter formatter,
string formatString = " {0}")
where T : PropertyFormatter, new()
{
return new CompositePropertyFormatter(
formatter,
new T { FormatString = formatString });
}
}Having all this you can now add a new formatter anytime you want without touching the core. Hier is how you use it:
var contact = new Contact
{
FirstName = "Foo",
LastName = "Bar"
};
var contactFormatter = PropertyFormatter.Empty
.Add()
.Add(", {0}");
var result = contactFormatter.Format(contact);result:
Foo, BarCode Snippets
abstract class PropertyFormatter
{
public static PropertyFormatter Empty = new CompositePropertyFormatter();
public string FormatString { get; set; } = "{0}";
public abstract string Format(object obj);
}class FirstNameFormatter : PropertyFormatter
{
public override string Format(object obj)
{
return (obj as IFirstName)?.FirstName;
}
}
class LastNameFormatter : PropertyFormatter
{
public override string Format(object obj)
{
return (obj as ILastName)?.LastName;
}
}class CompositePropertyFormatter : PropertyFormatter
{
private readonly PropertyFormatter[] _formatters;
public CompositePropertyFormatter(params PropertyFormatter[] formatters)
{
_formatters = formatters;
}
public override string Format(object obj)
{
return string.Join(string.Empty, _formatters.Select(x
=> string.Format(x.FormatString, x.Format(obj)))
).Trim();
}
}static class PropertyFormatterComposer
{
public static CompositePropertyFormatter Add<T>(
this PropertyFormatter formatter,
string formatString = " {0}")
where T : PropertyFormatter, new()
{
return new CompositePropertyFormatter(
formatter,
new T { FormatString = formatString });
}
}var contact = new Contact
{
FirstName = "Foo",
LastName = "Bar"
};
var contactFormatter = PropertyFormatter.Empty
.Add<FirstNameFormatter>()
.Add<LastNameFormatter>(", {0}");
var result = contactFormatter.Format(contact);Context
StackExchange Code Review Q#119026, answer score: 3
Revisions (0)
No revisions yet.