7. public class AllSubscriptions : IAllSubscriptions
{
...
public Subscription CreateOrUpdate(
string userName, string email, string organization,
DateTime currentMoment)
{
var guid = Guid.NewGuid().ToString("D", CultureInfo.InvariantCulture);
// INSERT INTO subscriptions(....)
// VALUES (...)
// ON DUPLICATE KEY UPDATE subscription_id = subscription_id
var subscription = FindByEmail(email);
if (subscription.ConfirmationGuid != guid
&& !new[] { SubscriptionStatus.Confirmed,
SubscriptionStatus.ToBeConfirmed
}.Contains(subscription.Status)
&& (subscription.Status != SubscriptionStatus.Confirming
|| subscription.MailedAt == null
|| subscription.MailedAt.Value < currentMoment.AddMinutes(-10)))
{
// UPDATE subscriptions SET status = 'ToBeConfirmed', ...
// WHERE subscription_id = subscription.id
}
return subscription;
}
8. И так попадет в
Это старая систему рассылки
запись
И так должен
получить письмо
if (subscription.ConfirmationGuid != guid со ссылкой
&& !new[] { SubscriptionStatus.Confirmed,
SubscriptionStatus.ToBeConfirmed
}.Contains(subscription.Status)
&& (subscription.Status != SubscriptionStatus.Confirming
|| subscription.MailedAt == null
|| subscription.MailedAt.Value <
currentMoment.AddMinutes(-10)))
С момента отправки письма со ссылкой
прошло больше 10 минут (возможно,
пользователь не получил его и повторно
подписывается) А если прошло меньше
времени, то это больше похоже
на «паразитный» Reload
14. // given
var subscription = new Subscription
{
Status = SubscriptionStatus.Confirming,
MailedAt = new DateTime(2011, 12, 17, 18, 10, 0),
};
var now = DateTime = new DateTime(2011, 12, 17, 18, 22, 0);
var sepcification = new SubscriptionCanBeRenewed(now);
// when
var isOk = sepcification.IsSatisfiedBy(subscription);
// then
isOk.Should().BeTrue();
FluentAssertions
16. public Subscription CreateOrUpdate(
string userName, string email, string organization,
DateTime currentMoment)
{
var guid = Guid.NewGuid().ToString("D", CultureInfo.InvariantCulture);
// INSERT INTO subscriptions(....)
// VALUES (...)
// ON DUPLICATE KEY UPDATE subscription_id = subscription_id
var subscription = FindByEmail(email);
var isOldRecord = subscription.ConfirmationGuid != guid;
if (isOldRecord)
{
var spec = new SubscriptionCanBeRenewed(currentMoment);
if (spec.IsSatisfiedBy(subscription))
{
// UPDATE subscriptions SET status = 'ToBeConfirmed', ...
// WHERE subscription_id = subscription.id
}
}
return subscription;
}
17. Но как теперь тестировать UPDATE?
• Можно сделать фабрику для спецификации,
инжектить ее в репозиторий, на тестах mock-ать.
Но это столько возни…
• Лучше продолжим отрывать куски в отдельные
классы
18. public interface ICommand<T> { void ExecuteFor(T obj); }
internal class SubscriptionRenewCommand : ICommand<Subscription>
{
public SubscriptionRenewCommand(IDataContext dataContext)
{ … }
public void ExecuteFor(Subscription obj)
{
// UPDATE subscriptions SET status = 'ToBeConfirmed', ...
// WHERE subscription_id = obj.id
}
}
20. public static class CommandExtensions
{
public static ICommand<T> If<T>(
this ICommand<T> command, ISpecification<T> condition)
{
return new IfCommand<T>(command, condition);
}
private class IfCommand<T> : ICommand<T>
{
public IfCommand(
ICommand<T> command, ISpecification<T> condition)
{
this.command = command;
this.condition = condition;
}
public void ExecuteFor(T obj)
{
if (condition.IsSatisfiedBy(obj))
command.ExecuteFor(obj);
}
private readonly ICommand<T> command;
private readonly ISpecification<T> condition;
}
21. Тогда код будет выглядеть:
new SubscriptionRenewCommand(dataContext)
.If(new SubscriptionCanBeRenewed(currentMoment))
.ExecuteFor(subscription);
Почти jQuery
Или даже монада? :)
22. • Аналогично для выполнения команды в цикле
• Для цепочки команд
• Если что, можно команды конструировать с
объектом контекста (мутабельным)
24. public static class SpecificationExtensions
{
public static ISpecification<T> And<T>(
this ISpecification<T> left, ISpecification<T> right)
{
return new AndSpecification(left, right);
}
public static ISpecification<T> Or<T>(
this ISpecification<T> left, ISpecification<T> right)
{
return new OrSpecification(left, right);
}
public static ISpecification<T> Not<T>(
this ISpecification<T> spec)
{
return new NotSpecification(spec);
}
}
40. • тяжело придумывать имена классов
o Правило: чем уже scope использования класса, тем длиннее
может быть его имя
o Плюс помогают постфиксы из шаблонов проектирования
• легко запутаться в таком количестве
классов
o Помогает (но не спасает) хорошая упаковка классов по
пакетам/пространствам имен
o см. ниже
• большая косвенность кода
o современные среды разработки слегка спасают
• как организовать взаимодействие?
o см. ниже
• есть проблемы с областью видимости
o Обычно на это плюют…
44. Пространства имен
• MyLogic
o MegaCool
• здесь ваши классы:
CoolClass, MegaClass, OtherClass
• вложенные пространства имен
o Internals.CoolClass
o Internals. MegaClass
o SomethingOther
55. typedef Shape* ShapeConstructor();
class Feature {
public:
static int registerShape(
int uniqueCode, ShapeConstructor* constructor)
{
shapeMap.insert(std::pair<int, ShapeConstructor*>(
uniqueCode, constructor));
return uniqueCode;
}
//…
bool read(File* file)
{
//...
auto it = shapeMap.find(type);
shape = it == shapeMap.end() ? new NullShape() : it->second();
return shape->read(file);
}
//…
private:
//…
static std::map<int, ShapeConstructor*> shapeMap;
};
56. Circle.h
class Circle : public Shape {
// …
private:
// …
static const int _readCodeNotUsed;
};
Circle.cpp
Shape* circleConstructor() { return new Circle(); }
int _readCodeNotUsed =
Feature::registerShape(1, &circleConstructor);
57. Если повторять этот подход на
Java/C#
при помощи статических конструкторов,
то надо не забыть
в статическом конструкторе Feature
принудительно инициировать все классы,
наследники от Shape
67. Решается специализированным тестом
в рамках тестов на MyPersistance:
через reflection перебираются
все классы из MyDomain,
реализующие ISpecification<>,
и проверяется, что для каждого из них
есть элемент в словаре
spec <-> visitorItem
68.
69. Наличие mixin-ов и duck-typing
в системе типов сильно бы помогло.
См. например, google GO